3ec8713b476514349c001f1854a4b9c05cc00ccf
[controller.git] / opendaylight / config / config-persister-file-adapter / src / main / java / org / opendaylight / controller / config / persist / storage / file / FileStorageAdapter.java
1 /*
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8
9 package org.opendaylight.controller.config.persist.storage.file;
10
11 import com.google.common.annotations.VisibleForTesting;
12 import com.google.common.base.Charsets;
13 import com.google.common.base.Optional;
14 import com.google.common.base.Preconditions;
15 import com.google.common.io.Files;
16 import org.apache.commons.lang3.StringUtils;
17 import org.opendaylight.controller.config.persist.api.ConfigSnapshotHolder;
18 import org.opendaylight.controller.config.persist.api.ConfigSnapshotHolderImpl;
19 import org.opendaylight.controller.config.persist.api.Persister;
20 import org.opendaylight.controller.config.persist.api.PropertiesProvider;
21 import org.opendaylight.controller.config.persist.api.StorageAdapter;
22 import org.slf4j.Logger;
23 import org.slf4j.LoggerFactory;
24
25 import java.io.File;
26 import java.io.IOException;
27 import java.nio.charset.Charset;
28 import java.util.Arrays;
29 import java.util.Collections;
30 import java.util.List;
31 import java.util.Set;
32 import java.util.SortedSet;
33 import java.util.TreeSet;
34
35 /**
36  * StorageAdapter that stores configuration in a plan file.
37  */
38 public class FileStorageAdapter implements StorageAdapter, Persister {
39     private static final Logger logger = LoggerFactory.getLogger(FileStorageAdapter.class);
40
41
42     private static final Charset ENCODING = Charsets.UTF_8;
43
44     public static final String FILE_STORAGE_PROP = "fileStorage";
45     public static final String NUMBER_OF_BACKUPS = "numberOfBackups";
46
47
48     private static final String SEPARATOR_E_PURE = "//END OF CONFIG";
49     private static final String SEPARATOR_E = newLine(SEPARATOR_E_PURE);
50
51     private static final String SEPARATOR_M_PURE = "//END OF SNAPSHOT";
52     private static final String SEPARATOR_M = newLine(SEPARATOR_M_PURE);
53
54     private static final String SEPARATOR_S = newLine("//START OF CONFIG");
55
56     private static final String SEPARATOR_SL_PURE = "//START OF CONFIG-LAST";
57     private static final String SEPARATOR_SL = newLine(SEPARATOR_SL_PURE);
58
59     private static Integer numberOfStoredBackups;
60     private File storage;
61
62     @Override
63     public Persister instantiate(PropertiesProvider propertiesProvider) {
64         File storage = extractStorageFileFromProperties(propertiesProvider);
65         logger.debug("Using file {}", storage.getAbsolutePath());
66         // Create file if it does not exist
67         File parentFile = storage.getAbsoluteFile().getParentFile();
68         if (parentFile.exists() == false) {
69             logger.debug("Creating parent folders {}", parentFile);
70             parentFile.mkdirs();
71         }
72         if (storage.exists() == false) {
73             logger.debug("Storage file does not exist, creating empty file");
74             try {
75                 boolean result = storage.createNewFile();
76                 if (result == false)
77                     throw new RuntimeException("Unable to create storage file " + storage);
78             } catch (IOException e) {
79                 throw new RuntimeException("Unable to create storage file " + storage, e);
80             }
81         }
82         if (numberOfStoredBackups == 0) {
83             throw new RuntimeException(NUMBER_OF_BACKUPS
84                     + " property should be either set to positive value, or ommited. Can not be set to 0.");
85         }
86         setFileStorage(storage);
87         return this;
88     }
89
90     @VisibleForTesting
91     void setFileStorage(File storage) {
92         this.storage = storage;
93     }
94
95     @VisibleForTesting
96     void setNumberOfBackups(Integer numberOfBackups) {
97         numberOfStoredBackups = numberOfBackups;
98     }
99
100     private static File extractStorageFileFromProperties(PropertiesProvider propertiesProvider) {
101         String fileStorageProperty = propertiesProvider.getProperty(FILE_STORAGE_PROP);
102         Preconditions.checkNotNull(fileStorageProperty, "Unable to find " + propertiesProvider.getFullKeyForReporting(FILE_STORAGE_PROP));
103         File result = new File(fileStorageProperty);
104         String numberOfBAckupsAsString = propertiesProvider.getProperty(NUMBER_OF_BACKUPS);
105         if (numberOfBAckupsAsString != null) {
106             numberOfStoredBackups = Integer.valueOf(numberOfBAckupsAsString);
107         } else {
108             numberOfStoredBackups = Integer.MAX_VALUE;
109         }
110         logger.trace("Property {} set to {}", NUMBER_OF_BACKUPS, numberOfStoredBackups);
111         return result;
112     }
113
114     private static String newLine(String string) {
115         return string + "\n";
116     }
117
118     @Override
119     public void persistConfig(ConfigSnapshotHolder holder) throws IOException {
120         Preconditions.checkNotNull(storage, "Storage file is null");
121
122         String content = Files.toString(storage, ENCODING);
123         if (numberOfStoredBackups == Integer.MAX_VALUE) {
124             resetLastConfig(content);
125             persistLastConfig(holder);
126         } else {
127             if (numberOfStoredBackups == 1) {
128                 Files.write("", storage, ENCODING);
129                 persistLastConfig(holder);
130             } else {
131                 int count = StringUtils.countMatches(content, SEPARATOR_S);
132                 if ((count + 1) < numberOfStoredBackups) {
133                     resetLastConfig(content);
134                     persistLastConfig(holder);
135                 } else {
136                     String contentSubString = StringUtils.substringBefore(content, SEPARATOR_E);
137                     contentSubString = contentSubString.concat(SEPARATOR_E_PURE);
138                     content = StringUtils.substringAfter(content, contentSubString);
139                     resetLastConfig(content);
140                     persistLastConfig(holder);
141                 }
142             }
143         }
144     }
145
146     private void resetLastConfig(String content) throws IOException {
147         content = content.replaceFirst(SEPARATOR_SL, SEPARATOR_S);
148         Files.write(content, storage, ENCODING);
149     }
150
151     private void persistLastConfig(ConfigSnapshotHolder holder) throws IOException {
152         Files.append(SEPARATOR_SL, storage, ENCODING);
153         String snapshotAsString = holder.getConfigSnapshot();
154         Files.append(newLine(snapshotAsString), storage, ENCODING);
155         Files.append(SEPARATOR_M, storage, ENCODING);
156         Files.append(toStringCaps(holder.getCapabilities()), storage, ENCODING);
157         Files.append(SEPARATOR_E, storage, ENCODING);
158     }
159
160     private CharSequence toStringCaps(Set<String> capabilities) {
161         StringBuilder b = new StringBuilder();
162         for (String capability : capabilities) {
163             b.append(newLine(capability));
164         }
165         return b.toString();
166     }
167
168     @Override
169     public List<ConfigSnapshotHolder> loadLastConfigs() throws IOException {
170         Preconditions.checkNotNull(storage, "Storage file is null");
171
172         if (!storage.exists()) {
173             return Collections.emptyList();
174         }
175
176         final LineProcessor lineProcessor = new LineProcessor();
177         Files.readLines(storage, ENCODING, lineProcessor);
178
179         if (lineProcessor.getConfigSnapshot().isPresent() == false) {
180             return Collections.emptyList();
181         } else {
182             return Arrays.<ConfigSnapshotHolder>asList(new ConfigSnapshotHolderImpl(lineProcessor.getConfigSnapshot().get(),
183                     lineProcessor.getCapabilities(), storage.getAbsolutePath()));
184         }
185
186     }
187
188     private static final class LineProcessor implements com.google.common.io.LineProcessor<String> {
189
190         private boolean inLastConfig, inLastSnapshot;
191         private final StringBuffer snapshotBuffer = new StringBuffer();
192         private final SortedSet<String> caps = new TreeSet<>();
193
194         @Override
195         public String getResult() {
196             return null;
197         }
198
199         @Override
200         public boolean processLine(String line) throws IOException {
201             if (inLastConfig && line.equals(SEPARATOR_E_PURE)) {
202                 inLastConfig = false;
203                 return false;
204             }
205
206             if (inLastConfig && line.equals(SEPARATOR_M_PURE)) {
207                 inLastSnapshot = false;
208                 return true;
209             }
210
211             if (inLastConfig) {
212                 if (inLastSnapshot) {
213                     snapshotBuffer.append(line);
214                     snapshotBuffer.append(System.lineSeparator());
215                 } else {
216                     caps.add(line);
217                 }
218             }
219
220             if (line.equals(SEPARATOR_SL_PURE)) {
221                 inLastConfig = true;
222                 inLastSnapshot = true;
223             }
224
225             return true;
226         }
227
228         Optional<String> getConfigSnapshot() {
229             final String xmlContent = snapshotBuffer.toString();
230             if (xmlContent.equals("")) {
231                 return Optional.absent();
232             } else {
233                 return Optional.of(xmlContent);
234             }
235         }
236
237         SortedSet<String> getCapabilities() {
238             return caps;
239         }
240
241     }
242
243     @Override
244     public void close() {
245
246     }
247
248     @Override
249     public String toString() {
250         return "FileStorageAdapter [storage=" + storage + "]";
251     }
252
253 }