d3508939d78ffc9b676c7b0e5c85e51be789ddfe
[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.storage.StorageAdapter;
18 import org.slf4j.Logger;
19 import org.slf4j.LoggerFactory;
20 import org.xml.sax.SAXException;
21
22 import javax.xml.parsers.ParserConfigurationException;
23 import java.io.File;
24 import java.io.IOException;
25 import java.nio.charset.Charset;
26 import java.util.Set;
27 import java.util.SortedSet;
28 import java.util.TreeSet;
29
30 /**
31  * StorageAdapter that stores configuration in a plan file.
32  */
33 public class FileStorageAdapter implements StorageAdapter {
34     private static final Logger logger = LoggerFactory.getLogger(FileStorageAdapter.class);
35
36     // TODO prefix properties
37
38     private static final Charset ENCODING = Charsets.UTF_8;
39
40     public static final String FILE_STORAGE_PROP = "fileStorage";
41     public static final String NUMBER_OF_BACKUPS = "numberOfBackups";
42
43     private static final String SEPARATOR_E_PURE = "//END OF CONFIG";
44     private static final String SEPARATOR_E = newLine(SEPARATOR_E_PURE);
45
46     private static final String SEPARATOR_M_PURE = "//END OF SNAPSHOT";
47     private static final String SEPARATOR_M = newLine(SEPARATOR_M_PURE);
48
49     private static final String SEPARATOR_S = newLine("//START OF CONFIG");
50
51     private static final String SEPARATOR_SL_PURE = "//START OF CONFIG-LAST";
52     private static final String SEPARATOR_SL = newLine(SEPARATOR_SL_PURE);
53
54     private static Integer numberOfStoredBackups;
55     private File storage;
56
57     @Override
58     public void setProperties(PropertiesProvider propertiesProvider) {
59         File storage = extractStorageFileFromProperties(propertiesProvider);
60         logger.debug("Using file {}", storage.getAbsolutePath());
61         // Create file if it does not exist
62         File parentFile = storage.getAbsoluteFile().getParentFile();
63         if (parentFile.exists() == false) {
64             logger.debug("Creating parent folders {}", parentFile);
65             parentFile.mkdirs();
66         }
67         if (storage.exists() == false) {
68             logger.debug("Storage file does not exist, creating empty file");
69             try {
70                 boolean result = storage.createNewFile();
71                 if (result == false)
72                     throw new RuntimeException("Unable to create storage file " + storage);
73             } catch (IOException e) {
74                 throw new RuntimeException("Unable to create storage file " + storage, e);
75             }
76         }
77         if (numberOfStoredBackups == 0) {
78             throw new RuntimeException(NUMBER_OF_BACKUPS
79                     + " property should be either set to positive value, or ommited. Can not be set to 0.");
80         }
81         setFileStorage(storage);
82
83     }
84
85     @VisibleForTesting
86     void setFileStorage(File storage) {
87         this.storage = storage;
88     }
89
90     @VisibleForTesting
91     void setNumberOfBackups(Integer numberOfBackups) {
92         numberOfStoredBackups = numberOfBackups;
93     }
94
95     private static File extractStorageFileFromProperties(PropertiesProvider propertiesProvider) {
96         String fileStorageProperty = propertiesProvider.getProperty(FILE_STORAGE_PROP);
97         Preconditions.checkNotNull(fileStorageProperty, "Unable to find " + propertiesProvider.getFullKeyForReporting(FILE_STORAGE_PROP));
98         File result = new File(fileStorageProperty);
99         String numberOfBAckupsAsString = propertiesProvider.getProperty(NUMBER_OF_BACKUPS);
100         if (numberOfBAckupsAsString != null) {
101             numberOfStoredBackups = Integer.valueOf(numberOfBAckupsAsString);
102         } else {
103             numberOfStoredBackups = Integer.MAX_VALUE;
104         }
105
106         return result;
107     }
108
109     private static String newLine(String string) {
110         return string + "\n";
111     }
112
113     @Override
114     public void persistConfig(ConfigSnapshotHolder holder) throws IOException {
115         Preconditions.checkNotNull(storage, "Storage file is null");
116
117         String content = Files.toString(storage, ENCODING);
118         if (numberOfStoredBackups == Integer.MAX_VALUE) {
119             resetLastConfig(content);
120             persistLastConfig(holder);
121         } else {
122             if (numberOfStoredBackups == 1) {
123                 Files.write("", storage, ENCODING);
124                 persistLastConfig(holder);
125             } else {
126                 int count = StringUtils.countMatches(content, SEPARATOR_S);
127                 if ((count + 1) < numberOfStoredBackups) {
128                     resetLastConfig(content);
129                     persistLastConfig(holder);
130                 } else {
131                     String contentSubString = StringUtils.substringBefore(content, SEPARATOR_E);
132                     contentSubString = contentSubString.concat(SEPARATOR_E_PURE);
133                     content = StringUtils.substringAfter(content, contentSubString);
134                     resetLastConfig(content);
135                     persistLastConfig(holder);
136                 }
137             }
138         }
139     }
140
141     private void resetLastConfig(String content) throws IOException {
142         content = content.replaceFirst(SEPARATOR_SL, SEPARATOR_S);
143         Files.write(content, storage, ENCODING);
144     }
145
146     private void persistLastConfig(ConfigSnapshotHolder holder) throws IOException {
147         Files.append(SEPARATOR_SL, storage, ENCODING);
148         String snapshotAsString = holder.getConfigSnapshot();
149         Files.append(newLine(snapshotAsString), storage, ENCODING);
150         Files.append(SEPARATOR_M, storage, ENCODING);
151         Files.append(toStringCaps(holder.getCapabilities()), storage, ENCODING);
152         Files.append(SEPARATOR_E, storage, ENCODING);
153     }
154
155     private CharSequence toStringCaps(Set<String> capabilities) {
156         StringBuilder b = new StringBuilder();
157         for (String capability : capabilities) {
158             b.append(newLine(capability));
159         }
160         return b.toString();
161     }
162
163     @Override
164     public Optional<ConfigSnapshotHolder> loadLastConfig() throws IOException {
165         Preconditions.checkNotNull(storage, "Storage file is null");
166
167         if (!storage.exists()) {
168             return Optional.absent();
169         }
170
171         final LineProcessor lineProcessor = new LineProcessor();
172         String result = Files.readLines(storage, ENCODING, lineProcessor);
173
174         try {
175             if (lineProcessor.getConfigSnapshot().isPresent() == false) {
176                 return Optional.absent();
177             } else {
178                 return Optional.<ConfigSnapshotHolder> of(new PersistedConfigImpl(lineProcessor.getConfigSnapshot(),
179                         lineProcessor.getCapabilities()));
180             }
181
182         } catch (ParserConfigurationException | SAXException e) {
183             throw new IOException("Unable to load last config ", e);
184         }
185     }
186
187     private static final class LineProcessor implements com.google.common.io.LineProcessor<String> {
188
189         private boolean inLastConfig, inLastSnapshot;
190         private final StringBuffer snapshotBuffer = new StringBuffer();
191         private final SortedSet<String> caps = new TreeSet<>();
192
193         @Override
194         public String getResult() {
195             return null;
196         }
197
198         @Override
199         public boolean processLine(String line) throws IOException {
200             if (inLastConfig && line.equals(SEPARATOR_E_PURE)) {
201                 inLastConfig = false;
202                 return false;
203             }
204
205             if (inLastConfig && line.equals(SEPARATOR_M_PURE)) {
206                 inLastSnapshot = false;
207                 return true;
208             }
209
210             if (inLastConfig) {
211                 if (inLastSnapshot) {
212                     snapshotBuffer.append(line);
213                     snapshotBuffer.append(System.lineSeparator());
214                 } else {
215                     caps.add(line);
216                 }
217             }
218
219             if (line.equals(SEPARATOR_SL_PURE)) {
220                 inLastConfig = true;
221                 inLastSnapshot = true;
222             }
223
224             return true;
225         }
226
227         Optional<String> getConfigSnapshot() throws IOException, SAXException, ParserConfigurationException {
228             final String xmlContent = snapshotBuffer.toString();
229             if (xmlContent == null || xmlContent.equals("")) {
230                 return Optional.absent();
231             } else
232                 return Optional.of(xmlContent);
233         }
234
235         SortedSet<String> getCapabilities() throws IOException, SAXException, ParserConfigurationException {
236             return caps;
237         }
238
239     }
240
241     @Override
242     public void close() throws IOException {
243
244     }
245
246     @Override
247     public String toString() {
248         return "FileStorageAdapter [storage=" + storage + "]";
249     }
250
251     private class PersistedConfigImpl implements ConfigSnapshotHolder {
252
253         private final String snapshot;
254         private final SortedSet<String> caps;
255
256         public PersistedConfigImpl(Optional<String> configSnapshot, SortedSet<String> capabilities) {
257             this.snapshot = configSnapshot.get();
258             this.caps = capabilities;
259         }
260
261         @Override
262         public String getConfigSnapshot() {
263             return snapshot;
264         }
265
266         @Override
267         public SortedSet<String> getCapabilities() {
268             return caps;
269         }
270     }
271
272 }