2 * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
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
9 package org.opendaylight.controller.config.persist.storage.file;
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;
23 import javax.xml.parsers.ParserConfigurationException;
25 import java.io.IOException;
26 import java.nio.charset.Charset;
30 * StorageAdapter that stores configuration in a plan file.
32 public class FileStorageAdapter implements StorageAdapter {
33 private static final Logger logger = LoggerFactory.getLogger(FileStorageAdapter.class);
35 // TODO prefix properties
37 private static final Charset ENCODING = Charsets.UTF_8;
39 public static final String FILE_STORAGE_PROP = "fileStorage";
40 public static final String NUMBER_OF_BACKUPS = "numberOfBackups";
42 private static final String SEPARATOR_E_PURE = "//END OF CONFIG";
43 private static final String SEPARATOR_E = newLine(SEPARATOR_E_PURE);
45 private static final String SEPARATOR_M_PURE = "//END OF SNAPSHOT";
46 private static final String SEPARATOR_M = newLine(SEPARATOR_M_PURE);
48 private static final String SEPARATOR_S = newLine("//START OF CONFIG");
50 private static final String SEPARATOR_SL_PURE = "//START OF CONFIG-LAST";
51 private static final String SEPARATOR_SL = newLine(SEPARATOR_SL_PURE);
53 private static Integer numberOfStoredBackups;
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);
66 if (storage.exists() == false) {
67 logger.debug("Storage file does not exist, creating empty file");
69 boolean result = storage.createNewFile();
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);
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.");
80 setFileStorage(storage);
85 void setFileStorage(File storage) {
86 this.storage = storage;
90 void setNumberOfBackups(Integer numberOfBackups) {
91 numberOfStoredBackups = numberOfBackups;
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);
102 numberOfStoredBackups = Integer.MAX_VALUE;
108 private static String newLine(String string) {
109 return string + "\n";
113 public void persistConfig(ConfigSnapshotHolder holder) throws IOException {
114 Preconditions.checkNotNull(storage, "Storage file is null");
116 String content = Files.toString(storage, ENCODING);
117 if (numberOfStoredBackups == Integer.MAX_VALUE) {
118 resetLastConfig(content);
119 persistLastConfig(holder);
121 if (numberOfStoredBackups == 1) {
122 Files.write("", storage, ENCODING);
123 persistLastConfig(holder);
125 int count = StringUtils.countMatches(content, SEPARATOR_S);
126 if ((count + 1) < numberOfStoredBackups) {
127 resetLastConfig(content);
128 persistLastConfig(holder);
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);
140 private void resetLastConfig(String content) throws IOException {
141 content = content.replaceFirst(SEPARATOR_SL, SEPARATOR_S);
142 Files.write(content, storage, ENCODING);
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);
154 private CharSequence toStringCaps(Set<String> capabilities) {
155 StringBuilder b = new StringBuilder();
156 for (String capability : capabilities) {
157 b.append(newLine(capability));
163 public Optional<ConfigSnapshotHolder> loadLastConfig() throws IOException {
164 Preconditions.checkNotNull(storage, "Storage file is null");
166 if (!storage.exists()) {
167 return Optional.absent();
170 final LineProcessor lineProcessor = new LineProcessor();
171 String result = Files.readLines(storage, ENCODING, lineProcessor);
174 if (lineProcessor.getConfigSnapshot().isPresent() == false) {
175 return Optional.absent();
177 return Optional.<ConfigSnapshotHolder> of(new PersistedConfigImpl(lineProcessor.getConfigSnapshot(),
178 lineProcessor.getCapabilities()));
181 } catch (ParserConfigurationException | SAXException e) {
182 throw new IOException("Unable to load last config ", e);
186 private static final class LineProcessor implements com.google.common.io.LineProcessor<String> {
188 private boolean inLastConfig, inLastSnapshot;
189 private final StringBuffer snapshotBuffer = new StringBuffer();
190 private final Set<String> caps = Sets.newHashSet();
193 public String getResult() {
198 public boolean processLine(String line) throws IOException {
199 if (inLastConfig && line.equals(SEPARATOR_E_PURE)) {
200 inLastConfig = false;
204 if (inLastConfig && line.equals(SEPARATOR_M_PURE)) {
205 inLastSnapshot = false;
210 if (inLastSnapshot) {
211 snapshotBuffer.append(line);
212 snapshotBuffer.append(System.lineSeparator());
218 if (line.equals(SEPARATOR_SL_PURE)) {
220 inLastSnapshot = true;
226 Optional<String> getConfigSnapshot() throws IOException, SAXException, ParserConfigurationException {
227 final String xmlContent = snapshotBuffer.toString();
228 if (xmlContent == null || xmlContent.equals("")) {
229 return Optional.absent();
231 return Optional.of(xmlContent);
234 Set<String> getCapabilities() throws IOException, SAXException, ParserConfigurationException {
241 public void close() throws IOException {
246 public String toString() {
247 return "FileStorageAdapter [storage=" + storage + "]";
250 private class PersistedConfigImpl implements ConfigSnapshotHolder {
252 private final String snapshot;
253 private final Set<String> caps;
255 public PersistedConfigImpl(Optional<String> configSnapshot, Set<String> capabilities) {
256 this.snapshot = configSnapshot.get();
257 this.caps = capabilities;
261 public String getConfigSnapshot() {
266 public Set<String> getCapabilities() {