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.io.Files;
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;
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;
35 * StorageAdapter that stores configuration in a plain file.
37 public class FileStorageAdapter implements StorageAdapter, Persister {
38 private static final Logger logger = LoggerFactory.getLogger(FileStorageAdapter.class);
41 private static final Charset ENCODING = Charsets.UTF_8;
43 public static final String FILE_STORAGE_PROP = "fileStorage";
44 public static final String NUMBER_OF_BACKUPS = "numberOfBackups";
47 private static final String SEPARATOR_E_PURE = "//END OF CONFIG";
48 private static final String SEPARATOR_E = newLine(SEPARATOR_E_PURE);
50 private static final String SEPARATOR_M_PURE = "//END OF SNAPSHOT";
51 private static final String SEPARATOR_M = newLine(SEPARATOR_M_PURE);
53 private static final String SEPARATOR_S = newLine("//START OF CONFIG");
55 private static final String SEPARATOR_SL_PURE = "//START OF CONFIG-LAST";
56 private static final String SEPARATOR_SL = newLine(SEPARATOR_SL_PURE);
58 private static Integer numberOfStoredBackups;
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);
71 if (storage.exists() == false) {
72 logger.debug("Storage file does not exist, creating empty file");
74 boolean result = storage.createNewFile();
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);
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.");
85 setFileStorage(storage);
90 void setFileStorage(File storage) {
91 this.storage = storage;
95 void setNumberOfBackups(Integer numberOfBackups) {
96 numberOfStoredBackups = numberOfBackups;
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);
107 numberOfStoredBackups = Integer.MAX_VALUE;
109 logger.trace("Property {} set to {}", NUMBER_OF_BACKUPS, numberOfStoredBackups);
113 private static String newLine(String string) {
114 return string + "\n";
118 public void persistConfig(ConfigSnapshotHolder holder) throws IOException {
119 Preconditions.checkNotNull(storage, "Storage file is null");
121 String content = Files.toString(storage, ENCODING);
122 if (numberOfStoredBackups == Integer.MAX_VALUE) {
123 resetLastConfig(content);
124 persistLastConfig(holder);
126 if (numberOfStoredBackups == 1) {
127 Files.write("", storage, ENCODING);
128 persistLastConfig(holder);
130 int count = StringUtils.countMatches(content, SEPARATOR_S);
131 if ((count + 1) < numberOfStoredBackups) {
132 resetLastConfig(content);
133 persistLastConfig(holder);
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);
145 private void resetLastConfig(String content) throws IOException {
146 content = content.replaceFirst(SEPARATOR_SL, SEPARATOR_S);
147 Files.write(content, storage, ENCODING);
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);
159 private CharSequence toStringCaps(Set<String> capabilities) {
160 StringBuilder b = new StringBuilder();
161 for (String capability : capabilities) {
162 b.append(newLine(capability));
168 public List<ConfigSnapshotHolder> loadLastConfigs() throws IOException {
169 Preconditions.checkNotNull(storage, "Storage file is null");
171 if (!storage.exists()) {
172 return Collections.emptyList();
175 final LineProcessor lineProcessor = new LineProcessor();
176 Files.readLines(storage, ENCODING, lineProcessor);
178 if (lineProcessor.getConfigSnapshot().isPresent() == false) {
179 return Collections.emptyList();
181 return Arrays.<ConfigSnapshotHolder>asList(new ConfigSnapshotHolderImpl(lineProcessor.getConfigSnapshot().get(),
182 lineProcessor.getCapabilities(), storage.getAbsolutePath()));
187 private static final class LineProcessor implements com.google.common.io.LineProcessor<String> {
189 private boolean inLastConfig, inLastSnapshot;
190 private final StringBuffer snapshotBuffer = new StringBuffer();
191 private final SortedSet<String> caps = new TreeSet<>();
194 public String getResult() {
199 public boolean processLine(String line) throws IOException {
200 if (inLastConfig && line.equals(SEPARATOR_E_PURE)) {
201 inLastConfig = false;
205 if (inLastConfig && line.equals(SEPARATOR_M_PURE)) {
206 inLastSnapshot = false;
211 if (inLastSnapshot) {
212 snapshotBuffer.append(line);
213 snapshotBuffer.append(System.lineSeparator());
219 if (line.equals(SEPARATOR_SL_PURE)) {
221 inLastSnapshot = true;
227 Optional<String> getConfigSnapshot() {
228 final String xmlContent = snapshotBuffer.toString();
229 if (xmlContent.equals("")) {
230 return Optional.absent();
232 return Optional.of(xmlContent);
236 SortedSet<String> getCapabilities() {
243 public void close() {
248 public String toString() {
249 return "FileStorageAdapter [storage=" + storage + "]";