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