Convert blueprint ext classes to MDSAL APIs
[controller.git] / opendaylight / blueprint / src / main / java / org / opendaylight / controller / blueprint / ext / DataStoreAppConfigMetadata.java
index f8a6127f0a29fd39a017966e1fdf541c455369bb..b2801d052f3a0e595eaf0865e45974ae67ac80b9 100644 (file)
@@ -7,52 +7,42 @@
  */
 package org.opendaylight.controller.blueprint.ext;
 
-import com.google.common.base.Optional;
-import com.google.common.base.Preconditions;
-import com.google.common.base.Strings;
-import com.google.common.util.concurrent.CheckedFuture;
+import com.google.common.util.concurrent.FluentFuture;
 import com.google.common.util.concurrent.FutureCallback;
-import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.MoreExecutors;
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
 import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.concurrent.atomic.AtomicBoolean;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.stream.XMLStreamException;
 import org.apache.aries.blueprint.services.ExtendedBlueprintContainer;
-import org.opendaylight.controller.md.sal.binding.api.ClusteredDataTreeChangeListener;
-import org.opendaylight.controller.md.sal.binding.api.DataBroker;
-import org.opendaylight.controller.md.sal.binding.api.DataObjectModification;
-import org.opendaylight.controller.md.sal.binding.api.DataObjectModification.ModificationType;
-import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier;
-import org.opendaylight.controller.md.sal.binding.api.DataTreeModification;
-import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction;
-import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
-import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
-import org.opendaylight.controller.sal.core.api.model.SchemaService;
-import org.opendaylight.yangtools.binding.data.codec.api.BindingNormalizedNodeSerializer;
+import org.opendaylight.controller.blueprint.ext.DataStoreAppConfigDefaultXMLReader.ConfigURLProvider;
+import org.opendaylight.mdsal.binding.api.ClusteredDataTreeChangeListener;
+import org.opendaylight.mdsal.binding.api.DataBroker;
+import org.opendaylight.mdsal.binding.api.DataObjectModification;
+import org.opendaylight.mdsal.binding.api.DataObjectModification.ModificationType;
+import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
+import org.opendaylight.mdsal.binding.api.DataTreeModification;
+import org.opendaylight.mdsal.binding.api.ReadTransaction;
+import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeSerializer;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.mdsal.dom.api.DOMSchemaService;
 import org.opendaylight.yangtools.concepts.ListenerRegistration;
 import org.opendaylight.yangtools.yang.binding.DataObject;
-import org.opendaylight.yangtools.yang.binding.Identifiable;
-import org.opendaylight.yangtools.yang.binding.Identifier;
-import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
-import org.opendaylight.yangtools.yang.binding.util.BindingReflections;
-import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
-import org.opendaylight.yangtools.yang.data.impl.codec.xml.XmlUtils;
-import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
-import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.parser.DomToNormalizedNodeParserFactory;
-import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.Module;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.osgi.service.blueprint.container.ComponentDefinitionException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.w3c.dom.Element;
+import org.xml.sax.SAXException;
 
 /**
  * Factory metadata corresponding to the "clustered-app-config" element that obtains an application's
@@ -67,11 +57,17 @@ public class DataStoreAppConfigMetadata extends AbstractDependentComponentFactor
 
     static final String BINDING_CLASS = "binding-class";
     static final String DEFAULT_CONFIG = "default-config";
+    static final String DEFAULT_CONFIG_FILE_NAME = "default-config-file-name";
     static final String LIST_KEY_VALUE = "list-key-value";
 
+    private static final String DEFAULT_APP_CONFIG_FILE_PATH = "etc" + File.separator + "opendaylight" + File.separator
+            + "datastore" + File.separator + "initial" + File.separator + "config";
+
     private final Element defaultAppConfigElement;
+    private final String defaultAppConfigFileName;
     private final String appConfigBindingClassName;
     private final String appConfigListKeyValue;
+    private final UpdateStrategy appConfigUpdateStrategy;
     private final AtomicBoolean readingInitialAppConfig = new AtomicBoolean(true);
 
     private volatile BindingContext bindingContext;
@@ -83,53 +79,38 @@ public class DataStoreAppConfigMetadata extends AbstractDependentComponentFactor
     // project are still used - conversion to the mdsal binding classes hasn't occurred yet.
     private volatile BindingNormalizedNodeSerializer bindingSerializer;
 
-    public DataStoreAppConfigMetadata(@Nonnull String id, @Nonnull String appConfigBindingClassName,
-            @Nullable String appConfigListKeyValue, @Nullable Element defaultAppConfigElement) {
+    public DataStoreAppConfigMetadata(@Nonnull final String id, @Nonnull final String appConfigBindingClassName,
+            @Nullable final String appConfigListKeyValue, @Nullable final String defaultAppConfigFileName,
+            @Nonnull final UpdateStrategy updateStrategyValue, @Nullable final Element defaultAppConfigElement) {
         super(id);
         this.defaultAppConfigElement = defaultAppConfigElement;
+        this.defaultAppConfigFileName = defaultAppConfigFileName;
         this.appConfigBindingClassName = appConfigBindingClassName;
         this.appConfigListKeyValue = appConfigListKeyValue;
+        this.appConfigUpdateStrategy = updateStrategyValue;
     }
 
     @Override
     @SuppressWarnings("unchecked")
-    public void init(ExtendedBlueprintContainer container) {
+    public void init(final ExtendedBlueprintContainer container) {
         super.init(container);
 
         Class<DataObject> appConfigBindingClass;
         try {
             Class<?> bindingClass = container.getBundleContext().getBundle().loadClass(appConfigBindingClassName);
-            if(!DataObject.class.isAssignableFrom(bindingClass)) {
+            if (!DataObject.class.isAssignableFrom(bindingClass)) {
                 throw new ComponentDefinitionException(String.format(
                         "%s: Specified app config binding class %s does not extend %s",
                         logName(), appConfigBindingClassName, DataObject.class.getName()));
             }
 
             appConfigBindingClass = (Class<DataObject>) bindingClass;
-        } catch(ClassNotFoundException e) {
+        } catch (final ClassNotFoundException e) {
             throw new ComponentDefinitionException(String.format("%s: Error loading app config binding class %s",
                     logName(), appConfigBindingClassName), e);
         }
 
-        if(Identifiable.class.isAssignableFrom(appConfigBindingClass)) {
-            // The binding class corresponds to a yang list.
-            if(Strings.isNullOrEmpty(appConfigListKeyValue)) {
-                throw new ComponentDefinitionException(String.format(
-                        "%s: App config binding class %s represents a yang list therefore \"%s\" must be specified",
-                        logName(), appConfigBindingClassName, LIST_KEY_VALUE));
-            }
-
-            try {
-                bindingContext = ListBindingContext.newInstance(appConfigBindingClass, appConfigListKeyValue);
-            } catch(Exception e) {
-                throw new ComponentDefinitionException(String.format(
-                        "%s: Error initializing for app config list binding class %s",
-                        logName(), appConfigBindingClassName), e);
-            }
-
-        } else {
-            bindingContext = new ContainerBindingContext(appConfigBindingClass);
-        }
+        bindingContext = BindingContext.create(logName(), appConfigBindingClass, appConfigListKeyValue);
     }
 
     @Override
@@ -154,96 +135,91 @@ public class DataStoreAppConfigMetadata extends AbstractDependentComponentFactor
 
     private void retrieveDataBrokerService() {
         LOG.debug("{}: In retrieveDataBrokerService", logName());
-
         // Get the binding DataBroker OSGi service.
-
         retrieveService("data-broker", DataBroker.class, service -> retrieveInitialAppConfig((DataBroker)service));
     }
 
-    private void retrieveInitialAppConfig(DataBroker dataBroker) {
+    private void retrieveInitialAppConfig(final DataBroker dataBroker) {
         LOG.debug("{}: Got DataBroker instance - reading app config {}", logName(), bindingContext.appConfigPath);
 
-        setDependendencyDesc("Initial app config " + bindingContext.appConfigBindingClass.getSimpleName());
+        setDependencyDesc("Initial app config " + bindingContext.appConfigBindingClass.getSimpleName());
 
         // We register a DTCL to get updates and also read the app config data from the data store. If
         // the app config data is present then both the read and initial DTCN update will return it. If the
         // the data isn't present, we won't get an initial DTCN update so the read will indicate the data
         // isn't present.
 
-        DataTreeIdentifier<DataObject> dataTreeId = new DataTreeIdentifier<>(LogicalDatastoreType.CONFIGURATION,
+        DataTreeIdentifier<DataObject> dataTreeId = DataTreeIdentifier.create(LogicalDatastoreType.CONFIGURATION,
                 bindingContext.appConfigPath);
         appConfigChangeListenerReg = dataBroker.registerDataTreeChangeListener(dataTreeId,
-                new ClusteredDataTreeChangeListener<DataObject>() {
-                    @Override
-                    public void onDataTreeChanged(Collection<DataTreeModification<DataObject>> changes) {
-                        onAppConfigChanged(changes);
-                    }
-                });
+                (ClusteredDataTreeChangeListener<DataObject>) this::onAppConfigChanged);
 
         readInitialAppConfig(dataBroker);
     }
 
     private void readInitialAppConfig(final DataBroker dataBroker) {
+        final FluentFuture<Optional<DataObject>> future;
+        try (ReadTransaction readOnlyTx = dataBroker.newReadOnlyTransaction()) {
+            future = readOnlyTx.read(LogicalDatastoreType.CONFIGURATION, bindingContext.appConfigPath);
+        }
 
-        final ReadOnlyTransaction readOnlyTx = dataBroker.newReadOnlyTransaction();
-        CheckedFuture<Optional<DataObject>, ReadFailedException> future = readOnlyTx.read(
-                LogicalDatastoreType.CONFIGURATION, bindingContext.appConfigPath);
-        Futures.addCallback(future, new FutureCallback<Optional<DataObject>>() {
+        future.addCallback(new FutureCallback<Optional<DataObject>>() {
             @Override
-            public void onSuccess(Optional<DataObject> possibleAppConfig) {
-                LOG.debug("{}: Read of app config {} succeeded: {}", logName(), bindingContext.appConfigBindingClass.getName(),
-                        possibleAppConfig);
+            public void onSuccess(final Optional<DataObject> possibleAppConfig) {
+                LOG.debug("{}: Read of app config {} succeeded: {}", logName(), bindingContext
+                        .appConfigBindingClass.getName(), possibleAppConfig);
 
-                readOnlyTx.close();
                 setInitialAppConfig(possibleAppConfig);
             }
 
             @Override
-            public void onFailure(Throwable t) {
-                readOnlyTx.close();
-
+            public void onFailure(final Throwable failure) {
                 // We may have gotten the app config via the data tree change listener so only retry if not.
-                if(readingInitialAppConfig.get()) {
+                if (readingInitialAppConfig.get()) {
                     LOG.warn("{}: Read of app config {} failed - retrying", logName(),
-                            bindingContext.appConfigBindingClass.getName(), t);
+                            bindingContext.appConfigBindingClass.getName(), failure);
 
                     readInitialAppConfig(dataBroker);
                 }
             }
-        });
+        }, MoreExecutors.directExecutor());
     }
 
-    private void onAppConfigChanged(Collection<DataTreeModification<DataObject>> changes) {
-        for(DataTreeModification<DataObject> change: changes) {
+    private void onAppConfigChanged(final Collection<DataTreeModification<DataObject>> changes) {
+        for (DataTreeModification<DataObject> change: changes) {
             DataObjectModification<DataObject> changeRoot = change.getRootNode();
             ModificationType type = changeRoot.getModificationType();
 
             LOG.debug("{}: onAppConfigChanged: {}, {}", logName(), type, change.getRootPath());
 
-            if(type == ModificationType.SUBTREE_MODIFIED || type == ModificationType.WRITE) {
+            if (type == ModificationType.SUBTREE_MODIFIED || type == ModificationType.WRITE) {
                 DataObject newAppConfig = changeRoot.getDataAfter();
 
                 LOG.debug("New app config instance: {}, previous: {}", newAppConfig, currentAppConfig);
 
-                if(!setInitialAppConfig(Optional.of(newAppConfig)) &&
-                        !Objects.equals(currentAppConfig, newAppConfig)) {
-                    LOG.debug("App config was updated - scheduling container for restart");
+                if (!setInitialAppConfig(Optional.of(newAppConfig))
+                        && !Objects.equals(currentAppConfig, newAppConfig)) {
+                    LOG.debug("App config was updated");
 
-                    restartContainer();
+                    if (appConfigUpdateStrategy == UpdateStrategy.RELOAD) {
+                        restartContainer();
+                    }
                 }
-            } else if(type == ModificationType.DELETE) {
-                LOG.debug("App config was deleted - scheduling container for restart");
+            } else if (type == ModificationType.DELETE) {
+                LOG.debug("App config was deleted");
 
-                restartContainer();
+                if (appConfigUpdateStrategy == UpdateStrategy.RELOAD) {
+                    restartContainer();
+                }
             }
         }
     }
 
-    private boolean setInitialAppConfig(Optional<DataObject> possibleAppConfig) {
+    private boolean setInitialAppConfig(final Optional<DataObject> possibleAppConfig) {
         boolean result = readingInitialAppConfig.compareAndSet(true, false);
-        if(result) {
+        if (result) {
             DataObject localAppConfig;
-            if(possibleAppConfig.isPresent()) {
+            if (possibleAppConfig.isPresent()) {
                 localAppConfig = possibleAppConfig.get();
             } else {
                 // No app config data is present so create an empty instance via the bindingSerializer service.
@@ -263,169 +239,74 @@ public class DataStoreAppConfigMetadata extends AbstractDependentComponentFactor
     }
 
     private DataObject createDefaultInstance() {
-        YangInstanceIdentifier yangPath = bindingSerializer.toYangInstanceIdentifier(bindingContext.appConfigPath);
-
-        LOG.debug("{}: Creating app config instance from path {}, Qname: {}", logName(), yangPath, bindingContext.bindingQName);
-
-        SchemaService schemaService = getOSGiService(SchemaService.class);
-        if(schemaService == null) {
-            setFailureMessage(String.format("%s: Could not obtain the SchemaService OSGi service", logName()));
-            return null;
-        }
-
-        SchemaContext schemaContext = schemaService.getGlobalContext();
+        try {
+            ConfigURLProvider inputStreamProvider = appConfigFileName -> {
+                File appConfigFile = new File(DEFAULT_APP_CONFIG_FILE_PATH, appConfigFileName);
+                LOG.debug("{}: parsePossibleDefaultAppConfigXMLFile looking for file {}", logName(),
+                        appConfigFile.getAbsolutePath());
 
-        Module module = schemaContext.findModuleByNamespaceAndRevision(bindingContext.bindingQName.getNamespace(),
-                bindingContext.bindingQName.getRevision());
-        if(module == null) {
-            setFailureMessage(String.format("%s: Could not obtain the module schema for namespace %s, revision %s",
-                    logName(), bindingContext.bindingQName.getNamespace(), bindingContext.bindingQName.getRevision()));
-            return null;
-        }
+                if (!appConfigFile.exists()) {
+                    return Optional.empty();
+                }
 
-        DataSchemaNode dataSchema = module.getDataChildByName(bindingContext.bindingQName);
-        if(dataSchema == null) {
-            setFailureMessage(String.format("%s: Could not obtain the schema for %s", logName(), bindingContext.bindingQName));
-            return null;
-        }
+                LOG.debug("{}: Found file {}", logName(), appConfigFile.getAbsolutePath());
+
+                return Optional.of(appConfigFile.toURI().toURL());
+            };
+
+            DataStoreAppConfigDefaultXMLReader<?> reader = new DataStoreAppConfigDefaultXMLReader<>(logName(),
+                    defaultAppConfigFileName, getOSGiService(DOMSchemaService.class), bindingSerializer, bindingContext,
+                    inputStreamProvider);
+            return reader.createDefaultInstance((schemaContext, dataSchema) -> {
+                // Fallback if file cannot be read, try XML from Config
+                NormalizedNode<?, ?> dataNode = parsePossibleDefaultAppConfigElement(schemaContext, dataSchema);
+                if (dataNode == null) {
+                    // or, as last resort, defaults from the model
+                    return bindingContext.newDefaultNode(dataSchema);
+                } else {
+                    return dataNode;
+                }
+            });
 
-        if(!bindingContext.schemaType.isAssignableFrom(dataSchema.getClass())) {
-            setFailureMessage(String.format("%s: Expected schema type %s for %s but actual type is %s", logName(),
-                    bindingContext.schemaType, bindingContext.bindingQName, dataSchema.getClass()));
+        } catch (final ConfigXMLReaderException | IOException | SAXException | XMLStreamException
+                | ParserConfigurationException | URISyntaxException e) {
+            if (e.getCause() == null) {
+                setFailureMessage(e.getMessage());
+            } else {
+                setFailure(e.getMessage(), e);
+            }
             return null;
         }
-
-        NormalizedNode<?, ?> dataNode = parsePossibleDefaultAppConfigElement(schemaContext, dataSchema);
-        if(dataNode == null) {
-            dataNode = bindingContext.newDefaultNode(dataSchema);
-        }
-
-        DataObject appConfig = bindingSerializer.fromNormalizedNode(yangPath, dataNode).getValue();
-
-        if(appConfig == null) {
-            // This shouldn't happen but need to handle it in case...
-            setFailureMessage(String.format("%s: Could not create instance for app config binding %s",
-                    logName(), bindingContext.appConfigBindingClass));
-        }
-
-        return appConfig;
     }
 
     @Nullable
-    private NormalizedNode<?, ?> parsePossibleDefaultAppConfigElement(SchemaContext schemaContext,
-            DataSchemaNode dataSchema) {
-        if(defaultAppConfigElement == null) {
+    private NormalizedNode<?, ?> parsePossibleDefaultAppConfigElement(final SchemaContext schemaContext,
+            final DataSchemaNode dataSchema) throws URISyntaxException, IOException, ParserConfigurationException,
+            SAXException, XMLStreamException {
+        if (defaultAppConfigElement == null) {
             return null;
         }
 
         LOG.debug("{}: parsePossibleDefaultAppConfigElement for {}", logName(), bindingContext.bindingQName);
 
-        DomToNormalizedNodeParserFactory parserFactory = DomToNormalizedNodeParserFactory.getInstance(
-                XmlUtils.DEFAULT_XML_CODEC_PROVIDER, schemaContext);
-
-
         LOG.debug("{}: Got app config schema: {}", logName(), dataSchema);
 
         NormalizedNode<?, ?> dataNode = bindingContext.parseDataElement(defaultAppConfigElement, dataSchema,
-                parserFactory);
+                schemaContext);
 
         LOG.debug("{}: Parsed data node: {}", logName(), dataNode);
 
         return dataNode;
     }
 
-
     @Override
-    public void destroy(Object instance) {
+    public void destroy(final Object instance) {
         super.destroy(instance);
 
-        if(appConfigChangeListenerReg != null) {
+        if (appConfigChangeListenerReg != null) {
             appConfigChangeListenerReg.close();
             appConfigChangeListenerReg = null;
         }
     }
 
-    /**
-     * Internal base class to abstract binding type-specific behavior.
-     */
-    private static abstract class BindingContext {
-        final InstanceIdentifier<DataObject> appConfigPath;
-        final Class<DataObject> appConfigBindingClass;
-        final Class<? extends DataSchemaNode> schemaType;
-        final QName bindingQName;
-
-        protected BindingContext(Class<DataObject> appConfigBindingClass, InstanceIdentifier<DataObject> appConfigPath,
-                Class<? extends DataSchemaNode> schemaType) {
-            this.appConfigBindingClass = appConfigBindingClass;
-            this.appConfigPath = appConfigPath;
-            this.schemaType = schemaType;
-
-            bindingQName = BindingReflections.findQName(appConfigBindingClass);
-        }
-
-        abstract NormalizedNode<?, ?> parseDataElement(Element element, DataSchemaNode dataSchema,
-                DomToNormalizedNodeParserFactory parserFactory);
-
-        abstract NormalizedNode<?, ?> newDefaultNode(DataSchemaNode dataSchema);
-    }
-
-    /**
-     * BindingContext implementation for a container binding.
-     */
-    private static class ContainerBindingContext extends BindingContext {
-        ContainerBindingContext(Class<DataObject> appConfigBindingClass) {
-            super(appConfigBindingClass, InstanceIdentifier.create(appConfigBindingClass), ContainerSchemaNode.class);
-        }
-
-        @Override
-        NormalizedNode<?, ?> newDefaultNode(DataSchemaNode dataSchema) {
-            return ImmutableNodes.containerNode(bindingQName);
-        }
-
-        @Override
-        NormalizedNode<?, ?> parseDataElement(Element element, DataSchemaNode dataSchema,
-                DomToNormalizedNodeParserFactory parserFactory) {
-            return parserFactory.getContainerNodeParser().parse(Collections.singletonList(element),
-                    (ContainerSchemaNode)dataSchema);
-        }
-    }
-
-    /**
-     * BindingContext implementation for a list binding.
-     */
-    private static class ListBindingContext extends BindingContext {
-        final String appConfigListKeyValue;
-
-        ListBindingContext(Class<DataObject> appConfigBindingClass, InstanceIdentifier<DataObject> appConfigPath,
-                String appConfigListKeyValue) {
-            super(appConfigBindingClass, appConfigPath, ListSchemaNode.class);
-            this.appConfigListKeyValue = appConfigListKeyValue;
-        }
-
-        @SuppressWarnings({ "rawtypes", "unchecked" })
-        private static ListBindingContext newInstance(Class<DataObject> bindingClass, String listKeyValue)
-                throws Exception {
-            // We assume the yang list key type is string.
-            Identifier keyInstance = (Identifier) bindingClass.getMethod("getKey").getReturnType().
-                    getConstructor(String.class).newInstance(listKeyValue);
-            InstanceIdentifier appConfigPath = InstanceIdentifier.builder((Class)bindingClass, keyInstance).build();
-            return new ListBindingContext(bindingClass, appConfigPath, listKeyValue);
-        }
-
-        @Override
-        NormalizedNode<?, ?> newDefaultNode(DataSchemaNode dataSchema) {
-            // We assume there's only one key for the list.
-            List<QName> keys = ((ListSchemaNode)dataSchema).getKeyDefinition();
-            Preconditions.checkArgument(keys.size() == 1, "Expected only 1 key for list %s", appConfigBindingClass);
-            QName listKeyQName = keys.iterator().next();
-            return ImmutableNodes.mapEntryBuilder(bindingQName, listKeyQName, appConfigListKeyValue).build();
-        }
-
-        @Override
-        NormalizedNode<?, ?> parseDataElement(Element element, DataSchemaNode dataSchema,
-                DomToNormalizedNodeParserFactory parserFactory) {
-            return parserFactory.getMapEntryNodeParser().parse(Collections.singletonList(element),
-                    (ListSchemaNode)dataSchema);
-        }
-    }
 }