increased test coverage of config-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.io.Files;
16 import java.io.File;
17 import java.io.IOException;
18 import java.nio.charset.Charset;
19 import java.util.Arrays;
20 import java.util.Collections;
21 import java.util.List;
22 import java.util.Set;
23 import java.util.SortedSet;
24 import java.util.TreeSet;
25 import org.apache.commons.lang3.StringUtils;
26 import org.opendaylight.controller.config.persist.api.ConfigSnapshotHolder;
27 import org.opendaylight.controller.config.persist.api.ConfigSnapshotHolderImpl;
28 import org.opendaylight.controller.config.persist.api.Persister;
29 import org.opendaylight.controller.config.persist.api.PropertiesProvider;
30 import org.opendaylight.controller.config.persist.api.StorageAdapter;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33
34 /**
35  * StorageAdapter that stores configuration in a plain file.
36  */
37 public class FileStorageAdapter implements StorageAdapter, Persister {
38     private static final Logger logger = LoggerFactory.getLogger(FileStorageAdapter.class);
39
40
41     private static final Charset ENCODING = Charsets.UTF_8;
42
43     public static final String FILE_STORAGE_PROP = "fileStorage";
44     public static final String NUMBER_OF_BACKUPS = "numberOfBackups";
45
46
47     private static final String SEPARATOR_E_PURE = "//END OF CONFIG";
48     private static final String SEPARATOR_E = newLine(SEPARATOR_E_PURE);
49
50     private static final String SEPARATOR_M_PURE = "//END OF SNAPSHOT";
51     private static final String SEPARATOR_M = newLine(SEPARATOR_M_PURE);
52
53     private static final String SEPARATOR_S = newLine("//START OF CONFIG");
54
55     private static final String SEPARATOR_SL_PURE = "//START OF CONFIG-LAST";
56     private static final String SEPARATOR_SL = newLine(SEPARATOR_SL_PURE);
57
58     private static Integer numberOfStoredBackups;
59     private File storage;
60
61     @Override
62     public Persister instantiate(PropertiesProvider propertiesProvider) {
63         File storage = extractStorageFileFromProperties(propertiesProvider);
64         logger.debug("Using file {}", storage.getAbsolutePath());
65         // Create file if it does not exist
66         File parentFile = storage.getAbsoluteFile().getParentFile();
67         if (parentFile.exists() == false) {
68             logger.debug("Creating parent folders {}", parentFile);
69             parentFile.mkdirs();
70         }
71         if (storage.exists() == false) {
72             logger.debug("Storage file does not exist, creating empty file");
73             try {
74                 boolean result = storage.createNewFile();
75                 if (result == false)
76                     throw new RuntimeException("Unable to create storage file " + storage);
77             } catch (IOException e) {
78                 throw new RuntimeException("Unable to create storage file " + storage, e);
79             }
80         }
81         if (numberOfStoredBackups == 0) {
82             throw new RuntimeException(NUMBER_OF_BACKUPS
83                     + " property should be either set to positive value, or ommited. Can not be set to 0.");
84         }
85         setFileStorage(storage);
86         return this;
87     }
88
89     @VisibleForTesting
90     void setFileStorage(File storage) {
91         this.storage = storage;
92     }
93
94     @VisibleForTesting
95     void setNumberOfBackups(Integer numberOfBackups) {
96         numberOfStoredBackups = numberOfBackups;
97     }
98
99     private static File extractStorageFileFromProperties(PropertiesProvider propertiesProvider) {
100         String fileStorageProperty = propertiesProvider.getProperty(FILE_STORAGE_PROP);
101         Preconditions.checkNotNull(fileStorageProperty, "Unable to find " + propertiesProvider.getFullKeyForReporting(FILE_STORAGE_PROP));
102         File result = new File(fileStorageProperty);
103         String numberOfBAckupsAsString = propertiesProvider.getProperty(NUMBER_OF_BACKUPS);
104         if (numberOfBAckupsAsString != null) {
105             numberOfStoredBackups = Integer.valueOf(numberOfBAckupsAsString);
106         } else {
107             numberOfStoredBackups = Integer.MAX_VALUE;
108         }
109         logger.trace("Property {} set to {}", NUMBER_OF_BACKUPS, numberOfStoredBackups);
110         return result;
111     }
112
113     private static String newLine(String string) {
114         return string + "\n";
115     }
116
117     @Override
118     public void persistConfig(ConfigSnapshotHolder holder) throws IOException {
119         Preconditions.checkNotNull(storage, "Storage file is null");
120
121         String content = Files.toString(storage, ENCODING);
122         if (numberOfStoredBackups == Integer.MAX_VALUE) {
123             resetLastConfig(content);
124             persistLastConfig(holder);
125         } else {
126             if (numberOfStoredBackups == 1) {
127                 Files.write("", storage, ENCODING);
128                 persistLastConfig(holder);
129             } else {
130                 int count = StringUtils.countMatches(content, SEPARATOR_S);
131                 if ((count + 1) < numberOfStoredBackups) {
132                     resetLastConfig(content);
133                     persistLastConfig(holder);
134                 } else {
135                     String contentSubString = StringUtils.substringBefore(content, SEPARATOR_E);
136                     contentSubString = contentSubString.concat(SEPARATOR_E_PURE);
137                     content = StringUtils.substringAfter(content, contentSubString);
138                     resetLastConfig(content);
139                     persistLastConfig(holder);
140                 }
141             }
142         }
143     }
144
145     private void resetLastConfig(String content) throws IOException {
146         content = content.replaceFirst(SEPARATOR_SL, SEPARATOR_S);
147         Files.write(content, storage, ENCODING);
148     }
149
150     private void persistLastConfig(ConfigSnapshotHolder holder) throws IOException {
151         Files.append(SEPARATOR_SL, storage, ENCODING);
152         String snapshotAsString = holder.getConfigSnapshot();
153         Files.append(newLine(snapshotAsString), storage, ENCODING);
154         Files.append(SEPARATOR_M, storage, ENCODING);
155         Files.append(toStringCaps(holder.getCapabilities()), storage, ENCODING);
156         Files.append(SEPARATOR_E, storage, ENCODING);
157     }
158
159     private CharSequence toStringCaps(Set<String> capabilities) {
160         StringBuilder b = new StringBuilder();
161         for (String capability : capabilities) {
162             b.append(newLine(capability));
163         }
164         return b.toString();
165     }
166
167     @Override
168     public List<ConfigSnapshotHolder> loadLastConfigs() throws IOException {
169         Preconditions.checkNotNull(storage, "Storage file is null");
170
171         if (!storage.exists()) {
172             return Collections.emptyList();
173         }
174
175         final LineProcessor lineProcessor = new LineProcessor();
176         Files.readLines(storage, ENCODING, lineProcessor);
177
178         if (lineProcessor.getConfigSnapshot().isPresent() == false) {
179             return Collections.emptyList();
180         } else {
181             return Arrays.<ConfigSnapshotHolder>asList(new ConfigSnapshotHolderImpl(lineProcessor.getConfigSnapshot().get(),
182                     lineProcessor.getCapabilities(), storage.getAbsolutePath()));
183         }
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() {
228             final String xmlContent = snapshotBuffer.toString();
229             if (xmlContent.equals("")) {
230                 return Optional.absent();
231             } else {
232                 return Optional.of(xmlContent);
233             }
234         }
235
236         SortedSet<String> getCapabilities() {
237             return caps;
238         }
239
240     }
241
242     @Override
243     public void close() {
244
245     }
246
247     @Override
248     public String toString() {
249         return "FileStorageAdapter [storage=" + storage + "]";
250     }
251
252 }