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