+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.netconf.persist.impl;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Optional;
+import org.opendaylight.controller.config.persist.api.ConfigSnapshotHolder;
+import org.opendaylight.controller.config.persist.api.Persister;
+import org.opendaylight.controller.config.persist.api.StorageAdapter;
+import org.opendaylight.controller.netconf.persist.impl.osgi.ConfigPersisterActivator;
+import org.opendaylight.controller.netconf.persist.impl.osgi.PropertiesProviderBaseImpl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ListIterator;
+
+/**
+ * {@link Persister} implementation that delegates persisting functionality to
+ * underlying {@link Persister} storages. Each storage has unique id, class, readonly value.
+ *
+ * Storage adapters are low level persisters that do the heavy lifting for this
+ * class. Instances of storage adapters can be injected directly via constructor
+ * or instantiated from a full name of its class provided in a properties file.
+ *
+ * Example configuration:<pre>
+ netconf.config.persister.active=2,3
+ # read startup configuration
+ netconf.config.persister.1.storageAdapterClass=org.opendaylight.controller.config.persist.storage.directory.DirectoryStorageAdapter
+ netconf.config.persister.1.properties.fileStorage=configuration/initial/
+
+ netconf.config.persister.2.storageAdapterClass=org.opendaylight.controller.config.persist.storage.file.FileStorageAdapter
+ netconf.config.persister.2.readonly=true
+ netconf.config.persister.2.properties.fileStorage=configuration/current/controller.config.1.txt
+
+ netconf.config.persister.3.storageAdapterClass=org.opendaylight.controller.config.persist.storage.file.FileStorageAdapter
+ netconf.config.persister.3.properties.fileStorage=configuration/current/controller.config.2.txt
+ netconf.config.persister.3.properties.numberOfBackups=3
+
+ </pre>
+ * During server startup {@link ConfigPersisterNotificationHandler} requests last snapshot from underlying storages.
+ * Each storage can respond by giving snapshot or absent response.
+ * The {@link #loadLastConfig()} will search for first non-absent response from storages ordered backwards as user
+ * specified (first '3', then '2').
+ *
+ * When a commit notification is received, '2' will be omitted because readonly flag is set to true, so
+ * only '3' will have a chance to persist new configuration. If readonly was false or not specified, both storage adapters
+ * would be called in order specified by 'netconf.config.persister' property.
+ *
+ */
+public final class PersisterAggregator implements Persister {
+ private static final Logger logger = LoggerFactory.getLogger(PersisterAggregator.class);
+
+ public static class PersisterWithConfiguration {
+
+ public final Persister storage;
+ private final boolean readOnly;
+
+ public PersisterWithConfiguration(Persister storage, boolean readOnly) {
+ this.storage = storage;
+ this.readOnly = readOnly;
+ }
+
+ @Override
+ public String toString() {
+ return "PersisterWithConfiguration{" +
+ "storage=" + storage +
+ ", readOnly=" + readOnly +
+ '}';
+ }
+ }
+
+ private static PersisterWithConfiguration loadConfiguration(final String index, final PropertiesProviderBaseImpl propertiesProvider) {
+
+ String classKey = index + "." + ConfigPersisterActivator.STORAGE_ADAPTER_CLASS_PROP_SUFFIX;
+ String storageAdapterClass = propertiesProvider.getProperty(classKey);
+ StorageAdapter storageAdapter;
+ if (storageAdapterClass == null || storageAdapterClass.equals("")) {
+ throw new IllegalStateException("No persister is defined in " +
+ propertiesProvider.getFullKeyForReporting(classKey)
+ + " property. Persister is not operational");
+ }
+
+ try {
+ Class<?> clazz = Class.forName(storageAdapterClass);
+ boolean implementsCorrectIfc = StorageAdapter.class.isAssignableFrom(clazz);
+ if (implementsCorrectIfc == false) {
+ throw new IllegalArgumentException("Storage adapter " + clazz + " does not implement " + StorageAdapter.class);
+ }
+ storageAdapter = StorageAdapter.class.cast(clazz.newInstance());
+
+ boolean readOnly = false;
+ String readOnlyProperty = propertiesProvider.getProperty(index + "." + "readonly");
+ if (readOnlyProperty != null && readOnlyProperty.equals("true")) {
+ readOnly = true;
+ }
+
+ PropertiesProviderAdapterImpl innerProvider = new PropertiesProviderAdapterImpl(propertiesProvider, index);
+ Persister storage = storageAdapter.instantiate(innerProvider);
+ return new PersisterWithConfiguration(storage, readOnly);
+ } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
+ throw new IllegalArgumentException("Unable to instantiate storage adapter from " + storageAdapterClass, e);
+ }
+ }
+
+ /**
+ * Persisters ordered by 'netconf.config.persister' property.
+ */
+ private final List<PersisterWithConfiguration> persisterWithConfigurations;
+
+ public PersisterAggregator(List<PersisterWithConfiguration> persisterWithConfigurations) {
+ this.persisterWithConfigurations = persisterWithConfigurations;
+
+ }
+
+ public static PersisterAggregator createFromProperties(PropertiesProviderBaseImpl propertiesProvider) {
+ List<PersisterWithConfiguration> persisterWithConfigurations = new ArrayList<>();
+ String prefixes = propertiesProvider.getProperty("active");
+ if (prefixes.isEmpty() == false) {
+ String [] keys = prefixes.split(",");
+ for (String index: keys) {
+ persisterWithConfigurations.add(PersisterAggregator.loadConfiguration(index, propertiesProvider));
+ }
+ }
+ logger.debug("Initialized persister with following adapters {}", persisterWithConfigurations);
+ return new PersisterAggregator(persisterWithConfigurations);
+ }
+
+ @Override
+ public void persistConfig(ConfigSnapshotHolder holder) throws IOException {
+ for (PersisterWithConfiguration persisterWithConfiguration: persisterWithConfigurations){
+ if (!persisterWithConfiguration.readOnly){
+ logger.debug("Calling {}.persistConfig",persisterWithConfiguration.storage);
+ persisterWithConfiguration.storage.persistConfig(holder);
+ }
+ }
+ }
+
+ @Override
+ public Optional<ConfigSnapshotHolder> loadLastConfig() throws IOException {
+ // iterate in reverse order
+ ListIterator<PersisterWithConfiguration> li = persisterWithConfigurations.listIterator(persisterWithConfigurations.size());
+ while(li.hasPrevious()) {
+ PersisterWithConfiguration persisterWithConfiguration = li.previous();
+ Optional<ConfigSnapshotHolder> configSnapshotHolderOptional = persisterWithConfiguration.storage.loadLastConfig();
+ if (configSnapshotHolderOptional.isPresent()) {
+ return configSnapshotHolderOptional;
+ }
+ }
+ // no storage had an answer
+ return Optional.absent();
+ }
+
+ @VisibleForTesting
+ List<PersisterWithConfiguration> getPersisterWithConfigurations() {
+ return persisterWithConfigurations;
+ }
+
+ @Override
+ public void close() {
+ RuntimeException lastException = null;
+ for (PersisterWithConfiguration persisterWithConfiguration: persisterWithConfigurations){
+ try{
+ persisterWithConfiguration.storage.close();
+ }catch(RuntimeException e) {
+ logger.error("Error while closing {}", persisterWithConfiguration.storage, e);
+ if (lastException == null){
+ lastException = e;
+ } else {
+ lastException.addSuppressed(e);
+ }
+ }
+ }
+ if (lastException != null){
+ throw lastException;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "PersisterAggregator{" +
+ "persisterWithConfigurations=" + persisterWithConfigurations +
+ '}';
+ }
+}