Merge branch 'blueprint' from controller
authorAnil Belur <abelur@linuxfoundation.org>
Wed, 29 Aug 2018 11:13:08 +0000 (16:43 +0530)
committerAnil Belur <abelur@linuxfoundation.org>
Wed, 29 Aug 2018 11:45:20 +0000 (17:15 +0530)
Moved code from controller.git:opendaylight/blueprint
to mdsal.git:opendaylight/blueprint

Issue-Id: RELENG-1170
Change-Id: I06d81bd60410e174222762d4eaee1811cac9bcb8
Signed-off-by: Anil Belur <abelur@linuxfoundation.org>
28 files changed:
opendaylight/blueprint/pom.xml [new file with mode: 0644]
opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/BlueprintBundleTracker.java [new file with mode: 0644]
opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/BlueprintContainerRestartService.java [new file with mode: 0644]
opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/BlueprintContainerRestartServiceImpl.java [new file with mode: 0644]
opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/AbstractDependentComponentFactoryMetadata.java [new file with mode: 0644]
opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/AbstractInvokableServiceMetadata.java [new file with mode: 0644]
opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/ActionProviderBean.java [new file with mode: 0644]
opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/ActionServiceMetadata.java [new file with mode: 0644]
opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/BindingContext.java [new file with mode: 0644]
opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/ComponentProcessor.java [new file with mode: 0644]
opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/ConfigXMLReaderException.java [new file with mode: 0644]
opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/DataStoreAppConfigDefaultXMLReader.java [new file with mode: 0644]
opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/DataStoreAppConfigMetadata.java [new file with mode: 0644]
opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/MandatoryServiceReferenceMetadata.java [new file with mode: 0644]
opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/NotificationListenerBean.java [new file with mode: 0644]
opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/OpendaylightNamespaceHandler.java [new file with mode: 0644]
opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/RoutedRpcMetadata.java [new file with mode: 0644]
opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/RoutedRpcRegistrationConverter.java [new file with mode: 0644]
opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/RpcImplementationBean.java [new file with mode: 0644]
opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/RpcServiceMetadata.java [new file with mode: 0644]
opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/RpcUtil.java [new file with mode: 0644]
opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/SpecificReferenceListMetadata.java [new file with mode: 0644]
opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/StaticReferenceMetadata.java [new file with mode: 0644]
opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/StaticServiceReferenceRecipe.java [new file with mode: 0644]
opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/UpdateStrategy.java [new file with mode: 0644]
opendaylight/blueprint/src/main/resources/opendaylight-blueprint-ext-1.0.0.xsd [new file with mode: 0644]
opendaylight/blueprint/src/test/java/org/opendaylight/controller/blueprint/tests/DataStoreAppConfigDefaultXMLReaderTest.java [new file with mode: 0644]
opendaylight/blueprint/src/test/resources/opendaylight-sal-test-store-config.xml [new file with mode: 0644]

diff --git a/opendaylight/blueprint/pom.xml b/opendaylight/blueprint/pom.xml
new file mode 100644 (file)
index 0000000..b3b14c0
--- /dev/null
@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.opendaylight.odlparent</groupId>
+    <artifactId>bundle-parent</artifactId>
+    <version>3.1.3</version>
+    <relativePath/>
+  </parent>
+
+  <groupId>org.opendaylight.controller</groupId>
+  <artifactId>blueprint</artifactId>
+  <packaging>bundle</packaging>
+  <name>${project.artifactId}</name>
+  <version>0.10.0-SNAPSHOT</version>
+
+  <dependencyManagement>
+    <dependencies>
+      <dependency>
+        <groupId>org.opendaylight.mdsal</groupId>
+        <artifactId>mdsal-artifacts</artifactId>
+        <version>2.6.0-SNAPSHOT</version>
+        <type>pom</type>
+        <scope>import</scope>
+      </dependency>
+      <dependency>
+        <groupId>org.opendaylight.controller</groupId>
+        <artifactId>mdsal-artifacts</artifactId>
+        <version>1.9.0-SNAPSHOT</version>
+        <type>pom</type>
+        <scope>import</scope>
+      </dependency>
+      <dependency>
+        <groupId>org.opendaylight.yangtools</groupId>
+        <artifactId>yangtools-artifacts</artifactId>
+        <version>2.0.10</version>
+        <type>pom</type>
+        <scope>import</scope>
+      </dependency>
+    </dependencies>
+  </dependencyManagement>
+
+  <dependencies>
+    <dependency>
+      <groupId>com.google.guava</groupId>
+      <artifactId>guava</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.aries.blueprint</groupId>
+      <artifactId>org.apache.aries.blueprint.core</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.aries</groupId>
+      <artifactId>org.apache.aries.util</artifactId>
+      <version>1.1.3</version>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>sal-binding-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>sal-core-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>sal-core-spi</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.mdsal</groupId>
+      <artifactId>mdsal-binding-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.mdsal</groupId>
+      <artifactId>mdsal-binding-dom-codec</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.yangtools</groupId>
+      <artifactId>yang-data-codec-xml</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.osgi</groupId>
+      <artifactId>org.osgi.core</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.osgi</groupId>
+      <artifactId>org.osgi.compendium</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.osgi</groupId>
+      <artifactId>org.osgi.service.event</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.truth</groupId>
+      <artifactId>truth</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>sal-test-model</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>sal-binding-broker-impl</artifactId>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>sal-binding-broker-impl</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <configuration>
+          <instructions>
+            <Bundle-Activator>org.opendaylight.controller.blueprint.BlueprintBundleTracker</Bundle-Activator>
+          </instructions>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-checkstyle-plugin</artifactId>
+        <configuration>
+          <propertyExpansion>checkstyle.violationSeverity=error</propertyExpansion>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>findbugs-maven-plugin</artifactId>
+        <configuration>
+          <failOnError>true</failOnError>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+</project>
diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/BlueprintBundleTracker.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/BlueprintBundleTracker.java
new file mode 100644 (file)
index 0000000..d3db327
--- /dev/null
@@ -0,0 +1,367 @@
+/*
+ * Copyright (c) 2016 Brocade Communications 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.blueprint;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.List;
+import javax.annotation.Nullable;
+import org.apache.aries.blueprint.NamespaceHandler;
+import org.apache.aries.blueprint.services.BlueprintExtenderService;
+import org.apache.aries.quiesce.participant.QuiesceParticipant;
+import org.apache.aries.util.AriesFrameworkUtil;
+import org.opendaylight.controller.blueprint.ext.OpendaylightNamespaceHandler;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.framework.SynchronousBundleListener;
+import org.osgi.service.blueprint.container.BlueprintContainer;
+import org.osgi.service.blueprint.container.BlueprintEvent;
+import org.osgi.service.blueprint.container.BlueprintListener;
+import org.osgi.util.tracker.BundleTracker;
+import org.osgi.util.tracker.BundleTrackerCustomizer;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class is created in bundle activation and scans ACTIVE bundles for blueprint XML files located under
+ * the well-known org/opendaylight/blueprint/ path and deploys the XML files via the Aries
+ * BlueprintExtenderService. This path differs from the standard OSGI-INF/blueprint path to allow for
+ * controlled deployment of blueprint containers in an orderly manner.
+ *
+ * @author Thomas Pantelis
+ */
+public class BlueprintBundleTracker implements BundleActivator, BundleTrackerCustomizer<Bundle>, BlueprintListener,
+        SynchronousBundleListener {
+    private static final Logger LOG = LoggerFactory.getLogger(BlueprintBundleTracker.class);
+    private static final String BLUEPRINT_FILE_PATH = "org/opendaylight/blueprint/";
+    private static final String BLUEPRINT_FLE_PATTERN = "*.xml";
+    private static final long SYSTEM_BUNDLE_ID = 0;
+
+    private ServiceTracker<BlueprintExtenderService, BlueprintExtenderService> blueprintExtenderServiceTracker;
+    private ServiceTracker<QuiesceParticipant, QuiesceParticipant> quiesceParticipantTracker;
+    private BundleTracker<Bundle> bundleTracker;
+    private BundleContext bundleContext;
+    private volatile BlueprintExtenderService blueprintExtenderService;
+    private volatile QuiesceParticipant quiesceParticipant;
+    private volatile ServiceRegistration<?> blueprintContainerRestartReg;
+    private volatile BlueprintContainerRestartServiceImpl restartService;
+    private volatile boolean shuttingDown;
+    private ServiceRegistration<?> eventHandlerReg;
+    private ServiceRegistration<?> namespaceReg;
+
+    /**
+     * Implemented from BundleActivator.
+     */
+    @Override
+    public void start(final BundleContext context) {
+        LOG.info("Starting {}", getClass().getSimpleName());
+
+        restartService = new BlueprintContainerRestartServiceImpl();
+
+        bundleContext = context;
+
+        registerBlueprintEventHandler(context);
+
+        registerNamespaceHandler(context);
+
+        bundleTracker = new BundleTracker<>(context, Bundle.ACTIVE, this);
+
+        blueprintExtenderServiceTracker = new ServiceTracker<>(context, BlueprintExtenderService.class.getName(),
+                new ServiceTrackerCustomizer<BlueprintExtenderService, BlueprintExtenderService>() {
+                    @Override
+                    public BlueprintExtenderService addingService(
+                            final ServiceReference<BlueprintExtenderService> reference) {
+                        return onBlueprintExtenderServiceAdded(reference);
+                    }
+
+                    @Override
+                    public void modifiedService(final ServiceReference<BlueprintExtenderService> reference,
+                            final BlueprintExtenderService service) {
+                    }
+
+                    @Override
+                    public void removedService(final ServiceReference<BlueprintExtenderService> reference,
+                            final BlueprintExtenderService service) {
+                    }
+                });
+        blueprintExtenderServiceTracker.open();
+
+        quiesceParticipantTracker = new ServiceTracker<>(context, QuiesceParticipant.class.getName(),
+                new ServiceTrackerCustomizer<QuiesceParticipant, QuiesceParticipant>() {
+                    @Override
+                    public QuiesceParticipant addingService(
+                            final ServiceReference<QuiesceParticipant> reference) {
+                        return onQuiesceParticipantAdded(reference);
+                    }
+
+                    @Override
+                    public void modifiedService(final ServiceReference<QuiesceParticipant> reference,
+                                                final QuiesceParticipant service) {
+                    }
+
+                    @Override
+                    public void removedService(final ServiceReference<QuiesceParticipant> reference,
+                                               final QuiesceParticipant service) {
+                    }
+                });
+        quiesceParticipantTracker.open();
+    }
+
+    private QuiesceParticipant onQuiesceParticipantAdded(final ServiceReference<QuiesceParticipant> reference) {
+        quiesceParticipant = reference.getBundle().getBundleContext().getService(reference);
+
+        LOG.debug("Got QuiesceParticipant");
+
+        restartService.setQuiesceParticipant(quiesceParticipant);
+
+        return quiesceParticipant;
+    }
+
+    private BlueprintExtenderService onBlueprintExtenderServiceAdded(
+            final ServiceReference<BlueprintExtenderService> reference) {
+        blueprintExtenderService = reference.getBundle().getBundleContext().getService(reference);
+        bundleTracker.open();
+
+        bundleContext.addBundleListener(BlueprintBundleTracker.this);
+
+        LOG.debug("Got BlueprintExtenderService");
+
+        restartService.setBlueprintExtenderService(blueprintExtenderService);
+
+        blueprintContainerRestartReg = bundleContext.registerService(
+                BlueprintContainerRestartService.class.getName(), restartService, new Hashtable<>());
+
+        return blueprintExtenderService;
+    }
+
+    private void registerNamespaceHandler(final BundleContext context) {
+        Dictionary<String, Object> props = new Hashtable<>();
+        props.put("osgi.service.blueprint.namespace", OpendaylightNamespaceHandler.NAMESPACE_1_0_0);
+        namespaceReg = context.registerService(NamespaceHandler.class.getName(),
+                new OpendaylightNamespaceHandler(), props);
+    }
+
+    private void registerBlueprintEventHandler(final BundleContext context) {
+        eventHandlerReg = context.registerService(BlueprintListener.class.getName(), this, new Hashtable<>());
+    }
+
+    /**
+     * Implemented from BundleActivator.
+     */
+    @Override
+    public void stop(final BundleContext context) {
+        bundleTracker.close();
+        blueprintExtenderServiceTracker.close();
+        quiesceParticipantTracker.close();
+
+        AriesFrameworkUtil.safeUnregisterService(eventHandlerReg);
+        AriesFrameworkUtil.safeUnregisterService(namespaceReg);
+        AriesFrameworkUtil.safeUnregisterService(blueprintContainerRestartReg);
+    }
+
+    /**
+     * Implemented from SynchronousBundleListener.
+     */
+    @Override
+    public void bundleChanged(final BundleEvent event) {
+        // If the system bundle (id 0) is stopping, do an orderly shutdown of all blueprint containers. On
+        // shutdown the system bundle is stopped first.
+        if (event.getBundle().getBundleId() == SYSTEM_BUNDLE_ID && event.getType() == BundleEvent.STOPPING) {
+            shutdownAllContainers();
+        }
+    }
+
+    /**
+     * Implemented from BundleActivator.
+     */
+    @Override
+    public Bundle addingBundle(final Bundle bundle, final BundleEvent event) {
+        modifiedBundle(bundle, event, bundle);
+        return bundle;
+    }
+
+    /**
+     * Implemented from BundleTrackerCustomizer.
+     */
+    @Override
+    public void modifiedBundle(final Bundle bundle, final BundleEvent event, final Bundle object) {
+        if (shuttingDown) {
+            return;
+        }
+
+        if (bundle.getState() == Bundle.ACTIVE) {
+            List<Object> paths = findBlueprintPaths(bundle);
+
+            if (!paths.isEmpty()) {
+                LOG.info("Creating blueprint container for bundle {} with paths {}", bundle, paths);
+
+                blueprintExtenderService.createContainer(bundle, paths);
+            }
+        }
+    }
+
+    /**
+     * Implemented from BundleTrackerCustomizer.
+     */
+    @Override
+    public void removedBundle(final Bundle bundle, final BundleEvent event, final Bundle object) {
+        // BlueprintExtenderService will handle this.
+    }
+
+    /**
+     * Implemented from BlueprintListener to listen for blueprint events.
+     *
+     * @param event the event to handle
+     */
+    @Override
+    public void blueprintEvent(BlueprintEvent event) {
+        if (event.getType() == BlueprintEvent.CREATED) {
+            LOG.info("Blueprint container for bundle {} was successfully created", event.getBundle());
+            return;
+        }
+
+        // If the container timed out waiting for dependencies, we'll destroy it and start it again. This
+        // is indicated via a non-null DEPENDENCIES property containing the missing dependencies. The
+        // default timeout is 5 min and ideally we would set this to infinite but the timeout can only
+        // be set at the bundle level in the manifest - there's no way to set it globally.
+        if (event.getType() == BlueprintEvent.FAILURE && event.getDependencies() != null) {
+            Bundle bundle = event.getBundle();
+
+            List<Object> paths = findBlueprintPaths(bundle);
+            if (!paths.isEmpty()) {
+                LOG.warn("Blueprint container for bundle {} timed out waiting for dependencies - restarting it",
+                        bundle);
+
+                restartService.restartContainer(bundle, paths);
+            }
+        }
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    static List<Object> findBlueprintPaths(final Bundle bundle) {
+        Enumeration<?> rntries = bundle.findEntries(BLUEPRINT_FILE_PATH, BLUEPRINT_FLE_PATTERN, false);
+        if (rntries == null) {
+            return Collections.emptyList();
+        } else {
+            return Collections.list((Enumeration)rntries);
+        }
+    }
+
+    private void shutdownAllContainers() {
+        shuttingDown = true;
+
+        restartService.close();
+
+        LOG.info("Shutting down all blueprint containers...");
+
+        Collection<Bundle> containerBundles = new HashSet<>(Arrays.asList(bundleContext.getBundles()));
+        while (!containerBundles.isEmpty()) {
+            // For each iteration of getBundlesToDestroy, as containers are destroyed, other containers become
+            // eligible to be destroyed. We loop until we've destroyed them all.
+            for (Bundle bundle : getBundlesToDestroy(containerBundles)) {
+                containerBundles.remove(bundle);
+                BlueprintContainer container = blueprintExtenderService.getContainer(bundle);
+                if (container != null) {
+                    blueprintExtenderService.destroyContainer(bundle, container);
+                }
+            }
+        }
+
+        LOG.info("Shutdown of blueprint containers complete");
+    }
+
+    private List<Bundle> getBundlesToDestroy(final Collection<Bundle> containerBundles) {
+        List<Bundle> bundlesToDestroy = new ArrayList<>();
+
+        // Find all container bundles that either have no registered services or whose services are no
+        // longer in use.
+        for (Bundle bundle : containerBundles) {
+            ServiceReference<?>[] references = bundle.getRegisteredServices();
+            int usage = 0;
+            if (references != null) {
+                for (ServiceReference<?> reference : references) {
+                    usage += getServiceUsage(reference);
+                }
+            }
+
+            LOG.debug("Usage for bundle {} is {}", bundle, usage);
+            if (usage == 0) {
+                bundlesToDestroy.add(bundle);
+            }
+        }
+
+        if (!bundlesToDestroy.isEmpty()) {
+            bundlesToDestroy.sort((b1, b2) -> (int) (b2.getLastModified() - b1.getLastModified()));
+
+            LOG.debug("Selected bundles {} for destroy (no services in use)", bundlesToDestroy);
+        } else {
+            // There's either no more container bundles or they all have services being used. For
+            // the latter it means there's either circular service usage or a service is being used
+            // by a non-container bundle. But we need to make progress so we pick the bundle with a
+            // used service with the highest service ID. Each service is assigned a monotonically
+            // increasing ID as they are registered. By picking the bundle with the highest service
+            // ID, we're picking the bundle that was (likely) started after all the others and thus
+            // is likely the safest to destroy at this point.
+
+            Bundle bundle = findBundleWithHighestUsedServiceId(containerBundles);
+            if (bundle != null) {
+                bundlesToDestroy.add(bundle);
+            }
+
+            LOG.debug("Selected bundle {} for destroy (lowest ranking service or highest service ID)",
+                    bundlesToDestroy);
+        }
+
+        return bundlesToDestroy;
+    }
+
+    @Nullable
+    private Bundle findBundleWithHighestUsedServiceId(final Collection<Bundle> containerBundles) {
+        ServiceReference<?> highestServiceRef = null;
+        for (Bundle bundle : containerBundles) {
+            ServiceReference<?>[] references = bundle.getRegisteredServices();
+            if (references == null) {
+                continue;
+            }
+
+            for (ServiceReference<?> reference : references) {
+                // We did check the service usage previously but it's possible the usage has changed since then.
+                if (getServiceUsage(reference) == 0) {
+                    continue;
+                }
+
+                // Choose 'reference' if it has a lower service ranking or, if the rankings are equal
+                // which is usually the case, if it has a higher service ID. For the latter the < 0
+                // check looks backwards but that's how ServiceReference#compareTo is documented to work.
+                if (highestServiceRef == null || reference.compareTo(highestServiceRef) < 0) {
+                    LOG.debug("Currently selecting bundle {} for destroy (with reference {})", bundle, reference);
+                    highestServiceRef = reference;
+                }
+            }
+        }
+
+        return highestServiceRef == null ? null : highestServiceRef.getBundle();
+    }
+
+    private static int getServiceUsage(final ServiceReference<?> ref) {
+        Bundle[] usingBundles = ref.getUsingBundles();
+        return usingBundles != null ? usingBundles.length : 0;
+    }
+}
diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/BlueprintContainerRestartService.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/BlueprintContainerRestartService.java
new file mode 100644 (file)
index 0000000..522415b
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2016 Brocade Communications 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.blueprint;
+
+import org.osgi.framework.Bundle;
+
+/**
+ * Interface that restarts blueprint containers.
+ *
+ * @author Thomas Pantelis
+ */
+public interface BlueprintContainerRestartService {
+
+    /**
+     * Restarts the blueprint container for the given bundle and all its dependent containers in an atomic
+     * and orderly manner. The dependent containers are identified by walking the OSGi service dependency
+     * hierarchies for the service(s) provided by the given bundle.
+     *
+     * @param bundle the bundle to restart
+     */
+    void restartContainerAndDependents(Bundle bundle);
+}
\ No newline at end of file
diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/BlueprintContainerRestartServiceImpl.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/BlueprintContainerRestartServiceImpl.java
new file mode 100644 (file)
index 0000000..2b2af15
--- /dev/null
@@ -0,0 +1,237 @@
+/*
+ * Copyright (c) 2016 Brocade Communications 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.blueprint;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.Hashtable;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import org.apache.aries.blueprint.services.BlueprintExtenderService;
+import org.apache.aries.quiesce.participant.QuiesceParticipant;
+import org.apache.aries.util.AriesFrameworkUtil;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.blueprint.container.BlueprintEvent;
+import org.osgi.service.blueprint.container.BlueprintListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Implementation of the BlueprintContainerRestartService.
+ *
+ * @author Thomas Pantelis
+ */
+class BlueprintContainerRestartServiceImpl implements AutoCloseable, BlueprintContainerRestartService {
+    private static final Logger LOG = LoggerFactory.getLogger(BlueprintContainerRestartServiceImpl.class);
+    private static final int CONTAINER_CREATE_TIMEOUT_IN_MINUTES = 5;
+
+    private final ExecutorService restartExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder()
+            .setDaemon(true).setNameFormat("BlueprintContainerRestartService").build());
+
+    private BlueprintExtenderService blueprintExtenderService;
+    private QuiesceParticipant quiesceParticipant;
+
+    void setBlueprintExtenderService(final BlueprintExtenderService blueprintExtenderService) {
+        this.blueprintExtenderService = blueprintExtenderService;
+    }
+
+    void setQuiesceParticipant(final QuiesceParticipant quiesceParticipant) {
+        this.quiesceParticipant = quiesceParticipant;
+    }
+
+    public void restartContainer(final Bundle bundle, final List<Object> paths) {
+        LOG.debug("restartContainer for bundle {}", bundle);
+
+        if (restartExecutor.isShutdown()) {
+            LOG.debug("Already closed - returning");
+            return;
+        }
+
+        restartExecutor.execute(() -> {
+            blueprintExtenderService.destroyContainer(bundle, blueprintExtenderService.getContainer(bundle));
+            blueprintExtenderService.createContainer(bundle, paths);
+        });
+    }
+
+    @Override
+    public void restartContainerAndDependents(final Bundle bundle) {
+        if (restartExecutor.isShutdown()) {
+            return;
+        }
+
+        LOG.debug("restartContainerAndDependents for bundle {}", bundle);
+
+        restartExecutor.execute(() -> restartContainerAndDependentsInternal(bundle));
+    }
+
+    private void restartContainerAndDependentsInternal(final Bundle forBundle) {
+        Preconditions.checkNotNull(blueprintExtenderService);
+        Preconditions.checkNotNull(quiesceParticipant);
+
+        // We use a LinkedHashSet to preserve insertion order as we walk the service usage hierarchy.
+        Set<Bundle> containerBundlesSet = new LinkedHashSet<>();
+        findDependentContainersRecursively(forBundle, containerBundlesSet);
+
+        List<Bundle> containerBundles = new ArrayList<>(containerBundlesSet);
+
+        LOG.info("Restarting blueprint containers for bundle {} and its dependent bundles {}", forBundle,
+                containerBundles.subList(1, containerBundles.size()));
+
+        // The blueprint containers are created asynchronously so we register a handler for blueprint events
+        // that are sent when a container is complete, successful or not. The CountDownLatch tells when all
+        // containers are complete. This is done to ensure all blueprint containers are finished before we
+        // restart config modules.
+        final CountDownLatch containerCreationComplete = new CountDownLatch(containerBundles.size());
+        ServiceRegistration<?> eventHandlerReg = registerEventHandler(forBundle.getBundleContext(), event -> {
+            final Bundle bundle = event.getBundle();
+            if (event.isReplay()) {
+                LOG.trace("Got replay BlueprintEvent {} for bundle {}", event.getType(), bundle);
+                return;
+            }
+
+            LOG.debug("Got BlueprintEvent {} for bundle {}", event.getType(), bundle);
+            if (containerBundles.contains(bundle)
+                    && (event.getType() == BlueprintEvent.CREATED || event.getType() == BlueprintEvent.FAILURE)) {
+                containerCreationComplete.countDown();
+                LOG.debug("containerCreationComplete is now {}", containerCreationComplete.getCount());
+            }
+        });
+
+        final Runnable createContainerCallback = () -> createContainers(containerBundles);
+
+        // Destroy the container down-top recursively and once done, restart the container top-down
+        destroyContainers(new ArrayDeque<>(Lists.reverse(containerBundles)), createContainerCallback);
+
+
+        try {
+            if (!containerCreationComplete.await(CONTAINER_CREATE_TIMEOUT_IN_MINUTES, TimeUnit.MINUTES)) {
+                LOG.warn("Failed to restart all blueprint containers within {} minutes. Attempted to restart {} {} "
+                        + "but only {} completed restart", CONTAINER_CREATE_TIMEOUT_IN_MINUTES, containerBundles.size(),
+                        containerBundles, containerBundles.size() - containerCreationComplete.getCount());
+                return;
+            }
+        } catch (final InterruptedException e) {
+            LOG.debug("CountDownLatch await was interrupted - returning");
+            return;
+        }
+
+        AriesFrameworkUtil.safeUnregisterService(eventHandlerReg);
+
+        LOG.info("Finished restarting blueprint containers for bundle {} and its dependent bundles", forBundle);
+    }
+
+    /**
+     * Recursively quiesce and destroy the bundles one by one in order to maintain synchronicity and ordering.
+     * @param remainingBundlesToDestroy the list of remaining bundles to destroy.
+     * @param createContainerCallback a {@link Runnable} to {@code run()} when the recursive function is completed.
+     */
+    private void destroyContainers(final Deque<Bundle> remainingBundlesToDestroy,
+            final Runnable createContainerCallback) {
+
+        final Bundle nextBundle;
+        synchronized (remainingBundlesToDestroy) {
+            if (remainingBundlesToDestroy.isEmpty()) {
+                LOG.debug("All blueprint containers were quiesced and destroyed");
+                createContainerCallback.run();
+                return;
+            }
+
+            nextBundle = remainingBundlesToDestroy.poll();
+        }
+
+        // The Quiesce capability is a like a soft-stop, clean-stop. In the case of the Blueprint extender, in flight
+        // service calls are allowed to finish; they're counted in and counted out, and no new calls are allowed. When
+        // there are no in flight service calls, the bundle is told to stop. The Blueprint bundle itself doesn't know
+        // this is happening which is a key design point. In the case of Blueprint, the extender ensures no new Entity
+        // Managers(EMs) are created. Then when all those EMs are closed the quiesce operation reports that it is
+        // finished.
+        // To properly restart the blueprint containers, first we have to quiesce the list of bundles, and once done, it
+        // is safe to destroy their BlueprintContainer, so no reference is retained.
+        //
+        // Mail - thread explaining Quiesce API:
+        //      https://www.mail-archive.com/dev@aries.apache.org/msg08403.html
+
+        // Quiesced the bundle to unregister the associated BlueprintContainer
+        quiesceParticipant.quiesce(bundlesQuiesced -> {
+
+            // Destroy the container once Quiesced
+            Arrays.stream(bundlesQuiesced).forEach(quiescedBundle -> {
+                LOG.debug("Quiesced bundle {}", quiescedBundle);
+                blueprintExtenderService.destroyContainer(
+                        quiescedBundle, blueprintExtenderService.getContainer(quiescedBundle));
+            });
+
+            destroyContainers(remainingBundlesToDestroy, createContainerCallback);
+
+        }, Collections.singletonList(nextBundle));
+    }
+
+    private void createContainers(final List<Bundle> containerBundles) {
+        containerBundles.forEach(bundle -> {
+            List<Object> paths = BlueprintBundleTracker.findBlueprintPaths(bundle);
+
+            LOG.info("Restarting blueprint container for bundle {} with paths {}", bundle, paths);
+
+            blueprintExtenderService.createContainer(bundle, paths);
+        });
+    }
+
+    /**
+     * Recursively finds the services registered by the given bundle and the bundles using those services.
+     * User bundles that have an associated blueprint container are added to containerBundles.
+     *
+     * @param bundle the bundle to traverse
+     * @param containerBundles the current set of bundles containing blueprint containers
+     */
+    private void findDependentContainersRecursively(final Bundle bundle, final Set<Bundle> containerBundles) {
+        if (!containerBundles.add(bundle)) {
+            // Already seen this bundle...
+            return;
+        }
+
+        ServiceReference<?>[] references = bundle.getRegisteredServices();
+        if (references != null) {
+            for (ServiceReference<?> reference : references) {
+                Bundle[] usingBundles = reference.getUsingBundles();
+                if (usingBundles != null) {
+                    for (Bundle usingBundle : usingBundles) {
+                        if (blueprintExtenderService.getContainer(usingBundle) != null) {
+                            findDependentContainersRecursively(usingBundle, containerBundles);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private ServiceRegistration<?> registerEventHandler(final BundleContext bundleContext,
+            final BlueprintListener listener) {
+        return bundleContext.registerService(BlueprintListener.class.getName(), listener, new Hashtable<>());
+    }
+
+    @Override
+    public void close() {
+        LOG.debug("Closing");
+
+        restartExecutor.shutdownNow();
+    }
+}
diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/AbstractDependentComponentFactoryMetadata.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/AbstractDependentComponentFactoryMetadata.java
new file mode 100644 (file)
index 0000000..37343b9
--- /dev/null
@@ -0,0 +1,253 @@
+/*
+ * Copyright (c) 2016 Brocade Communications 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.blueprint.ext;
+
+import com.google.common.base.Preconditions;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
+import org.apache.aries.blueprint.di.AbstractRecipe;
+import org.apache.aries.blueprint.di.ExecutionContext;
+import org.apache.aries.blueprint.di.Recipe;
+import org.apache.aries.blueprint.ext.DependentComponentFactoryMetadata;
+import org.apache.aries.blueprint.services.ExtendedBlueprintContainer;
+import org.opendaylight.controller.blueprint.BlueprintContainerRestartService;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.blueprint.container.ComponentDefinitionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Abstract base class for a DependentComponentFactoryMetadata implementation.
+ *
+ * @author Thomas Pantelis
+ */
+abstract class AbstractDependentComponentFactoryMetadata implements DependentComponentFactoryMetadata {
+    @SuppressFBWarnings("SLF4J_LOGGER_SHOULD_BE_PRIVATE")
+    final Logger log = LoggerFactory.getLogger(getClass());
+    private final String id;
+    private final AtomicBoolean started = new AtomicBoolean();
+    private final AtomicBoolean satisfied = new AtomicBoolean();
+    private final AtomicBoolean restarting = new AtomicBoolean();
+    @GuardedBy("serviceRecipes")
+    private final List<StaticServiceReferenceRecipe> serviceRecipes = new ArrayList<>();
+    private volatile ExtendedBlueprintContainer container;
+    private volatile SatisfactionCallback satisfactionCallback;
+    private volatile String failureMessage;
+    private volatile Throwable failureCause;
+    private volatile String dependencyDesc;
+    @GuardedBy("serviceRecipes")
+    private boolean stoppedServiceRecipes;
+
+    protected AbstractDependentComponentFactoryMetadata(final String id) {
+        this.id = Preconditions.checkNotNull(id);
+    }
+
+    @Override
+    public String getId() {
+        return id;
+    }
+
+    @Override
+    public int getActivation() {
+        return ACTIVATION_EAGER;
+    }
+
+    @Override
+    public List<String> getDependsOn() {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public String getDependencyDescriptor() {
+        return dependencyDesc;
+    }
+
+    @Override
+    public boolean isSatisfied() {
+        return satisfied.get();
+    }
+
+    protected void setFailureMessage(final String failureMessage) {
+        setFailure(failureMessage, null);
+    }
+
+    @SuppressWarnings("checkstyle:hiddenField")
+    protected void setFailure(final String failureMessage, final Throwable failureCause) {
+        this.failureMessage = failureMessage;
+        this.failureCause = failureCause;
+    }
+
+    protected void setDependencyDesc(final String dependencyDesc) {
+        this.dependencyDesc = dependencyDesc;
+    }
+
+    protected final ExtendedBlueprintContainer container() {
+        return container;
+    }
+
+    protected void setSatisfied() {
+        if (satisfied.compareAndSet(false, true)) {
+            satisfactionCallback.notifyChanged();
+        }
+    }
+
+    protected void retrieveService(final String name, final Class<?> interfaceClass,
+            final Consumer<Object> onServiceRetrieved) {
+        retrieveService(name, interfaceClass.getName(), onServiceRetrieved);
+    }
+
+    protected void retrieveService(final String name, final String interfaceName,
+            final Consumer<Object> onServiceRetrieved) {
+        synchronized (serviceRecipes) {
+            if (stoppedServiceRecipes) {
+                return;
+            }
+
+            StaticServiceReferenceRecipe recipe = new StaticServiceReferenceRecipe(getId() + "-" + name,
+                    container, interfaceName);
+            setDependencyDesc(recipe.getOsgiFilter());
+            serviceRecipes.add(recipe);
+
+            recipe.startTracking(onServiceRetrieved);
+        }
+    }
+
+    protected final String logName() {
+        return (container != null ? container.getBundleContext().getBundle().getSymbolicName() : "") + " (" + id + ")";
+    }
+
+    @Override
+    public void init(final ExtendedBlueprintContainer newContainer) {
+        this.container = newContainer;
+
+        log.debug("{}: In init", logName());
+    }
+
+    protected void onCreate() throws ComponentDefinitionException {
+        if (failureMessage != null) {
+            throw new ComponentDefinitionException(failureMessage, failureCause);
+        }
+
+        // The following code is a bit odd so requires some explanation. A little background... If a bean
+        // is a prototype then the corresponding Recipe create method does not register the bean as created
+        // with the BlueprintRepository and thus the destroy method isn't called on container destroy. We
+        // rely on destroy being called to close our DTCL registration. Unfortunately the default setting
+        // for the prototype flag in AbstractRecipe is true and the DependentComponentFactoryRecipe, which
+        // is created for DependentComponentFactoryMetadata types of which we are one, doesn't have a way for
+        // us to indicate the prototype state via our metadata.
+        //
+        // The ExecutionContext is actually backed by the BlueprintRepository so we access it here to call
+        // the removePartialObject method which removes any partially created instance, which does not apply
+        // in our case, and also has the side effect of registering our bean as created as if it wasn't a
+        // prototype. We also obtain our corresponding Recipe instance and clear the prototype flag. This
+        // doesn't look to be necessary but is done so for completeness. Better late than never. Note we have
+        // to do this here rather than in startTracking b/c the ExecutionContext is not available yet at that
+        // point.
+        //
+        // Now the stopTracking method is called on container destroy but startTracking/stopTracking can also
+        // be called multiple times during the container creation process for Satisfiable recipes as bean
+        // processors may modify the metadata which could affect how dependencies are satisfied. An example of
+        // this is with service references where the OSGi filter metadata can be modified by bean processors
+        // after the initial service dependency is satisfied. However we don't have any metadata that could
+        // be modified by a bean processor and we don't want to register/unregister our DTCL multiple times
+        // so we only process startTracking once and close the DTCL registration once on container destroy.
+        ExecutionContext executionContext = ExecutionContext.Holder.getContext();
+        executionContext.removePartialObject(id);
+
+        Recipe myRecipe = executionContext.getRecipe(id);
+        if (myRecipe instanceof AbstractRecipe) {
+            log.debug("{}: setPrototype to false", logName());
+            ((AbstractRecipe)myRecipe).setPrototype(false);
+        } else {
+            log.warn("{}: Recipe is null or not an AbstractRecipe", logName());
+        }
+    }
+
+    protected abstract void startTracking();
+
+    @Override
+    public final void startTracking(final SatisfactionCallback newSatisfactionCallback) {
+        if (!started.compareAndSet(false, true)) {
+            return;
+        }
+
+        log.debug("{}: In startTracking", logName());
+
+        this.satisfactionCallback = newSatisfactionCallback;
+
+        startTracking();
+    }
+
+    @Override
+    public void stopTracking() {
+        log.debug("{}: In stopTracking", logName());
+
+        stopServiceRecipes();
+    }
+
+    @Override
+    public void destroy(final Object instance) {
+        log.debug("{}: In destroy", logName());
+
+        stopServiceRecipes();
+    }
+
+    private void stopServiceRecipes() {
+        synchronized (serviceRecipes) {
+            stoppedServiceRecipes = true;
+            for (StaticServiceReferenceRecipe recipe: serviceRecipes) {
+                recipe.stop();
+            }
+
+            serviceRecipes.clear();
+        }
+    }
+
+    protected void restartContainer() {
+        if (restarting.compareAndSet(false, true)) {
+            BlueprintContainerRestartService restartService = getOSGiService(BlueprintContainerRestartService.class);
+            if (restartService != null) {
+                log.debug("{}: Restarting container", logName());
+                restartService.restartContainerAndDependents(container().getBundleContext().getBundle());
+            }
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    @Nullable
+    protected <T> T getOSGiService(final Class<T> serviceInterface) {
+        try {
+            ServiceReference<T> serviceReference =
+                    container().getBundleContext().getServiceReference(serviceInterface);
+            if (serviceReference == null) {
+                log.warn("{}: {} reference not found", logName(), serviceInterface.getSimpleName());
+                return null;
+            }
+
+            T service = (T)container().getService(serviceReference);
+            if (service == null) {
+                // This could happen on shutdown if the service was already unregistered so we log as debug.
+                log.debug("{}: {} was not found", logName(), serviceInterface.getSimpleName());
+            }
+
+            return service;
+        } catch (final IllegalStateException e) {
+            // This is thrown if the BundleContext is no longer valid which is possible on shutdown so we
+            // log as debug.
+            log.debug("{}: Error obtaining {}", logName(), serviceInterface.getSimpleName(), e);
+        }
+
+        return null;
+    }
+}
diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/AbstractInvokableServiceMetadata.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/AbstractInvokableServiceMetadata.java
new file mode 100644 (file)
index 0000000..10ec16f
--- /dev/null
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2016 Brocade Communications 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.blueprint.ext;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableSet;
+import java.util.Collection;
+import java.util.Set;
+import java.util.function.Predicate;
+import org.apache.aries.blueprint.services.ExtendedBlueprintContainer;
+import org.opendaylight.mdsal.binding.api.RpcConsumerRegistry;
+import org.opendaylight.mdsal.dom.api.DOMRpcAvailabilityListener;
+import org.opendaylight.mdsal.dom.api.DOMRpcIdentifier;
+import org.opendaylight.mdsal.dom.api.DOMRpcService;
+import org.opendaylight.mdsal.dom.api.DOMSchemaService;
+import org.opendaylight.mdsal.dom.spi.RpcRoutingStrategy;
+import org.opendaylight.yangtools.concepts.ListenerRegistration;
+import org.opendaylight.yangtools.yang.binding.RpcService;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+import org.osgi.service.blueprint.container.ComponentDefinitionException;
+
+abstract class AbstractInvokableServiceMetadata extends AbstractDependentComponentFactoryMetadata {
+    private final String interfaceName;
+
+    private ListenerRegistration<DOMRpcAvailabilityListener> rpcListenerReg;
+    private RpcConsumerRegistry rpcRegistry;
+    private Class<RpcService> rpcInterface;
+    private Set<SchemaPath> rpcSchemaPaths;
+
+    AbstractInvokableServiceMetadata(final String id, final String interfaceName) {
+        super(id);
+        this.interfaceName = Preconditions.checkNotNull(interfaceName);
+    }
+
+    Class<RpcService> rpcInterface() {
+        return rpcInterface;
+    }
+
+    @SuppressWarnings({ "checkstyle:IllegalCatch", "unchecked" })
+    @Override
+    public final void init(final ExtendedBlueprintContainer container) {
+        super.init(container);
+
+        final Class<?> interfaceClass;
+        try {
+            interfaceClass = container().getBundleContext().getBundle().loadClass(interfaceName);
+        } catch (final Exception e) {
+            throw new ComponentDefinitionException(String.format("%s: Error obtaining interface class %s",
+                    logName(), interfaceName), e);
+        }
+
+        if (!RpcService.class.isAssignableFrom(interfaceClass)) {
+            throw new ComponentDefinitionException(String.format(
+                "%s: The specified interface %s is not an RpcService", logName(), interfaceName));
+        }
+
+        rpcInterface = (Class<RpcService>)interfaceClass;
+    }
+
+    @Override
+    protected final void startTracking() {
+        // Request RpcProviderRegistry first ...
+        retrieveService("RpcConsumerRegistry", RpcConsumerRegistry.class, this::onRpcRegistry);
+    }
+
+    private void onRpcRegistry(final Object service) {
+        log.debug("{}: Retrieved RpcProviderRegistry {}", logName(), service);
+        rpcRegistry = (RpcConsumerRegistry)service;
+
+        // Now acquire SchemaService...
+        retrieveService("SchemaService", DOMSchemaService.class, this::onSchemaService);
+    }
+
+    private void onSchemaService(final Object service) {
+        log.debug("{}: Retrieved SchemaService {}", logName(), service);
+
+        // Now get the SchemaContext and trigger RPC resolution
+        retrievedSchemaContext(((DOMSchemaService)service).getGlobalContext());
+    }
+
+    private void retrievedSchemaContext(final SchemaContext schemaContext) {
+        log.debug("{}: retrievedSchemaContext", logName());
+
+        final Collection<SchemaPath> schemaPaths = RpcUtil.decomposeRpcService(rpcInterface, schemaContext,
+            rpcFilter());
+        if (schemaPaths.isEmpty()) {
+            log.debug("{}: interface {} has no acceptable entries, assuming it is satisfied", logName(), rpcInterface);
+            setSatisfied();
+            return;
+        }
+
+        rpcSchemaPaths = ImmutableSet.copyOf(schemaPaths);
+        log.debug("{}: Got SchemaPaths: {}", logName(), rpcSchemaPaths);
+
+        // First get the DOMRpcService OSGi service. This will be used to register a listener to be notified
+        // when the underlying DOM RPC service is available.
+        retrieveService("DOMRpcService", DOMRpcService.class, this::retrievedDOMRpcService);
+    }
+
+    private void retrievedDOMRpcService(final Object service) {
+        log.debug("{}: retrievedDOMRpcService {}", logName(), service);
+        final DOMRpcService domRpcService = (DOMRpcService)service;
+
+        setDependencyDesc("Available DOM RPC for binding RPC: " + rpcInterface);
+        rpcListenerReg = domRpcService.registerRpcListener(new DOMRpcAvailabilityListener() {
+            @Override
+            public void onRpcAvailable(final Collection<DOMRpcIdentifier> rpcs) {
+                onRpcsAvailable(rpcs);
+            }
+
+            @Override
+            public void onRpcUnavailable(final Collection<DOMRpcIdentifier> rpcs) {
+            }
+        });
+    }
+
+    abstract Predicate<RpcRoutingStrategy> rpcFilter();
+
+    @SuppressWarnings("checkstyle:IllegalCatch")
+    @Override
+    public final Object create() throws ComponentDefinitionException {
+        log.debug("{}: In create: interfaceName: {}", logName(), interfaceName);
+
+        super.onCreate();
+
+        try {
+            RpcService rpcService = rpcRegistry.getRpcService(rpcInterface);
+
+            log.debug("{}: create returning service {}", logName(), rpcService);
+
+            return rpcService;
+        } catch (final RuntimeException e) {
+            throw new ComponentDefinitionException("Error getting RPC service for " + interfaceName, e);
+        }
+    }
+
+    protected final void onRpcsAvailable(final Collection<DOMRpcIdentifier> rpcs) {
+        for (DOMRpcIdentifier identifier : rpcs) {
+            if (rpcSchemaPaths.contains(identifier.getType())) {
+                log.debug("{}: onRpcsAvailable - found SchemaPath {}", logName(), identifier.getType());
+                setSatisfied();
+                break;
+            }
+        }
+    }
+
+    @Override
+    public final void stopTracking() {
+        super.stopTracking();
+        closeRpcListenerReg();
+    }
+
+    private void closeRpcListenerReg() {
+        if (rpcListenerReg != null) {
+            rpcListenerReg.close();
+            rpcListenerReg = null;
+        }
+    }
+
+    @Override
+    public final void destroy(final Object instance) {
+        super.destroy(instance);
+        closeRpcListenerReg();
+    }
+
+    @Override
+    public final String toString() {
+        return MoreObjects.toStringHelper(this).add("id", getId()).add("interfaceName", interfaceName).toString();
+    }
+}
diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/ActionProviderBean.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/ActionProviderBean.java
new file mode 100644 (file)
index 0000000..af943f9
--- /dev/null
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2017 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.blueprint.ext;
+
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableSet;
+import java.util.Collection;
+import java.util.Set;
+import org.opendaylight.mdsal.binding.api.RpcProviderService;
+import org.opendaylight.mdsal.dom.api.DOMRpcIdentifier;
+import org.opendaylight.mdsal.dom.api.DOMRpcImplementationNotAvailableException;
+import org.opendaylight.mdsal.dom.api.DOMRpcProviderService;
+import org.opendaylight.mdsal.dom.api.DOMSchemaService;
+import org.opendaylight.mdsal.dom.spi.RpcRoutingStrategy;
+import org.opendaylight.yangtools.concepts.Registration;
+import org.opendaylight.yangtools.util.concurrent.FluentFutures;
+import org.opendaylight.yangtools.yang.binding.RpcService;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+import org.osgi.framework.Bundle;
+import org.osgi.service.blueprint.container.ComponentDefinitionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Blueprint bean corresponding to the "action-provider" element that registers the promise to instantiate action
+ * instances with RpcProviderRegistry.
+ *
+ * <p>
+ * This bean has two distinct facets:
+ * - if a reference bean is provided, it registers it with {@link RpcProviderService}
+ * - if a reference bean is not provided, it registers the corresponding no-op implementation with
+ *   {@link DOMRpcProviderService} for all action (Routed RPC) elements in the provided interface
+ *
+ * @author Robert Varga
+ */
+public class ActionProviderBean {
+    static final String ACTION_PROVIDER = "action-provider";
+
+    private static final Logger LOG = LoggerFactory.getLogger(ActionProviderBean.class);
+
+    private DOMRpcProviderService domRpcProvider;
+    private RpcProviderService bindingRpcProvider;
+    private DOMSchemaService schemaService;
+    private RpcService implementation;
+    private String interfaceName;
+    private Registration reg;
+    private Bundle bundle;
+
+    public void setBundle(final Bundle bundle) {
+        this.bundle = bundle;
+    }
+
+    public void setInterfaceName(final String interfaceName) {
+        this.interfaceName = interfaceName;
+    }
+
+    public void setImplementation(final RpcService implementation) {
+        this.implementation = implementation;
+    }
+
+    public void setDomRpcProvider(final DOMRpcProviderService rpcProviderService) {
+        this.domRpcProvider = rpcProviderService;
+    }
+
+    public void setBindingRpcProvider(final RpcProviderService rpcProvider) {
+        this.bindingRpcProvider = rpcProvider;
+    }
+
+    public void setSchemaService(final DOMSchemaService schemaService) {
+        this.schemaService = schemaService;
+    }
+
+    public void init() {
+        // First resolve the interface class
+        final Class<RpcService> interfaceClass = getRpcClass();
+
+        LOG.debug("{}: resolved interface {} to {}", ACTION_PROVIDER, interfaceName, interfaceClass);
+
+        if (implementation != null) {
+            registerImplementation(interfaceClass);
+        } else {
+            registerFallback(interfaceClass);
+        }
+    }
+
+    @SuppressWarnings("checkstyle:IllegalCatch")
+    public void destroy() {
+        if (reg != null) {
+            try {
+                reg.close();
+            } catch (final Exception e) {
+                LOG.warn("{}: error while unregistering", ACTION_PROVIDER, e);
+            } finally {
+                reg = null;
+            }
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private Class<RpcService> getRpcClass() {
+        final Class<?> iface;
+
+        try {
+            iface = bundle.loadClass(interfaceName);
+        } catch (final ClassNotFoundException e) {
+            throw new ComponentDefinitionException(String.format(
+                "The specified \"interface\" for %s \"%s\" does not refer to an available class", interfaceName,
+                ACTION_PROVIDER), e);
+        }
+        if (!RpcService.class.isAssignableFrom(iface)) {
+            throw new ComponentDefinitionException(String.format(
+                "The specified \"interface\" %s for \"%s\" is not an RpcService", interfaceName, ACTION_PROVIDER));
+        }
+
+        return (Class<RpcService>) iface;
+    }
+
+    private void registerFallback(final Class<RpcService> interfaceClass) {
+        final Collection<SchemaPath> paths = RpcUtil.decomposeRpcService(interfaceClass,
+            schemaService.getGlobalContext(), RpcRoutingStrategy::isContextBasedRouted);
+        if (paths.isEmpty()) {
+            LOG.warn("{}: interface {} has no actions defined", ACTION_PROVIDER, interfaceClass);
+            return;
+        }
+
+        final Set<DOMRpcIdentifier> rpcs = ImmutableSet.copyOf(Collections2.transform(paths, DOMRpcIdentifier::create));
+        reg = domRpcProvider.registerRpcImplementation(
+            (rpc, input) -> FluentFutures.immediateFailedFluentFuture(new DOMRpcImplementationNotAvailableException(
+                "Action %s has no instance matching %s", rpc, input)), rpcs);
+        LOG.debug("Registered provider for {}", interfaceName);
+    }
+
+    private void registerImplementation(final Class<RpcService> interfaceClass) {
+        if (!interfaceClass.isInstance(implementation)) {
+            throw new ComponentDefinitionException(String.format(
+                "The specified \"interface\" %s for \"%s\" is not implemented by RpcService \"ref\" %s",
+                interfaceName, ACTION_PROVIDER, implementation.getClass()));
+        }
+
+        reg = bindingRpcProvider.registerRpcImplementation(interfaceClass, implementation);
+        LOG.debug("Registered implementation {} for {}", implementation, interfaceName);
+    }
+}
diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/ActionServiceMetadata.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/ActionServiceMetadata.java
new file mode 100644 (file)
index 0000000..5bb3f14
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2017 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.blueprint.ext;
+
+import java.util.function.Predicate;
+import org.opendaylight.mdsal.dom.spi.RpcRoutingStrategy;
+
+/**
+ * Factory metadata corresponding to the "action-service" element. It waits for a DOM promise of registration
+ * to appear in the {@link DOMRpcService} and then acquires a dynamic proxy via RpcProviderRegistry.
+ *
+ * @author Robert Varga
+ */
+final class ActionServiceMetadata extends AbstractInvokableServiceMetadata {
+    /*
+     * Implementation note:
+     *
+     * This implementation assumes Binding V1 semantics for actions, which means actions are packaged along with RPCs
+     * into a single interface. This has interesting implications on working with RpcServiceMetadata, which only
+     * handles the RPC side of the contract.
+     *
+     * Further interesting interactions stem from the fact that in DOM world each action is a separate entity, so the
+     * interface contract can let some actions to be invoked, while failing for others. This is a shortcoming of the
+     * Binding Specification and will be addressed in Binding V2 -- where each action is its own interface.
+     */
+    ActionServiceMetadata(final String id, final String interfaceName) {
+        super(id, interfaceName);
+    }
+
+    @Override
+    Predicate<RpcRoutingStrategy> rpcFilter() {
+        return RpcRoutingStrategy::isContextBasedRouted;
+    }
+}
diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/BindingContext.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/BindingContext.java
new file mode 100644 (file)
index 0000000..93c46b4
--- /dev/null
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2016 Brocade Communications 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.blueprint.ext;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.net.URISyntaxException;
+import java.util.List;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.transform.dom.DOMSource;
+import org.opendaylight.mdsal.binding.spec.reflect.BindingReflections;
+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.common.QName;
+import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.codec.xml.XmlParserStream;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
+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.SchemaContext;
+import org.osgi.service.blueprint.container.ComponentDefinitionException;
+import org.w3c.dom.Element;
+import org.xml.sax.SAXException;
+
+/**
+ * Base class to abstract binding type-specific behavior.
+ *
+ * @author Thomas Pantelis (originally; re-factored by Michael Vorburger.ch)
+ */
+public abstract class BindingContext {
+    private static String GET_KEY_METHOD = "key";
+
+    public static BindingContext create(final String logName, final Class<? extends DataObject> klass,
+            final String appConfigListKeyValue) {
+        if (Identifiable.class.isAssignableFrom(klass)) {
+            // 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, klass.getName(), DataStoreAppConfigMetadata.LIST_KEY_VALUE));
+            }
+
+            try {
+                return ListBindingContext.newInstance(klass, appConfigListKeyValue);
+            } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
+                    | InvocationTargetException | NoSuchMethodException | SecurityException e) {
+                throw new ComponentDefinitionException(String.format(
+                        "%s: Error initializing for app config list binding class %s",
+                        logName, klass.getName()), e);
+            }
+
+        } else {
+            return new ContainerBindingContext(klass);
+        }
+    }
+
+    public final InstanceIdentifier<DataObject> appConfigPath;
+    public final Class<DataObject> appConfigBindingClass;
+    public final Class<? extends DataSchemaNode> schemaType;
+    public final QName bindingQName;
+
+    private BindingContext(final Class<DataObject> appConfigBindingClass,
+            final InstanceIdentifier<DataObject> appConfigPath, final Class<? extends DataSchemaNode> schemaType) {
+        this.appConfigBindingClass = appConfigBindingClass;
+        this.appConfigPath = appConfigPath;
+        this.schemaType = schemaType;
+
+        bindingQName = BindingReflections.findQName(appConfigBindingClass);
+    }
+
+    public NormalizedNode<?, ?> parseDataElement(final Element element, final DataSchemaNode dataSchema,
+            final SchemaContext schemaContext) throws XMLStreamException, IOException, ParserConfigurationException,
+            SAXException, URISyntaxException {
+        final NormalizedNodeResult resultHolder = new NormalizedNodeResult();
+        final NormalizedNodeStreamWriter writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder);
+        final XmlParserStream xmlParser = XmlParserStream.create(writer, schemaContext, dataSchema);
+        xmlParser.traverse(new DOMSource(element));
+
+        final NormalizedNode<?, ?> result = resultHolder.getResult();
+        if (result instanceof MapNode) {
+            final MapNode mapNode = (MapNode) result;
+            final MapEntryNode mapEntryNode = mapNode.getValue().iterator().next();
+            return mapEntryNode;
+        }
+
+        return result;
+    }
+
+    public abstract NormalizedNode<?, ?> newDefaultNode(DataSchemaNode dataSchema);
+
+    /**
+     * BindingContext implementation for a container binding.
+     */
+    private static class ContainerBindingContext extends BindingContext {
+        @SuppressWarnings("unchecked")
+        ContainerBindingContext(final Class<? extends DataObject> appConfigBindingClass) {
+            super((Class<DataObject>) appConfigBindingClass,
+                    InstanceIdentifier.create((Class<DataObject>) appConfigBindingClass), ContainerSchemaNode.class);
+        }
+
+        @Override
+        public NormalizedNode<?, ?> newDefaultNode(final DataSchemaNode dataSchema) {
+            return ImmutableNodes.containerNode(bindingQName);
+        }
+    }
+
+    /**
+     * BindingContext implementation for a list binding.
+     */
+    private static class ListBindingContext extends BindingContext {
+        final String appConfigListKeyValue;
+
+        @SuppressWarnings("unchecked")
+        ListBindingContext(final Class<? extends DataObject> appConfigBindingClass,
+                final InstanceIdentifier<? extends DataObject> appConfigPath, final String appConfigListKeyValue) {
+            super((Class<DataObject>) appConfigBindingClass, (InstanceIdentifier<DataObject>) appConfigPath,
+                    ListSchemaNode.class);
+            this.appConfigListKeyValue = appConfigListKeyValue;
+        }
+
+        @SuppressWarnings({ "rawtypes", "unchecked" })
+        private static ListBindingContext newInstance(final Class<? extends DataObject> bindingClass,
+                final String listKeyValue) throws InstantiationException, IllegalAccessException,
+                IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
+            // We assume the yang list key type is string.
+            Identifier keyInstance = (Identifier) bindingClass.getMethod(GET_KEY_METHOD).getReturnType()
+                    .getConstructor(String.class).newInstance(listKeyValue);
+            InstanceIdentifier appConfigPath = InstanceIdentifier.builder((Class)bindingClass, keyInstance).build();
+            return new ListBindingContext(bindingClass, appConfigPath, listKeyValue);
+        }
+
+        @Override
+        public NormalizedNode<?, ?> newDefaultNode(final 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.get(0);
+            return ImmutableNodes.mapEntryBuilder(bindingQName, listKeyQName, appConfigListKeyValue).build();
+        }
+    }
+}
diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/ComponentProcessor.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/ComponentProcessor.java
new file mode 100644 (file)
index 0000000..84847a1
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2016 Brocade Communications 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.blueprint.ext;
+
+import com.google.common.base.Strings;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.apache.aries.blueprint.ComponentDefinitionRegistry;
+import org.apache.aries.blueprint.ComponentDefinitionRegistryProcessor;
+import org.apache.aries.blueprint.ext.AbstractPropertyPlaceholder;
+import org.apache.aries.blueprint.mutable.MutableBeanMetadata;
+import org.apache.aries.blueprint.mutable.MutableServiceReferenceMetadata;
+import org.apache.aries.util.AriesFrameworkUtil;
+import org.opendaylight.controller.blueprint.BlueprintContainerRestartService;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.blueprint.reflect.BeanProperty;
+import org.osgi.service.blueprint.reflect.ComponentMetadata;
+import org.osgi.service.blueprint.reflect.ValueMetadata;
+import org.osgi.service.cm.ManagedService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The singleton component processor that is invoked by the blueprint container to perform operations on
+ * various component definitions prior to component creation.
+ *
+ * @author Thomas Pantelis
+ */
+public class ComponentProcessor implements ComponentDefinitionRegistryProcessor {
+    static final String DEFAULT_TYPE_FILTER = "(|(type=default)(!(type=*)))";
+
+    private static final Logger LOG = LoggerFactory.getLogger(ComponentProcessor.class);
+    private static final String CM_PERSISTENT_ID_PROPERTY = "persistentId";
+
+    private final List<ServiceRegistration<?>> managedServiceRegs = new ArrayList<>();
+    private Bundle bundle;
+    private BlueprintContainerRestartService blueprintContainerRestartService;
+    private boolean restartDependentsOnUpdates;
+    private boolean useDefaultForReferenceTypes;
+
+    public void setBundle(final Bundle bundle) {
+        this.bundle = bundle;
+    }
+
+    public void setBlueprintContainerRestartService(final BlueprintContainerRestartService restartService) {
+        this.blueprintContainerRestartService = restartService;
+    }
+
+    public void setRestartDependentsOnUpdates(final boolean restartDependentsOnUpdates) {
+        this.restartDependentsOnUpdates = restartDependentsOnUpdates;
+    }
+
+    public void setUseDefaultForReferenceTypes(final boolean useDefaultForReferenceTypes) {
+        this.useDefaultForReferenceTypes = useDefaultForReferenceTypes;
+    }
+
+    public void destroy() {
+        for (ServiceRegistration<?> reg: managedServiceRegs) {
+            AriesFrameworkUtil.safeUnregisterService(reg);
+        }
+    }
+
+    @Override
+    public void process(final ComponentDefinitionRegistry registry) {
+        LOG.debug("{}: In process",  logName());
+
+        for (String name : registry.getComponentDefinitionNames()) {
+            ComponentMetadata component = registry.getComponentDefinition(name);
+            if (component instanceof MutableBeanMetadata) {
+                processMutableBeanMetadata((MutableBeanMetadata) component);
+            } else if (component instanceof MutableServiceReferenceMetadata) {
+                processServiceReferenceMetadata((MutableServiceReferenceMetadata)component);
+            }
+        }
+    }
+
+    private void processServiceReferenceMetadata(final MutableServiceReferenceMetadata serviceRef) {
+        if (!useDefaultForReferenceTypes) {
+            return;
+        }
+
+        String filter = serviceRef.getFilter();
+        String extFilter = serviceRef.getExtendedFilter() == null ? null :
+            serviceRef.getExtendedFilter().getStringValue();
+
+        LOG.debug("{}: processServiceReferenceMetadata for {}, filter: {}, ext filter: {}", logName(),
+                serviceRef.getId(), filter, extFilter);
+
+        if (Strings.isNullOrEmpty(filter) && Strings.isNullOrEmpty(extFilter)) {
+            serviceRef.setFilter(DEFAULT_TYPE_FILTER);
+
+            LOG.debug("{}: processServiceReferenceMetadata for {} set filter to {}", logName(),
+                    serviceRef.getId(), serviceRef.getFilter());
+        }
+    }
+
+    private void processMutableBeanMetadata(final MutableBeanMetadata bean) {
+        if (restartDependentsOnUpdates && bean.getRuntimeClass() != null
+                && AbstractPropertyPlaceholder.class.isAssignableFrom(bean.getRuntimeClass())) {
+            LOG.debug("{}: Found PropertyPlaceholder bean: {}, runtime {}", logName(), bean.getId(),
+                    bean.getRuntimeClass());
+
+            for (BeanProperty prop : bean.getProperties()) {
+                if (CM_PERSISTENT_ID_PROPERTY.equals(prop.getName())) {
+                    if (prop.getValue() instanceof ValueMetadata) {
+                        ValueMetadata persistentId = (ValueMetadata)prop.getValue();
+
+                        LOG.debug("{}: Found {} property, value : {}", logName(),
+                                CM_PERSISTENT_ID_PROPERTY, persistentId.getStringValue());
+
+                        registerManagedService(persistentId.getStringValue());
+                    } else {
+                        LOG.debug("{}: {} property metadata {} is not instanceof ValueMetadata",
+                                logName(), CM_PERSISTENT_ID_PROPERTY, prop.getValue());
+                    }
+
+                    break;
+                }
+            }
+        }
+    }
+
+    private void registerManagedService(final String persistentId) {
+        // Register a ManagedService so we get updates from the ConfigAdmin when the cfg file corresponding
+        // to the persistentId changes.
+        final ManagedService managedService = new ManagedService() {
+            private final AtomicBoolean initialUpdate = new AtomicBoolean(true);
+            private volatile Dictionary<String, ?> previousProperties;
+
+            @Override
+            public void updated(final Dictionary<String, ?> properties) {
+                LOG.debug("{}: ManagedService updated for persistentId {}, properties: {}, initialUpdate: {}",
+                        logName(), persistentId, properties, initialUpdate);
+
+                // The first update occurs when the service is registered so ignore it as we want subsequent
+                // updates when it changes. The ConfigAdmin will send an update even if the cfg file doesn't
+                // yet exist.
+                if (!initialUpdate.compareAndSet(true, false) && !Objects.equals(previousProperties, properties)) {
+                    blueprintContainerRestartService.restartContainerAndDependents(bundle);
+                }
+
+                previousProperties = properties;
+            }
+        };
+
+        Dictionary<String, Object> props = new Hashtable<>();
+        props.put(Constants.SERVICE_PID, persistentId);
+        props.put(Constants.BUNDLE_SYMBOLICNAME, bundle.getSymbolicName());
+        props.put(Constants.BUNDLE_VERSION, bundle.getHeaders().get(Constants.BUNDLE_VERSION));
+        managedServiceRegs.add(bundle.getBundleContext().registerService(ManagedService.class.getName(),
+                managedService, props));
+    }
+
+    private String logName() {
+        return bundle.getSymbolicName();
+    }
+}
diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/ConfigXMLReaderException.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/ConfigXMLReaderException.java
new file mode 100644 (file)
index 0000000..12a547e
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2017 Red Hat, 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.blueprint.ext;
+
+/**
+ * Exception thrown by {@link DataStoreAppConfigDefaultXMLReader}.
+ *
+ * @author Michael Vorburger.ch
+ */
+public class ConfigXMLReaderException extends Exception {
+    private static final long serialVersionUID = 1L;
+
+    public ConfigXMLReaderException(final String message) {
+        super(message);
+    }
+
+    public ConfigXMLReaderException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+
+}
diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/DataStoreAppConfigDefaultXMLReader.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/DataStoreAppConfigDefaultXMLReader.java
new file mode 100644 (file)
index 0000000..9f909c4
--- /dev/null
@@ -0,0 +1,200 @@
+/*
+ * Copyright (c) 2016 Brocade Communications 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.blueprint.ext;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Strings;
+import com.google.common.io.Resources;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URISyntaxException;
+import java.net.URL;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.stream.XMLStreamException;
+import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeSerializer;
+import org.opendaylight.mdsal.dom.api.DOMSchemaService;
+import org.opendaylight.yangtools.util.xml.UntrustedXML;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+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.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.xml.sax.SAXException;
+
+/**
+ * DataObject XML file reader used by {@link DataStoreAppConfigMetadata}.
+ * Available as a standalone class to make it easy to write unit tests which can
+ * catch malformed default "clustered-app-conf" config data XML files in
+ * downstream projects.
+ *
+ * @author Thomas Pantelis (originally; re-factored by Michael Vorburger.ch)
+ */
+public class DataStoreAppConfigDefaultXMLReader<T extends DataObject> {
+
+    private static final Logger LOG = LoggerFactory.getLogger(DataStoreAppConfigDefaultXMLReader.class);
+
+    private final String logName;
+    private final String defaultAppConfigFileName;
+    private final DOMSchemaService schemaService;
+    private final BindingNormalizedNodeSerializer bindingSerializer;
+    private final BindingContext bindingContext;
+    private final ConfigURLProvider inputStreamProvider;
+
+    @FunctionalInterface
+    public interface FallbackConfigProvider {
+        NormalizedNode<?,?> get(SchemaContext schemaContext, DataSchemaNode dataSchema) throws IOException,
+                XMLStreamException, ParserConfigurationException, SAXException, URISyntaxException;
+    }
+
+    @FunctionalInterface
+    public interface ConfigURLProvider {
+        Optional<URL> getURL(String appConfigFileName) throws IOException;
+    }
+
+    public DataStoreAppConfigDefaultXMLReader(
+            final String logName,
+            final String defaultAppConfigFileName,
+            final DOMSchemaService schemaService,
+            final BindingNormalizedNodeSerializer bindingSerializer,
+            final BindingContext bindingContext,
+            final ConfigURLProvider inputStreamProvider) {
+
+        this.logName = logName;
+        this.defaultAppConfigFileName = defaultAppConfigFileName;
+        this.schemaService = schemaService;
+        this.bindingSerializer = bindingSerializer;
+        this.bindingContext = bindingContext;
+        this.inputStreamProvider = inputStreamProvider;
+    }
+
+    public DataStoreAppConfigDefaultXMLReader(
+            final Class<?> testClass,
+            final String defaultAppConfigFileName,
+            final DOMSchemaService schemaService,
+            final BindingNormalizedNodeSerializer bindingSerializer,
+            final Class<T> klass) {
+        this(testClass.getName(), defaultAppConfigFileName, schemaService, bindingSerializer,
+            BindingContext.create(testClass.getName(), klass, null),
+            appConfigFileName -> Optional.of(getURL(testClass, defaultAppConfigFileName)));
+    }
+
+    private static URL getURL(final Class<?> testClass, final String defaultAppConfigFileName) {
+        return Resources.getResource(testClass, defaultAppConfigFileName);
+    }
+
+    public T createDefaultInstance() throws ConfigXMLReaderException, ParserConfigurationException, XMLStreamException,
+            IOException, SAXException, URISyntaxException {
+        return createDefaultInstance((schemaContext, dataSchema) -> {
+            throw new IllegalArgumentException("Failed to read XML "
+                    + "(not creating model from defaults as runtime would, for better clarity in tests)");
+        });
+    }
+
+    @SuppressWarnings("unchecked")
+    public T createDefaultInstance(final FallbackConfigProvider fallback) throws ConfigXMLReaderException,
+            URISyntaxException, ParserConfigurationException, XMLStreamException, SAXException, IOException {
+        YangInstanceIdentifier yangPath = bindingSerializer.toYangInstanceIdentifier(bindingContext.appConfigPath);
+
+        LOG.debug("{}: Creating app config instance from path {}, Qname: {}", logName, yangPath,
+                bindingContext.bindingQName);
+
+        checkNotNull(schemaService, "%s: Could not obtain the SchemaService OSGi service", logName);
+
+        SchemaContext schemaContext = schemaService.getGlobalContext();
+
+        Module module = schemaContext.findModule(bindingContext.bindingQName.getModule()).orElse(null);
+        checkNotNull(module, "%s: Could not obtain the module schema for namespace %s, revision %s",
+                logName, bindingContext.bindingQName.getNamespace(), bindingContext.bindingQName.getRevision());
+
+        DataSchemaNode dataSchema = module.getDataChildByName(bindingContext.bindingQName);
+        checkNotNull(dataSchema, "%s: Could not obtain the schema for %s", logName, bindingContext.bindingQName);
+
+        checkCondition(bindingContext.schemaType.isAssignableFrom(dataSchema.getClass()),
+                "%s: Expected schema type %s for %s but actual type is %s", logName,
+                bindingContext.schemaType, bindingContext.bindingQName, dataSchema.getClass());
+
+        NormalizedNode<?, ?> dataNode = parsePossibleDefaultAppConfigXMLFile(schemaContext, dataSchema);
+        if (dataNode == null) {
+            dataNode = fallback.get(schemaService.getGlobalContext(), dataSchema);
+        }
+
+        DataObject appConfig = bindingSerializer.fromNormalizedNode(yangPath, dataNode).getValue();
+
+        // This shouldn't happen but need to handle it in case...
+        checkNotNull(appConfig, "%s: Could not create instance for app config binding %s", logName,
+                bindingContext.appConfigBindingClass);
+
+        return (T) appConfig;
+    }
+
+    private static void checkNotNull(final Object reference, final String errorMessageFormat,
+            final Object... formatArgs) throws ConfigXMLReaderException {
+        checkCondition(reference != null, errorMessageFormat, formatArgs);
+    }
+
+    private static void checkCondition(final boolean expression, final String errorMessageFormat,
+            final Object... formatArgs) throws ConfigXMLReaderException {
+        if (!expression) {
+            throw new ConfigXMLReaderException(String.format(errorMessageFormat, formatArgs));
+        }
+    }
+
+    private NormalizedNode<?, ?> parsePossibleDefaultAppConfigXMLFile(final SchemaContext schemaContext,
+            final DataSchemaNode dataSchema) throws ConfigXMLReaderException {
+
+        String appConfigFileName = defaultAppConfigFileName;
+        if (Strings.isNullOrEmpty(appConfigFileName)) {
+            String moduleName = findYangModuleName(bindingContext.bindingQName, schemaContext);
+            appConfigFileName = moduleName + "_" + bindingContext.bindingQName.getLocalName() + ".xml";
+        }
+
+        Optional<URL> optionalURL;
+        try {
+            optionalURL = inputStreamProvider.getURL(appConfigFileName);
+        } catch (final IOException e) {
+            String msg = String.format("%s: Could not getURL()", logName);
+            LOG.error(msg, e);
+            throw new ConfigXMLReaderException(msg, e);
+        }
+        if (!optionalURL.isPresent()) {
+            return null;
+        }
+        URL url = optionalURL.get();
+        try (InputStream is = url.openStream()) {
+            Document root = UntrustedXML.newDocumentBuilder().parse(is);
+            NormalizedNode<?, ?> dataNode = bindingContext.parseDataElement(root.getDocumentElement(), dataSchema,
+                    schemaContext);
+
+            LOG.debug("{}: Parsed data node: {}", logName, dataNode);
+
+            return dataNode;
+        } catch (final IOException | SAXException | XMLStreamException | ParserConfigurationException
+                | URISyntaxException e) {
+            String msg = String.format("%s: Could not read/parse app config %s", logName, url);
+            LOG.error(msg, e);
+            throw new ConfigXMLReaderException(msg, e);
+        }
+    }
+
+    private String findYangModuleName(final QName qname, final SchemaContext schemaContext)
+            throws ConfigXMLReaderException {
+        for (Module m : schemaContext.getModules()) {
+            if (qname.getModule().equals(m.getQNameModule())) {
+                return m.getName();
+            }
+        }
+        throw new ConfigXMLReaderException(
+                String.format("%s: Could not find yang module for QName %s", logName, qname));
+    }
+
+}
diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/DataStoreAppConfigMetadata.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/DataStoreAppConfigMetadata.java
new file mode 100644 (file)
index 0000000..0d6585b
--- /dev/null
@@ -0,0 +1,314 @@
+/*
+ * Copyright (c) 2016 Brocade Communications 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.blueprint.ext;
+
+import com.google.common.base.Optional;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+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.Objects;
+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.blueprint.ext.DataStoreAppConfigDefaultXMLReader.ConfigURLProvider;
+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.mdsal.binding.dom.codec.api.BindingNormalizedNodeSerializer;
+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.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+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
+ * config data from the data store and provides the binding DataObject instance to the Blueprint container
+ * as a bean. In addition registers a DataTreeChangeListener to restart the Blueprint container when the
+ * config data is changed.
+ *
+ * @author Thomas Pantelis
+ */
+public class DataStoreAppConfigMetadata extends AbstractDependentComponentFactoryMetadata {
+    private static final Logger LOG = LoggerFactory.getLogger(DataStoreAppConfigMetadata.class);
+
+    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;
+    private volatile ListenerRegistration<?> appConfigChangeListenerReg;
+    private volatile DataObject currentAppConfig;
+
+    // Note: the BindingNormalizedNodeSerializer interface is annotated as deprecated because there's an
+    // equivalent interface in the mdsal project but the corresponding binding classes in the controller
+    // project are still used - conversion to the mdsal binding classes hasn't occurred yet.
+    private volatile BindingNormalizedNodeSerializer bindingSerializer;
+
+    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(final ExtendedBlueprintContainer container) {
+        super.init(container);
+
+        Class<DataObject> appConfigBindingClass;
+        try {
+            Class<?> bindingClass = container.getBundleContext().getBundle().loadClass(appConfigBindingClassName);
+            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 (final ClassNotFoundException e) {
+            throw new ComponentDefinitionException(String.format("%s: Error loading app config binding class %s",
+                    logName(), appConfigBindingClassName), e);
+        }
+
+        bindingContext = BindingContext.create(logName(), appConfigBindingClass, appConfigListKeyValue);
+    }
+
+    @Override
+    public Object create() throws ComponentDefinitionException {
+        LOG.debug("{}: In create - currentAppConfig: {}", logName(), currentAppConfig);
+
+        super.onCreate();
+
+        return currentAppConfig;
+    }
+
+    @Override
+    protected void startTracking() {
+        // First get the BindingNormalizedNodeSerializer OSGi service. This will be used to create a default
+        // instance of the app config binding class, if necessary.
+
+        retrieveService("binding-codec", BindingNormalizedNodeSerializer.class, service -> {
+            bindingSerializer = (BindingNormalizedNodeSerializer)service;
+            retrieveDataBrokerService();
+        });
+    }
+
+    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(final DataBroker dataBroker) {
+        LOG.debug("{}: Got DataBroker instance - reading app config {}", logName(), bindingContext.appConfigPath);
+
+        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,
+                bindingContext.appConfigPath);
+        appConfigChangeListenerReg = dataBroker.registerDataTreeChangeListener(dataTreeId,
+                (ClusteredDataTreeChangeListener<DataObject>) this::onAppConfigChanged);
+
+        readInitialAppConfig(dataBroker);
+    }
+
+    private void readInitialAppConfig(final DataBroker dataBroker) {
+        final ReadOnlyTransaction readOnlyTx = dataBroker.newReadOnlyTransaction();
+        ListenableFuture<Optional<DataObject>> future = readOnlyTx.read(
+                LogicalDatastoreType.CONFIGURATION, bindingContext.appConfigPath);
+        Futures.addCallback(future, new FutureCallback<Optional<DataObject>>() {
+            @Override
+            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(final Throwable failure) {
+                readOnlyTx.close();
+
+                // We may have gotten the app config via the data tree change listener so only retry if not.
+                if (readingInitialAppConfig.get()) {
+                    LOG.warn("{}: Read of app config {} failed - retrying", logName(),
+                            bindingContext.appConfigBindingClass.getName(), failure);
+
+                    readInitialAppConfig(dataBroker);
+                }
+            }
+        }, MoreExecutors.directExecutor());
+    }
+
+    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) {
+                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");
+
+                    if (appConfigUpdateStrategy == UpdateStrategy.RELOAD) {
+                        restartContainer();
+                    }
+                }
+            } else if (type == ModificationType.DELETE) {
+                LOG.debug("App config was deleted");
+
+                if (appConfigUpdateStrategy == UpdateStrategy.RELOAD) {
+                    restartContainer();
+                }
+            }
+        }
+    }
+
+    private boolean setInitialAppConfig(final Optional<DataObject> possibleAppConfig) {
+        boolean result = readingInitialAppConfig.compareAndSet(true, false);
+        if (result) {
+            DataObject localAppConfig;
+            if (possibleAppConfig.isPresent()) {
+                localAppConfig = possibleAppConfig.get();
+            } else {
+                // No app config data is present so create an empty instance via the bindingSerializer service.
+                // This will also return default values for leafs that haven't been explicitly set.
+                localAppConfig = createDefaultInstance();
+            }
+
+            LOG.debug("{}: Setting currentAppConfig instance: {}", logName(), localAppConfig);
+
+            // Now publish the app config instance to the volatile field and notify the callback to let the
+            // container know our dependency is now satisfied.
+            currentAppConfig = localAppConfig;
+            setSatisfied();
+        }
+
+        return result;
+    }
+
+    private DataObject createDefaultInstance() {
+        try {
+            ConfigURLProvider inputStreamProvider = appConfigFileName -> {
+                File appConfigFile = new File(DEFAULT_APP_CONFIG_FILE_PATH, appConfigFileName);
+                LOG.debug("{}: parsePossibleDefaultAppConfigXMLFile looking for file {}", logName(),
+                        appConfigFile.getAbsolutePath());
+
+                if (!appConfigFile.exists()) {
+                    return Optional.absent();
+                }
+
+                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;
+                }
+            });
+
+        } catch (final ConfigXMLReaderException | IOException | SAXException | XMLStreamException
+                | ParserConfigurationException | URISyntaxException e) {
+            if (e.getCause() == null) {
+                setFailureMessage(e.getMessage());
+            } else {
+                setFailure(e.getMessage(), e);
+            }
+            return null;
+        }
+    }
+
+    @Nullable
+    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);
+
+        LOG.debug("{}: Got app config schema: {}", logName(), dataSchema);
+
+        NormalizedNode<?, ?> dataNode = bindingContext.parseDataElement(defaultAppConfigElement, dataSchema,
+                schemaContext);
+
+        LOG.debug("{}: Parsed data node: {}", logName(), dataNode);
+
+        return dataNode;
+    }
+
+    @Override
+    public void destroy(final Object instance) {
+        super.destroy(instance);
+
+        if (appConfigChangeListenerReg != null) {
+            appConfigChangeListenerReg.close();
+            appConfigChangeListenerReg = null;
+        }
+    }
+
+}
diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/MandatoryServiceReferenceMetadata.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/MandatoryServiceReferenceMetadata.java
new file mode 100644 (file)
index 0000000..8139e1d
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2016 Brocade Communications 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.blueprint.ext;
+
+import com.google.common.base.Preconditions;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import org.osgi.service.blueprint.reflect.ReferenceListener;
+import org.osgi.service.blueprint.reflect.ServiceReferenceMetadata;
+
+/**
+ * A ServiceReferenceMetadata implementation for a mandatory OSGi service.
+ *
+ * @author Thomas Pantelis
+ */
+class MandatoryServiceReferenceMetadata implements ServiceReferenceMetadata {
+    private final String interfaceClass;
+    private final String id;
+
+    MandatoryServiceReferenceMetadata(final String id, final String interfaceClass) {
+        this.id = Preconditions.checkNotNull(id);
+        this.interfaceClass = interfaceClass;
+    }
+
+    @Override
+    public String getId() {
+        return id;
+    }
+
+    @Override
+    public int getActivation() {
+        return ACTIVATION_EAGER;
+    }
+
+    @Override
+    public List<String> getDependsOn() {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public int getAvailability() {
+        return AVAILABILITY_MANDATORY;
+    }
+
+    @Override
+    public String getInterface() {
+        return interfaceClass;
+    }
+
+    @Override
+    public String getComponentName() {
+        return null;
+    }
+
+    @Override
+    public String getFilter() {
+        return ComponentProcessor.DEFAULT_TYPE_FILTER;
+    }
+
+    @Override
+    public Collection<ReferenceListener> getReferenceListeners() {
+        return Collections.emptyList();
+    }
+}
diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/NotificationListenerBean.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/NotificationListenerBean.java
new file mode 100644 (file)
index 0000000..74c2956
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2016 Brocade Communications 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.blueprint.ext;
+
+import org.opendaylight.mdsal.binding.api.NotificationService;
+import org.opendaylight.yangtools.concepts.ListenerRegistration;
+import org.opendaylight.yangtools.yang.binding.NotificationListener;
+import org.osgi.framework.Bundle;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Blueprint bean corresponding to the "notification-listener" element that registers a NotificationListener
+ * with the NotificationService.
+ *
+ * @author Thomas Pantelis
+ */
+public class NotificationListenerBean {
+    private static final Logger LOG = LoggerFactory.getLogger(NotificationListenerBean.class);
+    static final String NOTIFICATION_LISTENER = "notification-listener";
+
+    private Bundle bundle;
+    private NotificationService notificationService;
+    private NotificationListener notificationListener;
+    private ListenerRegistration<?> registration;
+
+    public void setNotificationService(final NotificationService notificationService) {
+        this.notificationService = notificationService;
+    }
+
+    public void setNotificationListener(final NotificationListener notificationListener) {
+        this.notificationListener = notificationListener;
+    }
+
+    public void setBundle(final Bundle bundle) {
+        this.bundle = bundle;
+    }
+
+    public void init() {
+        LOG.debug("{}: init - registering NotificationListener {}", bundle.getSymbolicName(), notificationListener);
+
+        registration = notificationService.registerNotificationListener(notificationListener);
+    }
+
+    public void destroy() {
+        if (registration != null) {
+            LOG.debug("{}: destroy - closing ListenerRegistration {}", bundle.getSymbolicName(), notificationListener);
+            registration.close();
+        } else {
+            LOG.debug("{}: destroy - listener was not registered", bundle.getSymbolicName());
+        }
+    }
+}
diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/OpendaylightNamespaceHandler.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/OpendaylightNamespaceHandler.java
new file mode 100644 (file)
index 0000000..ff22f1b
--- /dev/null
@@ -0,0 +1,498 @@
+/*
+ * Copyright (c) 2016 Brocade Communications 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.blueprint.ext;
+
+import com.google.common.base.Strings;
+import java.io.IOException;
+import java.io.StringReader;
+import java.net.URL;
+import java.util.Collections;
+import java.util.Set;
+import org.apache.aries.blueprint.ComponentDefinitionRegistry;
+import org.apache.aries.blueprint.NamespaceHandler;
+import org.apache.aries.blueprint.ParserContext;
+import org.apache.aries.blueprint.ext.ComponentFactoryMetadata;
+import org.apache.aries.blueprint.mutable.MutableBeanMetadata;
+import org.apache.aries.blueprint.mutable.MutableRefMetadata;
+import org.apache.aries.blueprint.mutable.MutableReferenceMetadata;
+import org.apache.aries.blueprint.mutable.MutableServiceMetadata;
+import org.apache.aries.blueprint.mutable.MutableServiceReferenceMetadata;
+import org.apache.aries.blueprint.mutable.MutableValueMetadata;
+import org.opendaylight.controller.blueprint.BlueprintContainerRestartService;
+import org.opendaylight.controller.sal.binding.api.RpcProviderRegistry;
+import org.opendaylight.mdsal.binding.api.NotificationService;
+import org.opendaylight.mdsal.binding.api.RpcProviderService;
+import org.opendaylight.mdsal.dom.api.DOMRpcProviderService;
+import org.opendaylight.mdsal.dom.api.DOMSchemaService;
+import org.opendaylight.yangtools.util.xml.UntrustedXML;
+import org.osgi.service.blueprint.container.ComponentDefinitionException;
+import org.osgi.service.blueprint.reflect.BeanMetadata;
+import org.osgi.service.blueprint.reflect.ComponentMetadata;
+import org.osgi.service.blueprint.reflect.Metadata;
+import org.osgi.service.blueprint.reflect.RefMetadata;
+import org.osgi.service.blueprint.reflect.ReferenceMetadata;
+import org.osgi.service.blueprint.reflect.ServiceMetadata;
+import org.osgi.service.blueprint.reflect.ServiceReferenceMetadata;
+import org.osgi.service.blueprint.reflect.ValueMetadata;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+/**
+ * The NamespaceHandler for Opendaylight blueprint extensions.
+ *
+ * @author Thomas Pantelis
+ */
+public final class OpendaylightNamespaceHandler implements NamespaceHandler {
+    public static final String NAMESPACE_1_0_0 = "http://opendaylight.org/xmlns/blueprint/v1.0.0";
+    static final String ROUTED_RPC_REG_CONVERTER_NAME = "org.opendaylight.blueprint.RoutedRpcRegConverter";
+    static final String DOM_RPC_PROVIDER_SERVICE_NAME = "org.opendaylight.blueprint.DOMRpcProviderService";
+    static final String RPC_REGISTRY_NAME = "org.opendaylight.blueprint.RpcRegistry";
+    static final String BINDING_RPC_PROVIDER_SERVICE_NAME = "org.opendaylight.blueprint.RpcProviderService";
+    static final String SCHEMA_SERVICE_NAME = "org.opendaylight.blueprint.SchemaService";
+    static final String NOTIFICATION_SERVICE_NAME = "org.opendaylight.blueprint.NotificationService";
+    static final String TYPE_ATTR = "type";
+    static final String UPDATE_STRATEGY_ATTR = "update-strategy";
+
+    private static final Logger LOG = LoggerFactory.getLogger(OpendaylightNamespaceHandler.class);
+    private static final String COMPONENT_PROCESSOR_NAME = ComponentProcessor.class.getName();
+    private static final String RESTART_DEPENDENTS_ON_UPDATES = "restart-dependents-on-updates";
+    private static final String USE_DEFAULT_FOR_REFERENCE_TYPES = "use-default-for-reference-types";
+    private static final String CLUSTERED_APP_CONFIG = "clustered-app-config";
+    private static final String INTERFACE = "interface";
+    private static final String REF_ATTR = "ref";
+    private static final String ID_ATTR = "id";
+    private static final String RPC_SERVICE = "rpc-service";
+    private static final String ACTION_SERVICE = "action-service";
+    private static final String SPECIFIC_SERVICE_REF_LIST = "specific-reference-list";
+    private static final String STATIC_REFERENCE = "static-reference";
+
+    @SuppressWarnings("rawtypes")
+    @Override
+    public Set<Class> getManagedClasses() {
+        return Collections.emptySet();
+    }
+
+    @Override
+    public URL getSchemaLocation(final String namespace) {
+        if (NAMESPACE_1_0_0.equals(namespace)) {
+            URL url = getClass().getResource("/opendaylight-blueprint-ext-1.0.0.xsd");
+            LOG.debug("getSchemaLocation for {} returning URL {}", namespace, url);
+            return url;
+        }
+
+        return null;
+    }
+
+    @Override
+    public Metadata parse(final Element element, final ParserContext context) {
+        LOG.debug("In parse for {}", element);
+
+        if (nodeNameEquals(element, RpcImplementationBean.RPC_IMPLEMENTATION)) {
+            return parseRpcImplementation(element, context);
+        } else if (nodeNameEquals(element, RoutedRpcMetadata.ROUTED_RPC_IMPLEMENTATION)) {
+            return parseRoutedRpcImplementation(element, context);
+        } else if (nodeNameEquals(element, RPC_SERVICE)) {
+            return parseRpcService(element, context);
+        } else if (nodeNameEquals(element, NotificationListenerBean.NOTIFICATION_LISTENER)) {
+            return parseNotificationListener(element, context);
+        } else if (nodeNameEquals(element, CLUSTERED_APP_CONFIG)) {
+            return parseClusteredAppConfig(element, context);
+        } else if (nodeNameEquals(element, SPECIFIC_SERVICE_REF_LIST)) {
+            return parseSpecificReferenceList(element, context);
+        } else if (nodeNameEquals(element, STATIC_REFERENCE)) {
+            return parseStaticReference(element, context);
+        } else if (nodeNameEquals(element, ACTION_SERVICE)) {
+            return parseActionService(element, context);
+        } else if (nodeNameEquals(element, ActionProviderBean.ACTION_PROVIDER)) {
+            return parseActionProvider(element, context);
+        }
+
+        throw new ComponentDefinitionException("Unsupported standalone element: " + element.getNodeName());
+    }
+
+    @Override
+    public ComponentMetadata decorate(final Node node, final ComponentMetadata component, final ParserContext context) {
+        if (node instanceof Attr) {
+            if (nodeNameEquals(node, RESTART_DEPENDENTS_ON_UPDATES)) {
+                return decorateRestartDependentsOnUpdates((Attr) node, component, context);
+            } else if (nodeNameEquals(node, USE_DEFAULT_FOR_REFERENCE_TYPES)) {
+                return decorateUseDefaultForReferenceTypes((Attr) node, component, context);
+            } else if (nodeNameEquals(node, TYPE_ATTR)) {
+                if (component instanceof ServiceReferenceMetadata) {
+                    return decorateServiceReferenceType((Attr) node, component, context);
+                } else if (component instanceof ServiceMetadata) {
+                    return decorateServiceType((Attr)node, component, context);
+                }
+
+                throw new ComponentDefinitionException("Attribute " + node.getNodeName()
+                        + " can only be used on a <reference>, <reference-list> or <service> element");
+            }
+
+            throw new ComponentDefinitionException("Unsupported attribute: " + node.getNodeName());
+        } else {
+            throw new ComponentDefinitionException("Unsupported node type: " + node);
+        }
+    }
+
+    private static ComponentMetadata decorateServiceType(final Attr attr, final ComponentMetadata component,
+            final ParserContext context) {
+        if (!(component instanceof MutableServiceMetadata)) {
+            throw new ComponentDefinitionException("Expected an instanceof MutableServiceMetadata");
+        }
+
+        MutableServiceMetadata service = (MutableServiceMetadata)component;
+
+        LOG.debug("decorateServiceType for {} - adding type property {}", service.getId(), attr.getValue());
+
+        service.addServiceProperty(createValue(context, TYPE_ATTR), createValue(context, attr.getValue()));
+        return component;
+    }
+
+    private static ComponentMetadata decorateServiceReferenceType(final Attr attr, final ComponentMetadata component,
+            final ParserContext context) {
+        if (!(component instanceof MutableServiceReferenceMetadata)) {
+            throw new ComponentDefinitionException("Expected an instanceof MutableServiceReferenceMetadata");
+        }
+
+        // We don't actually need the ComponentProcessor for augmenting the OSGi filter here but we create it
+        // to workaround an issue in Aries where it doesn't use the extended filter unless there's a
+        // Processor or ComponentDefinitionRegistryProcessor registered. This may actually be working as
+        // designed in Aries b/c the extended filter was really added to allow the OSGi filter to be
+        // substituted by a variable via the "cm:property-placeholder" processor. If so, it's a bit funky
+        // but as long as there's at least one processor registered, it correctly uses the extended filter.
+        registerComponentProcessor(context);
+
+        MutableServiceReferenceMetadata serviceRef = (MutableServiceReferenceMetadata)component;
+        String oldFilter = serviceRef.getExtendedFilter() == null ? null :
+            serviceRef.getExtendedFilter().getStringValue();
+
+        String filter;
+        if (Strings.isNullOrEmpty(oldFilter)) {
+            filter = String.format("(type=%s)", attr.getValue());
+        } else {
+            filter = String.format("(&(%s)(type=%s))", oldFilter, attr.getValue());
+        }
+
+        LOG.debug("decorateServiceReferenceType for {} with type {}, old filter: {}, new filter: {}",
+                serviceRef.getId(), attr.getValue(), oldFilter, filter);
+
+        serviceRef.setExtendedFilter(createValue(context, filter));
+        return component;
+    }
+
+    private static ComponentMetadata decorateRestartDependentsOnUpdates(final Attr attr,
+            final ComponentMetadata component, final ParserContext context) {
+        return enableComponentProcessorProperty(attr, component, context, "restartDependentsOnUpdates");
+    }
+
+    private static ComponentMetadata decorateUseDefaultForReferenceTypes(final Attr attr,
+            final ComponentMetadata component, final ParserContext context) {
+        return enableComponentProcessorProperty(attr, component, context, "useDefaultForReferenceTypes");
+    }
+
+    private static ComponentMetadata enableComponentProcessorProperty(final Attr attr,
+            final ComponentMetadata component, final ParserContext context, final String propertyName) {
+        if (component != null) {
+            throw new ComponentDefinitionException("Attribute " + attr.getNodeName()
+                    + " can only be used on the root <blueprint> element");
+        }
+
+        LOG.debug("Property {} = {}", propertyName, attr.getValue());
+
+        if (!Boolean.parseBoolean(attr.getValue())) {
+            return component;
+        }
+
+        MutableBeanMetadata metadata = registerComponentProcessor(context);
+        metadata.addProperty(propertyName, createValue(context, "true"));
+
+        return component;
+    }
+
+    private static MutableBeanMetadata registerComponentProcessor(final ParserContext context) {
+        ComponentDefinitionRegistry registry = context.getComponentDefinitionRegistry();
+        MutableBeanMetadata metadata = (MutableBeanMetadata) registry.getComponentDefinition(COMPONENT_PROCESSOR_NAME);
+        if (metadata == null) {
+            metadata = createBeanMetadata(context, COMPONENT_PROCESSOR_NAME, ComponentProcessor.class, false, true);
+            metadata.setProcessor(true);
+            addBlueprintBundleRefProperty(context, metadata);
+            metadata.addProperty("blueprintContainerRestartService", createServiceRef(context,
+                    BlueprintContainerRestartService.class, null));
+
+            LOG.debug("Registering ComponentProcessor bean: {}", metadata);
+
+            registry.registerComponentDefinition(metadata);
+        }
+
+        return metadata;
+    }
+
+    private static Metadata parseActionProvider(final Element element, final ParserContext context) {
+        registerDomRpcProviderServiceRefBean(context);
+        registerBindingRpcProviderServiceRefBean(context);
+        registerSchemaServiceRefBean(context);
+
+        MutableBeanMetadata metadata = createBeanMetadata(context, context.generateId(), ActionProviderBean.class,
+                true, true);
+        addBlueprintBundleRefProperty(context, metadata);
+        metadata.addProperty("domRpcProvider", createRef(context, DOM_RPC_PROVIDER_SERVICE_NAME));
+        metadata.addProperty("bindingRpcProvider", createRef(context, BINDING_RPC_PROVIDER_SERVICE_NAME));
+        metadata.addProperty("schemaService", createRef(context, SCHEMA_SERVICE_NAME));
+        metadata.addProperty("interfaceName", createValue(context, element.getAttribute(INTERFACE)));
+
+        if (element.hasAttribute(REF_ATTR)) {
+            metadata.addProperty("implementation", createRef(context, element.getAttribute(REF_ATTR)));
+        }
+
+        LOG.debug("parseActionProvider returning {}", metadata);
+        return metadata;
+    }
+
+
+    private static Metadata parseRpcImplementation(final Element element, final ParserContext context) {
+        registerBindingRpcProviderServiceRefBean(context);
+
+        MutableBeanMetadata metadata = createBeanMetadata(context, context.generateId(), RpcImplementationBean.class,
+                true, true);
+        addBlueprintBundleRefProperty(context, metadata);
+        metadata.addProperty("rpcProvider", createRef(context, BINDING_RPC_PROVIDER_SERVICE_NAME));
+        metadata.addProperty("implementation", createRef(context, element.getAttribute(REF_ATTR)));
+
+        if (element.hasAttribute(INTERFACE)) {
+            metadata.addProperty("interfaceName", createValue(context, element.getAttribute(INTERFACE)));
+        }
+
+        LOG.debug("parseRpcImplementation returning {}", metadata);
+        return metadata;
+    }
+
+    private static Metadata parseRoutedRpcImplementation(final Element element, final ParserContext context) {
+        registerRefBean(context, RPC_REGISTRY_NAME, RpcProviderRegistry.class);
+        registerRoutedRpcRegistrationConverter(context);
+
+        ComponentFactoryMetadata metadata = new RoutedRpcMetadata(getId(context, element),
+                element.getAttribute(INTERFACE), element.getAttribute(REF_ATTR));
+
+        LOG.debug("parseRoutedRpcImplementation returning {}", metadata);
+
+        return metadata;
+    }
+
+    private static Metadata parseActionService(final Element element, final ParserContext context) {
+        ComponentFactoryMetadata metadata = new ActionServiceMetadata(getId(context, element),
+                element.getAttribute(INTERFACE));
+
+        LOG.debug("parseActionService returning {}", metadata);
+
+        return metadata;
+    }
+
+    private static Metadata parseRpcService(final Element element, final ParserContext context) {
+        ComponentFactoryMetadata metadata = new RpcServiceMetadata(getId(context, element),
+                element.getAttribute(INTERFACE));
+
+        LOG.debug("parseRpcService returning {}", metadata);
+
+        return metadata;
+    }
+
+    private static void registerRoutedRpcRegistrationConverter(final ParserContext context) {
+        ComponentDefinitionRegistry registry = context.getComponentDefinitionRegistry();
+        if (registry.getComponentDefinition(ROUTED_RPC_REG_CONVERTER_NAME) == null) {
+            MutableBeanMetadata metadata = createBeanMetadata(context, ROUTED_RPC_REG_CONVERTER_NAME,
+                    RoutedRpcRegistrationConverter.class, false, false);
+            metadata.setActivation(ReferenceMetadata.ACTIVATION_LAZY);
+            registry.registerTypeConverter(metadata);
+        }
+    }
+
+    private static void registerDomRpcProviderServiceRefBean(final ParserContext context) {
+        registerRefBean(context, DOM_RPC_PROVIDER_SERVICE_NAME, DOMRpcProviderService.class);
+    }
+
+    private static void registerBindingRpcProviderServiceRefBean(final ParserContext context) {
+        registerRefBean(context, BINDING_RPC_PROVIDER_SERVICE_NAME, RpcProviderService.class);
+    }
+
+    private static void registerSchemaServiceRefBean(final ParserContext context) {
+        registerRefBean(context, SCHEMA_SERVICE_NAME, DOMSchemaService.class);
+    }
+
+    private static void registerRefBean(final ParserContext context, final String name, final Class<?> clazz) {
+        ComponentDefinitionRegistry registry = context.getComponentDefinitionRegistry();
+        if (registry.getComponentDefinition(name) == null) {
+            MutableReferenceMetadata metadata = createServiceRef(context, clazz, null);
+            metadata.setId(name);
+            registry.registerComponentDefinition(metadata);
+        }
+    }
+
+    private static Metadata parseNotificationListener(final Element element, final ParserContext context) {
+        registerNotificationServiceRefBean(context);
+
+        MutableBeanMetadata metadata = createBeanMetadata(context, context.generateId(), NotificationListenerBean.class,
+                true, true);
+        addBlueprintBundleRefProperty(context, metadata);
+        metadata.addProperty("notificationService", createRef(context, NOTIFICATION_SERVICE_NAME));
+        metadata.addProperty("notificationListener", createRef(context, element.getAttribute(REF_ATTR)));
+
+        LOG.debug("parseNotificationListener returning {}", metadata);
+
+        return metadata;
+    }
+
+    private static void registerNotificationServiceRefBean(final ParserContext context) {
+        ComponentDefinitionRegistry registry = context.getComponentDefinitionRegistry();
+        if (registry.getComponentDefinition(NOTIFICATION_SERVICE_NAME) == null) {
+            MutableReferenceMetadata metadata = createServiceRef(context, NotificationService.class, null);
+            metadata.setId(NOTIFICATION_SERVICE_NAME);
+            registry.registerComponentDefinition(metadata);
+        }
+    }
+
+    private static Metadata parseClusteredAppConfig(final Element element, final ParserContext context) {
+        LOG.debug("parseClusteredAppConfig");
+
+        // Find the default-config child element representing the default app config XML, if present.
+        Element defaultConfigElement = null;
+        NodeList children = element.getChildNodes();
+        for (int i = 0; i < children.getLength(); i++) {
+            Node child = children.item(i);
+            if (nodeNameEquals(child, DataStoreAppConfigMetadata.DEFAULT_CONFIG)) {
+                defaultConfigElement = (Element) child;
+                break;
+            }
+        }
+
+        Element defaultAppConfigElement = null;
+        if (defaultConfigElement != null) {
+            // Find the CDATA element containing the default app config XML.
+            children = defaultConfigElement.getChildNodes();
+            for (int i = 0; i < children.getLength(); i++) {
+                Node child = children.item(i);
+                if (child.getNodeType() == Node.CDATA_SECTION_NODE) {
+                    defaultAppConfigElement = parseXML(DataStoreAppConfigMetadata.DEFAULT_CONFIG,
+                            child.getTextContent());
+                    break;
+                }
+            }
+        }
+
+        return new DataStoreAppConfigMetadata(getId(context, element), element.getAttribute(
+                DataStoreAppConfigMetadata.BINDING_CLASS), element.getAttribute(
+                        DataStoreAppConfigMetadata.LIST_KEY_VALUE), element.getAttribute(
+                        DataStoreAppConfigMetadata.DEFAULT_CONFIG_FILE_NAME), parseUpdateStrategy(
+                        element.getAttribute(UPDATE_STRATEGY_ATTR)), defaultAppConfigElement);
+    }
+
+    private static UpdateStrategy parseUpdateStrategy(final String updateStrategyValue) {
+        if (Strings.isNullOrEmpty(updateStrategyValue)
+                || updateStrategyValue.equalsIgnoreCase(UpdateStrategy.RELOAD.name())) {
+            return UpdateStrategy.RELOAD;
+        } else if (updateStrategyValue.equalsIgnoreCase(UpdateStrategy.NONE.name())) {
+            return UpdateStrategy.NONE;
+        } else {
+            LOG.warn("update-strategy {} not supported, using reload", updateStrategyValue);
+            return UpdateStrategy.RELOAD;
+        }
+    }
+
+    private static Metadata parseSpecificReferenceList(final Element element, final ParserContext context) {
+        ComponentFactoryMetadata metadata = new SpecificReferenceListMetadata(getId(context, element),
+                element.getAttribute(INTERFACE));
+
+        LOG.debug("parseSpecificReferenceList returning {}", metadata);
+
+        return metadata;
+    }
+
+    private static Metadata parseStaticReference(final Element element, final ParserContext context) {
+        ComponentFactoryMetadata metadata = new StaticReferenceMetadata(getId(context, element),
+                element.getAttribute(INTERFACE));
+
+        LOG.debug("parseStaticReference returning {}", metadata);
+
+        return metadata;
+    }
+
+    private static Element parseXML(final String name, final String xml) {
+        try {
+            return UntrustedXML.newDocumentBuilder().parse(new InputSource(new StringReader(xml))).getDocumentElement();
+        } catch (SAXException | IOException e) {
+            throw new ComponentDefinitionException(String.format("Error %s parsing XML: %s", name, xml), e);
+        }
+    }
+
+    private static ValueMetadata createValue(final ParserContext context, final String value) {
+        MutableValueMetadata metadata = context.createMetadata(MutableValueMetadata.class);
+        metadata.setStringValue(value);
+        return metadata;
+    }
+
+    private static MutableReferenceMetadata createServiceRef(final ParserContext context, final Class<?> cls,
+            final String filter) {
+        MutableReferenceMetadata metadata = context.createMetadata(MutableReferenceMetadata.class);
+        metadata.setRuntimeInterface(cls);
+        metadata.setInterface(cls.getName());
+        metadata.setActivation(ReferenceMetadata.ACTIVATION_EAGER);
+        metadata.setAvailability(ReferenceMetadata.AVAILABILITY_MANDATORY);
+
+        if (filter != null) {
+            metadata.setFilter(filter);
+        }
+
+        return metadata;
+    }
+
+    private static RefMetadata createRef(final ParserContext context, final String id) {
+        MutableRefMetadata metadata = context.createMetadata(MutableRefMetadata.class);
+        metadata.setComponentId(id);
+        return metadata;
+    }
+
+    private static String getId(final ParserContext context, final Element element) {
+        if (element.hasAttribute(ID_ATTR)) {
+            return element.getAttribute(ID_ATTR);
+        } else {
+            return context.generateId();
+        }
+    }
+
+    private static boolean nodeNameEquals(final Node node, final String name) {
+        return name.equals(node.getNodeName()) || name.equals(node.getLocalName());
+    }
+
+    private static void addBlueprintBundleRefProperty(final ParserContext context, final MutableBeanMetadata metadata) {
+        metadata.addProperty("bundle", createRef(context, "blueprintBundle"));
+    }
+
+    private static MutableBeanMetadata createBeanMetadata(final ParserContext context, final String id,
+            final Class<?> runtimeClass, final boolean initMethod, final boolean destroyMethod) {
+        MutableBeanMetadata metadata = context.createMetadata(MutableBeanMetadata.class);
+        metadata.setId(id);
+        metadata.setScope(BeanMetadata.SCOPE_SINGLETON);
+        metadata.setActivation(ReferenceMetadata.ACTIVATION_EAGER);
+        metadata.setRuntimeClass(runtimeClass);
+
+        if (initMethod) {
+            metadata.setInitMethod("init");
+        }
+
+        if (destroyMethod) {
+            metadata.setDestroyMethod("destroy");
+        }
+
+        return metadata;
+    }
+}
diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/RoutedRpcMetadata.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/RoutedRpcMetadata.java
new file mode 100644 (file)
index 0000000..725e568
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2016 Brocade Communications 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.blueprint.ext;
+
+import java.util.Arrays;
+import java.util.List;
+import org.apache.aries.blueprint.ext.ComponentFactoryMetadata;
+import org.apache.aries.blueprint.services.ExtendedBlueprintContainer;
+import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.RoutedRpcRegistration;
+import org.opendaylight.controller.sal.binding.api.RpcProviderRegistry;
+import org.opendaylight.yangtools.yang.binding.RpcService;
+import org.osgi.service.blueprint.container.ComponentDefinitionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Factory metadata corresponding to the "routed-rpc-implementation" element that registers an RPC
+ * implementation with the RpcProviderRegistry and provides the RoutedRpcRegistration instance to the
+ * Blueprint container.
+ *
+ * @author Thomas Pantelis
+ */
+class RoutedRpcMetadata implements ComponentFactoryMetadata {
+    private static final Logger LOG = LoggerFactory.getLogger(RoutedRpcMetadata.class);
+    static final String ROUTED_RPC_IMPLEMENTATION = "routed-rpc-implementation";
+
+    private final String id;
+    private final String interfaceName;
+    private final String implementationRefId;
+    private ExtendedBlueprintContainer container;
+
+    RoutedRpcMetadata(final String id, final String interfaceName, final String implementationRefId) {
+        this.id = id;
+        this.interfaceName = interfaceName;
+        this.implementationRefId = implementationRefId;
+    }
+
+    @Override
+    public String getId() {
+        return id;
+    }
+
+    @Override
+    public int getActivation() {
+        return ACTIVATION_LAZY;
+    }
+
+    @Override
+    public List<String> getDependsOn() {
+        return Arrays.asList(OpendaylightNamespaceHandler.RPC_REGISTRY_NAME, implementationRefId);
+    }
+
+    @Override
+    public void init(final ExtendedBlueprintContainer newContainer) {
+        this.container = newContainer;
+
+        LOG.debug("{}: In init", logName());
+    }
+
+    @SuppressWarnings("checkstyle:IllegalCatch")
+    @Override
+    public Object create() throws ComponentDefinitionException {
+        RpcProviderRegistry rpcRegistry = (RpcProviderRegistry) container.getComponentInstance(
+                OpendaylightNamespaceHandler.RPC_REGISTRY_NAME);
+
+        Object implementation = container.getComponentInstance(implementationRefId);
+
+        try {
+            if (!RpcService.class.isAssignableFrom(implementation.getClass())) {
+                throw new ComponentDefinitionException(String.format(
+                        "Implementation \"ref\" instance %s for \"%s\" is not an RpcService",
+                        implementation.getClass(), ROUTED_RPC_IMPLEMENTATION));
+            }
+
+            List<Class<RpcService>> rpcInterfaces = RpcImplementationBean.getImplementedRpcServiceInterfaces(
+                    interfaceName, implementation.getClass(), container.getBundleContext().getBundle(),
+                    ROUTED_RPC_IMPLEMENTATION);
+
+            if (rpcInterfaces.size() > 1) {
+                throw new ComponentDefinitionException(String.format(
+                        "Implementation \"ref\" instance %s for \"%s\" implements more than one RpcService "
+                        + "interface (%s). Please specify the exact \"interface\"", implementation.getClass(),
+                        ROUTED_RPC_IMPLEMENTATION, rpcInterfaces));
+            }
+
+            Class<RpcService> rpcInterface = rpcInterfaces.iterator().next();
+
+            LOG.debug("{}: create - adding routed implementation {} for RpcService {}", logName(),
+                    implementation, rpcInterface);
+
+            return rpcRegistry.addRoutedRpcImplementation(rpcInterface, (RpcService)implementation);
+        } catch (final ComponentDefinitionException e) {
+            throw e;
+        } catch (final Exception e) {
+            throw new ComponentDefinitionException(String.format(
+                    "Error processing \"%s\" for %s", ROUTED_RPC_IMPLEMENTATION, implementation.getClass()), e);
+        }
+    }
+
+    @Override
+    public void destroy(final Object instance) {
+        LOG.debug("{}: In destroy: instance: {}", logName(), instance);
+
+        ((RoutedRpcRegistration<?>)instance).close();
+    }
+
+    private String logName() {
+        return (container != null ? container.getBundleContext().getBundle().getSymbolicName() : "") + " (" + id + ")";
+    }
+}
diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/RoutedRpcRegistrationConverter.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/RoutedRpcRegistrationConverter.java
new file mode 100644 (file)
index 0000000..6617443
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2016 Brocade Communications 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.blueprint.ext;
+
+import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.RoutedRpcRegistration;
+import org.osgi.service.blueprint.container.Converter;
+import org.osgi.service.blueprint.container.ReifiedType;
+
+/**
+ * Implements a Converter that converts RoutedRpcRegistration instances. This is to work around an issue
+ * when injecting a RoutedRpcRegistration instance into a bean where Aries is not able to convert the instance
+ * returned from the RpcRegistryProvider to the desired generic RoutedRpcRegistration type specified in the
+ * bean's setter method. This is because the actual instance class specifies a generic type variable T and,
+ * even though it extends RpcService and should match, Aries doesn't handle it correctly.
+ *
+ * @author Thomas Pantelis
+ */
+public class RoutedRpcRegistrationConverter implements Converter {
+    @Override
+    public boolean canConvert(final Object sourceObject, final ReifiedType targetType) {
+        return sourceObject instanceof RoutedRpcRegistration
+                && RoutedRpcRegistration.class.isAssignableFrom(targetType.getRawClass());
+    }
+
+    @Override
+    public Object convert(final Object sourceObject, final ReifiedType targetType) {
+        return sourceObject;
+    }
+}
diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/RpcImplementationBean.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/RpcImplementationBean.java
new file mode 100644 (file)
index 0000000..94d5b3b
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2016 Brocade Communications 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.blueprint.ext;
+
+import com.google.common.base.Strings;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.opendaylight.mdsal.binding.api.RpcProviderService;
+import org.opendaylight.yangtools.concepts.ObjectRegistration;
+import org.opendaylight.yangtools.yang.binding.RpcService;
+import org.osgi.framework.Bundle;
+import org.osgi.service.blueprint.container.ComponentDefinitionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Blueprint bean corresponding to the "rpc-implementation" element that registers an RPC implementation with
+ * the RpcProviderRegistry.
+ *
+ * @author Thomas Pantelis
+ */
+public class RpcImplementationBean {
+    private static final Logger LOG = LoggerFactory.getLogger(RpcImplementationBean.class);
+    static final String RPC_IMPLEMENTATION = "rpc-implementation";
+
+    private RpcProviderService rpcProvider;
+    private Bundle bundle;
+    private String interfaceName;
+    private RpcService implementation;
+    private final List<ObjectRegistration<RpcService>> rpcRegistrations = new ArrayList<>();
+
+    public void setRpcProvider(final RpcProviderService rpcProvider) {
+        this.rpcProvider = rpcProvider;
+    }
+
+    public void setBundle(final Bundle bundle) {
+        this.bundle = bundle;
+    }
+
+    public void setInterfaceName(final String interfaceName) {
+        this.interfaceName = interfaceName;
+    }
+
+    public void setImplementation(final RpcService implementation) {
+        this.implementation = implementation;
+    }
+
+    @SuppressWarnings("checkstyle:IllegalCatch")
+    public void init() {
+        try {
+            List<Class<RpcService>> rpcInterfaces = getImplementedRpcServiceInterfaces(interfaceName,
+                    implementation.getClass(), bundle, RPC_IMPLEMENTATION);
+
+            LOG.debug("{}: init - adding implementation {} for RpcService interface(s) {}", bundle.getSymbolicName(),
+                    implementation, rpcInterfaces);
+
+            for (Class<RpcService> rpcInterface : rpcInterfaces) {
+                rpcRegistrations.add(rpcProvider.registerRpcImplementation(rpcInterface, implementation));
+            }
+        } catch (final ComponentDefinitionException e) {
+            throw e;
+        } catch (final Exception e) {
+            throw new ComponentDefinitionException(String.format(
+                    "Error processing \"%s\" for %s", RPC_IMPLEMENTATION, implementation.getClass()), e);
+        }
+    }
+
+    public void destroy() {
+        for (ObjectRegistration<RpcService> reg: rpcRegistrations) {
+            reg.close();
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    static List<Class<RpcService>> getImplementedRpcServiceInterfaces(final String interfaceName,
+            final Class<?> implementationClass, final Bundle bundle, final String logName)
+            throws ClassNotFoundException {
+        if (!Strings.isNullOrEmpty(interfaceName)) {
+            Class<?> rpcInterface = bundle.loadClass(interfaceName);
+
+            if (!rpcInterface.isAssignableFrom(implementationClass)) {
+                throw new ComponentDefinitionException(String.format(
+                        "The specified \"interface\" %s for \"%s\" is not implemented by RpcService \"ref\" %s",
+                        interfaceName, logName, implementationClass));
+            }
+
+            return Collections.singletonList((Class<RpcService>)rpcInterface);
+        }
+
+        List<Class<RpcService>> rpcInterfaces = new ArrayList<>();
+        for (Class<?> intface : implementationClass.getInterfaces()) {
+            if (RpcService.class.isAssignableFrom(intface)) {
+                rpcInterfaces.add((Class<RpcService>) intface);
+            }
+        }
+
+        if (rpcInterfaces.isEmpty()) {
+            throw new ComponentDefinitionException(String.format(
+                    "The \"ref\" instance %s for \"%s\" does not implemented any RpcService interfaces",
+                    implementationClass, logName));
+        }
+
+        return rpcInterfaces;
+    }
+}
diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/RpcServiceMetadata.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/RpcServiceMetadata.java
new file mode 100644 (file)
index 0000000..4ab3867
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2016 Brocade Communications 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.blueprint.ext;
+
+import java.util.function.Predicate;
+import org.opendaylight.mdsal.dom.spi.RpcRoutingStrategy;
+
+/**
+ * Factory metadata corresponding to the "rpc-service" element that gets an RPC service implementation from
+ * the RpcProviderRegistry and provides it to the Blueprint container.
+ *
+ * @author Thomas Pantelis
+ */
+final class RpcServiceMetadata extends AbstractInvokableServiceMetadata {
+    RpcServiceMetadata(final String id, final String interfaceName) {
+        super(id, interfaceName);
+    }
+
+    @Override
+    Predicate<RpcRoutingStrategy> rpcFilter() {
+        return s -> !s.isContextBasedRouted();
+    }
+}
diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/RpcUtil.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/RpcUtil.java
new file mode 100644 (file)
index 0000000..97b816d
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2017 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.blueprint.ext;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.function.Predicate;
+import org.opendaylight.mdsal.binding.spec.reflect.BindingReflections;
+import org.opendaylight.mdsal.dom.spi.RpcRoutingStrategy;
+import org.opendaylight.yangtools.yang.binding.RpcService;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Utility methods for dealing with various aspects of RPCs and actions.
+ *
+ * @author Robert Varga
+ */
+final class RpcUtil {
+    private static final Logger LOG = LoggerFactory.getLogger(RpcUtil.class);
+
+    private RpcUtil() {
+        throw new UnsupportedOperationException();
+    }
+
+    static Collection<SchemaPath> decomposeRpcService(final Class<RpcService> service,
+            final SchemaContext schemaContext, final Predicate<RpcRoutingStrategy> filter) {
+        final QNameModule moduleName = BindingReflections.getQNameModule(service);
+        final Module module = schemaContext.findModule(moduleName).orElseThrow(() -> new IllegalArgumentException(
+                "Module not found in SchemaContext: " + moduleName + "; service: " + service));
+        LOG.debug("Resolved service {} to module {}", service, module);
+
+        final Collection<RpcDefinition> rpcs = module.getRpcs();
+        final Collection<SchemaPath> ret = new ArrayList<>(rpcs.size());
+        for (RpcDefinition rpc : rpcs) {
+            final RpcRoutingStrategy strategy = RpcRoutingStrategy.from(rpc);
+            if (filter.test(strategy)) {
+                ret.add(rpc.getPath());
+            }
+        }
+
+        return ret;
+    }
+}
diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/SpecificReferenceListMetadata.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/SpecificReferenceListMetadata.java
new file mode 100644 (file)
index 0000000..e781ddd
--- /dev/null
@@ -0,0 +1,220 @@
+/*
+ * Copyright (c) 2016 Brocade Communications 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.blueprint.ext;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.Resources;
+import java.io.IOException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ConcurrentSkipListSet;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.blueprint.container.ComponentDefinitionException;
+import org.osgi.util.tracker.BundleTracker;
+import org.osgi.util.tracker.BundleTrackerCustomizer;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Factory metadata corresponding to the "specific-reference-list" element that obtains a specific list
+ * of service instances from the OSGi registry for a given interface. The specific list is learned by first
+ * extracting the list of expected service types by inspecting RESOLVED bundles for a resource file under
+ * META-INF/services with the same name as the given interface. The type(s) listed in the resource file
+ * must match the "type" property of the advertised service(s). In this manner, an app bundle announces the
+ * service type(s) that it will advertise so that this class knows which services to expect up front. Once
+ * all the expected services are obtained, the container is notified that all dependencies of this component
+ * factory are satisfied.
+ *
+ * @author Thomas Pantelis
+ */
+class SpecificReferenceListMetadata extends AbstractDependentComponentFactoryMetadata {
+    private static final Logger LOG = LoggerFactory.getLogger(SpecificReferenceListMetadata.class);
+
+    private final String interfaceName;
+    private final String serviceResourcePath;
+    private final Collection<String> expectedServiceTypes = new ConcurrentSkipListSet<>();
+    private final Collection<String> retrievedServiceTypes = new ConcurrentSkipListSet<>();
+    private final Collection<Object> retrievedServices = Collections.synchronizedList(new ArrayList<>());
+    private volatile BundleTracker<Bundle> bundleTracker;
+    private volatile ServiceTracker<Object, Object> serviceTracker;
+
+    SpecificReferenceListMetadata(final String id, final String interfaceName) {
+        super(id);
+        this.interfaceName = interfaceName;
+        serviceResourcePath = "META-INF/services/" + interfaceName;
+    }
+
+    @Override
+    protected void startTracking() {
+        BundleTrackerCustomizer<Bundle> bundleListener = new BundleTrackerCustomizer<Bundle>() {
+            @Override
+            public Bundle addingBundle(final Bundle bundle, final BundleEvent event) {
+                bundleAdded(bundle);
+                return bundle;
+            }
+
+            @Override
+            public void modifiedBundle(final Bundle bundle, final BundleEvent event, final Bundle object) {
+            }
+
+            @Override
+            public void removedBundle(final Bundle bundle, final BundleEvent event, final Bundle object) {
+            }
+        };
+
+        bundleTracker = new BundleTracker<>(container().getBundleContext(), Bundle.RESOLVED | Bundle.STARTING
+                | Bundle.STOPPING | Bundle.ACTIVE, bundleListener);
+
+        // This will get the list of all current RESOLVED+ bundles.
+        bundleTracker.open();
+
+        if (expectedServiceTypes.isEmpty()) {
+            setSatisfied();
+            return;
+        }
+
+        ServiceTrackerCustomizer<Object, Object> serviceListener = new ServiceTrackerCustomizer<Object, Object>() {
+            @Override
+            public Object addingService(final ServiceReference<Object> reference) {
+                return serviceAdded(reference);
+            }
+
+            @Override
+            public void modifiedService(final ServiceReference<Object> reference, final Object service) {
+            }
+
+            @Override
+            public void removedService(final ServiceReference<Object> reference, final Object service) {
+                container().getBundleContext().ungetService(reference);
+            }
+        };
+
+        setDependencyDesc(interfaceName + " services with types " + expectedServiceTypes);
+
+        serviceTracker = new ServiceTracker<>(container().getBundleContext(), interfaceName, serviceListener);
+        serviceTracker.open();
+    }
+
+    private void bundleAdded(final Bundle bundle) {
+        URL resource = bundle.getEntry(serviceResourcePath);
+        if (resource == null) {
+            return;
+        }
+
+        LOG.debug("{}: Found {} resource in bundle {}", logName(), resource, bundle.getSymbolicName());
+
+        try {
+            for (String line : Resources.readLines(resource, StandardCharsets.UTF_8)) {
+                int ci = line.indexOf('#');
+                if (ci >= 0) {
+                    line = line.substring(0, ci);
+                }
+
+                line = line.trim();
+                if (line.isEmpty()) {
+                    continue;
+                }
+
+                String serviceType = line;
+                LOG.debug("{}: Retrieved service type {}", logName(), serviceType);
+                expectedServiceTypes.add(serviceType);
+            }
+        } catch (final IOException e) {
+            setFailure(String.format("%s: Error reading resource %s from bundle %s", logName(), resource,
+                    bundle.getSymbolicName()), e);
+        }
+    }
+
+    private Object serviceAdded(final ServiceReference<Object> reference) {
+        Object service = container().getBundleContext().getService(reference);
+        String serviceType = (String) reference.getProperty(OpendaylightNamespaceHandler.TYPE_ATTR);
+
+        LOG.debug("{}: Service type {} added from bundle {}", logName(), serviceType,
+                reference.getBundle().getSymbolicName());
+
+        if (serviceType == null) {
+            LOG.error("{}: Missing OSGi service property '{}' for service interface {} in bundle {}", logName(),
+                    OpendaylightNamespaceHandler.TYPE_ATTR, interfaceName,  reference.getBundle().getSymbolicName());
+            return service;
+        }
+
+        if (!expectedServiceTypes.contains(serviceType)) {
+            LOG.error("{}: OSGi service property '{}' for service interface {} in bundle {} was not found in the "
+                    + "expected service types {} obtained via {} bundle resources. Is the bundle resource missing or "
+                    + "the service type misspelled?", logName(), OpendaylightNamespaceHandler.TYPE_ATTR, interfaceName,
+                    reference.getBundle().getSymbolicName(), expectedServiceTypes, serviceResourcePath);
+            return service;
+        }
+
+        // If already satisfied, meaning we got all initial services, then a new bundle must've been
+        // dynamically installed or a prior service's blueprint container was restarted, in which case we
+        // restart our container.
+        if (isSatisfied()) {
+            restartContainer();
+        } else {
+            retrievedServiceTypes.add(serviceType);
+            retrievedServices.add(service);
+
+            if (retrievedServiceTypes.equals(expectedServiceTypes)) {
+                LOG.debug("{}: Got all expected service types", logName());
+                setSatisfied();
+            } else {
+                Set<String> remaining = new HashSet<>(expectedServiceTypes);
+                remaining.removeAll(retrievedServiceTypes);
+                setDependencyDesc(interfaceName + " services with types " + remaining);
+            }
+        }
+
+        return service;
+    }
+
+    @Override
+    public Object create() throws ComponentDefinitionException {
+        LOG.debug("{}: In create: interfaceName: {}", logName(), interfaceName);
+
+        super.onCreate();
+
+        LOG.debug("{}: create returning service list {}", logName(), retrievedServices);
+
+        synchronized (retrievedServices) {
+            return ImmutableList.copyOf(retrievedServices);
+        }
+    }
+
+    @Override
+    public void destroy(final Object instance) {
+        super.destroy(instance);
+
+        if (bundleTracker != null) {
+            bundleTracker.close();
+            bundleTracker = null;
+        }
+
+        if (serviceTracker != null) {
+            serviceTracker.close();
+            serviceTracker = null;
+        }
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("SpecificReferenceListMetadata [interfaceName=").append(interfaceName)
+                .append(", serviceResourcePath=").append(serviceResourcePath).append("]");
+        return builder.toString();
+    }
+}
diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/StaticReferenceMetadata.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/StaticReferenceMetadata.java
new file mode 100644 (file)
index 0000000..97c04af
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2016 Brocade Communications 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.blueprint.ext;
+
+import org.osgi.service.blueprint.container.ComponentDefinitionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Factory metadata corresponding to the "static-reference" element that obtains an OSGi service and
+ * returns the actual instance. This differs from the standard "reference" element that returns a dynamic
+ * proxy whose underlying service instance can come and go.
+ *
+ * @author Thomas Pantelis
+ */
+class StaticReferenceMetadata extends AbstractDependentComponentFactoryMetadata {
+    private static final Logger LOG = LoggerFactory.getLogger(StaticReferenceMetadata.class);
+
+    private final String interfaceName;
+    private volatile Object retrievedService;
+
+    StaticReferenceMetadata(final String id, final String interfaceName) {
+        super(id);
+        this.interfaceName = interfaceName;
+    }
+
+    @Override
+    protected void startTracking() {
+        retrieveService(interfaceName, interfaceName, service -> {
+            retrievedService = service;
+            setSatisfied();
+        });
+    }
+
+    @Override
+    public Object create() throws ComponentDefinitionException {
+        super.onCreate();
+
+        LOG.debug("{}: create returning service {}", logName(), retrievedService);
+
+        return retrievedService;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("StaticReferenceMetadata [interfaceName=").append(interfaceName).append("]");
+        return builder.toString();
+    }
+}
diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/StaticServiceReferenceRecipe.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/StaticServiceReferenceRecipe.java
new file mode 100644 (file)
index 0000000..fdeea3b
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2016 Brocade Communications 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.blueprint.ext;
+
+import com.google.common.base.Preconditions;
+import java.util.Collections;
+import java.util.function.Consumer;
+import org.apache.aries.blueprint.container.AbstractServiceReferenceRecipe;
+import org.apache.aries.blueprint.services.ExtendedBlueprintContainer;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.blueprint.container.ComponentDefinitionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A Blueprint bean recipe for a static OSGi service reference, meaning it obtains the service instance once
+ * and doesn't react to service removal. In addition the returned object is the actual service instance and
+ * not a proxy.
+ *
+ * @author Thomas Pantelis
+ */
+class StaticServiceReferenceRecipe extends AbstractServiceReferenceRecipe {
+    private static final Logger LOG = LoggerFactory.getLogger(StaticServiceReferenceRecipe.class);
+
+    private static final SatisfactionListener NOOP_LISTENER = satisfiable -> {
+        // Intentional NOOP
+    };
+
+    private volatile ServiceReference<?> trackedServiceReference;
+    private volatile Object trackedService;
+    private Consumer<Object> serviceSatisfiedCallback;
+
+    StaticServiceReferenceRecipe(final String name, final ExtendedBlueprintContainer blueprintContainer,
+            final String interfaceClass) {
+        super(name, blueprintContainer, new MandatoryServiceReferenceMetadata(name, interfaceClass), null, null,
+                Collections.emptyList());
+    }
+
+    void startTracking(final Consumer<Object> newServiceSatisfiedCallback) {
+        this.serviceSatisfiedCallback = newServiceSatisfiedCallback;
+        super.start(NOOP_LISTENER);
+    }
+
+    @SuppressWarnings("rawtypes")
+    @Override
+    protected void track(final ServiceReference reference) {
+        retrack();
+    }
+
+    @SuppressWarnings("rawtypes")
+    @Override
+    protected void untrack(final ServiceReference reference) {
+        LOG.debug("{}: In untrack {}", getName(), reference);
+
+        if (trackedServiceReference == reference) {
+            LOG.debug("{}: Current reference {} has been untracked", getName(), trackedServiceReference);
+        }
+    }
+
+    @Override
+    protected void retrack() {
+        LOG.debug("{}: In retrack", getName());
+
+        if (trackedServiceReference == null) {
+            trackedServiceReference = getBestServiceReference();
+
+            LOG.debug("{}: getBestServiceReference: {}", getName(), trackedServiceReference);
+
+            if (trackedServiceReference != null && serviceSatisfiedCallback != null) {
+                serviceSatisfiedCallback.accept(internalCreate());
+            }
+        }
+    }
+
+    @Override
+    // Disables "Either log or rethrow this exception" sonar warning
+    @SuppressWarnings("squid:S1166")
+    protected void doStop() {
+        LOG.debug("{}: In doStop", getName());
+
+        if (trackedServiceReference != null && trackedService != null) {
+            try {
+                getBundleContextForServiceLookup().ungetService(trackedServiceReference);
+            } catch (final IllegalStateException e) {
+                // In case the service no longer exists, ignore.
+            }
+
+            trackedServiceReference = null;
+            trackedService = null;
+        }
+    }
+
+    @Override
+    protected Object internalCreate() throws ComponentDefinitionException {
+        ServiceReference<?> localTrackedServiceReference = trackedServiceReference;
+
+        LOG.debug("{}: In internalCreate: trackedServiceReference: {}", getName(), localTrackedServiceReference);
+
+        // being paranoid - internalCreate should only get called once
+        if (trackedService != null) {
+            return trackedService;
+        }
+
+        Preconditions.checkNotNull(localTrackedServiceReference, "trackedServiceReference is null");
+
+        trackedService = getServiceSecurely(localTrackedServiceReference);
+
+        LOG.debug("{}: Returning service instance: {}", getName(), trackedService);
+
+        Preconditions.checkNotNull(trackedService, "getService() returned null for %s", localTrackedServiceReference);
+
+        return trackedService;
+    }
+}
diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/UpdateStrategy.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/UpdateStrategy.java
new file mode 100644 (file)
index 0000000..2f83bc6
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2016 Ericsson India Global Services Pvt Ltd. 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.blueprint.ext;
+
+/**
+ * Enumerates possible strategies when a component is updated.
+ *
+ * @author Vishal Thapar
+ */
+public enum UpdateStrategy {
+    /*
+     * Restart container
+     */
+    RELOAD,
+    /*
+     * Don't do anything
+     */
+    NONE
+}
diff --git a/opendaylight/blueprint/src/main/resources/opendaylight-blueprint-ext-1.0.0.xsd b/opendaylight/blueprint/src/main/resources/opendaylight-blueprint-ext-1.0.0.xsd
new file mode 100644 (file)
index 0000000..5bd8ed0
--- /dev/null
@@ -0,0 +1,120 @@
+<xsd:schema xmlns="http://opendaylight.org/xmlns/blueprint/v1.0.0" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+    xmlns:bp="http://www.osgi.org/xmlns/blueprint/v1.0.0"
+    targetNamespace="http://opendaylight.org/xmlns/blueprint/v1.0.0" elementFormDefault="qualified"
+    attributeFormDefault="unqualified" version="1.0.0">
+
+  <xsd:import namespace="http://www.osgi.org/xmlns/blueprint/v1.0.0"/>
+
+  <xsd:attribute name="restart-dependents-on-updates" type="xsd:boolean"/>
+  <xsd:attribute name="use-default-for-reference-types" type="xsd:boolean"/>
+  <xsd:attribute name="type" type="xsd:string"/>
+
+  <!--
+       String representation of an instance identifier. Precise string format
+       is undefined just now.
+   -->
+  <xsd:simpleType name="Tpath">
+    <xsd:restriction base="xsd:string"/>
+  </xsd:simpleType>
+
+  <!--
+       A promise to instantiate actions of specified binding interface.
+       Specified interface must define at least one action. In case a bean
+       is specified, it will be used as the implementation of last resort
+       for actions not otherwise bound. If a bean is not specified, a blank
+       implementation will be substituted.
+   -->
+  <xsd:complexType name="TactionProvider">
+    <xsd:attribute name="interface" type="bp:Tclass" use="required"/>
+    <xsd:attribute name="ref" type="bp:Tidref" use="optional"/>
+  </xsd:complexType>
+  <xsd:element name="action-provider" type="TactionProvider"/>
+
+  <!--
+       A reference to an action-provider providing specified interface.
+       Specified interface must define at least one action.
+   -->
+  <xsd:complexType name="TactionService">
+    <xsd:attribute name="interface" type="bp:Tclass" use="required"/>
+    <xsd:attribute name="id" type="xsd:ID"/>
+  </xsd:complexType>
+  <xsd:element name="action-service" type="TactionService"/>
+
+  <!--
+       Implementation of an action at specific instance. This is used
+       to provide binding to actions on specific context, rather than
+       to a more general contract of action-provider.
+  <xsd:complexType name="TactionImplementation">
+    <xsd:attribute name="interface" type="bp:Tclass" use="optional"/>
+    <xsd:attribute name="ref" type="bp:Tidref" use="required"/>
+    <xsd:attribute name="path" type="Tpath" use="required"/>
+    <xsd:attribute name="id" type="xsd:ID"/>
+  </xsd:complexType>
+  <xsd:element name="action-implementation" type="TactionImplementation"/ -->
+
+  <!--
+       A reference to a specific action-implementation.
+  <xsd:complexType name="TactionInstance">
+    <xsd:attribute name="interface" type="bp:Tclass" use="required"/>
+    <xsd:attribute name="path" type="Tpath" use="required"/>
+    <xsd:attribute name="id" type="xsd:ID"/>
+  </xsd:complexType>
+  <xsd:element name="action-instance" type="TactionInstance"/ -->
+
+  <xsd:complexType name="TrpcImplementation">
+    <xsd:attribute name="interface" type="bp:Tclass" use="optional"/>
+    <xsd:attribute name="ref" type="bp:Tidref" use="required"/>
+  </xsd:complexType>
+  <xsd:element name="rpc-implementation" type="TrpcImplementation"/>
+
+  <!--
+       To be deprecated. This interface contract is fulfilled by
+       action-implementation instead
+   -->
+  <xsd:complexType name="TroutedRpcImplementation">
+    <xsd:attribute name="interface" type="bp:Tclass" use="optional"/>
+    <xsd:attribute name="ref" type="bp:Tidref" use="required"/>
+    <xsd:attribute name="id" type="xsd:ID"/>
+  </xsd:complexType>
+  <xsd:element name="routed-rpc-implementation" type="TroutedRpcImplementation"/>
+
+  <xsd:complexType name="TrpcService">
+    <xsd:attribute name="interface" type="bp:Tclass" use="required"/>
+    <xsd:attribute name="id" type="xsd:ID"/>
+  </xsd:complexType>
+  <xsd:element name="rpc-service" type="TrpcService"/>
+
+  <xsd:complexType name="TnotificationListener">
+    <xsd:attribute name="ref" type="bp:Tidref" use="required"/>
+  </xsd:complexType>
+  <xsd:element name="notification-listener" type="TnotificationListener"/>
+
+  <xsd:complexType name="TclusteredAppConfig">
+    <xsd:sequence>
+      <xsd:element name="default-config" type="xsd:string" minOccurs="0" maxOccurs="1"/>
+    </xsd:sequence>
+    <xsd:attribute name="binding-class" type="bp:Tclass" use="required"/>
+    <xsd:attribute name="list-key-value" type="xsd:string" use="optional"/>
+    <xsd:attribute name="default-config-file-name" type="xsd:string" use="optional"/>
+    <xsd:attribute name="id" type="xsd:ID" use="required"/>
+    <xsd:attribute name="update-strategy" type="TupdateStrategy" use="optional" default="reload"/>
+  </xsd:complexType>
+  <xsd:element name="clustered-app-config" type="TclusteredAppConfig"/>
+ <xsd:simpleType name="TupdateStrategy">
+  <xsd:restriction base="xsd:NMTOKEN">
+    <xsd:enumeration value="none"/>
+    <xsd:enumeration value="reload"/>
+  </xsd:restriction>
+ </xsd:simpleType>
+  <xsd:complexType name="TspecificReferenceList">
+    <xsd:attribute name="interface" type="bp:Tclass" use="required"/>
+    <xsd:attribute name="id" type="xsd:ID"/>
+  </xsd:complexType>
+  <xsd:element name="specific-reference-list" type="TspecificReferenceList"/>
+
+  <xsd:complexType name="TstaticReference">
+    <xsd:attribute name="interface" type="bp:Tclass" use="required"/>
+    <xsd:attribute name="id" type="xsd:ID"/>
+  </xsd:complexType>
+  <xsd:element name="static-reference" type="TstaticReference"/>
+</xsd:schema>
diff --git a/opendaylight/blueprint/src/test/java/org/opendaylight/controller/blueprint/tests/DataStoreAppConfigDefaultXMLReaderTest.java b/opendaylight/blueprint/src/test/java/org/opendaylight/controller/blueprint/tests/DataStoreAppConfigDefaultXMLReaderTest.java
new file mode 100644 (file)
index 0000000..46bd699
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2017 Red Hat, 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.blueprint.tests;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.opendaylight.controller.blueprint.ext.DataStoreAppConfigDefaultXMLReader;
+import org.opendaylight.controller.md.sal.binding.test.AbstractConcurrentDataBrokerTest;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.store.rev140422.Lists;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.store.rev140422.lists.unordered.container.UnorderedList;
+
+/**
+ * Example unit test using the {@link DataStoreAppConfigDefaultXMLReader}.
+ *
+ * @author Michael Vorburger.ch
+ */
+public class DataStoreAppConfigDefaultXMLReaderTest extends AbstractConcurrentDataBrokerTest {
+
+    @Test
+    public void testConfigXML() throws Exception {
+        Lists lists = new DataStoreAppConfigDefaultXMLReader<>(
+                getClass(), "/opendaylight-sal-test-store-config.xml",
+                getDataBrokerTestCustomizer().getSchemaService(),
+                getDataBrokerTestCustomizer().getBindingToNormalized(),
+                Lists.class).createDefaultInstance();
+
+        UnorderedList element = lists.getUnorderedContainer().getUnorderedList().get(0);
+        assertThat(element.getName()).isEqualTo("someName");
+        assertThat(element.getValue()).isEqualTo("someValue");
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testBadXMLName() throws Exception {
+        new DataStoreAppConfigDefaultXMLReader<>(
+                getClass(), "/badname.xml",
+                getDataBrokerTestCustomizer().getSchemaService(),
+                getDataBrokerTestCustomizer().getBindingToNormalized(),
+                Lists.class).createDefaultInstance();
+    }
+}
diff --git a/opendaylight/blueprint/src/test/resources/opendaylight-sal-test-store-config.xml b/opendaylight/blueprint/src/test/resources/opendaylight-sal-test-store-config.xml
new file mode 100644 (file)
index 0000000..b2744b2
--- /dev/null
@@ -0,0 +1,8 @@
+<lists xmlns="urn:opendaylight:params:xml:ns:yang:controller:md:sal:test:store">
+  <unordered-container>
+    <unordered-list>
+      <name>someName</name>
+      <value>someValue</value>
+    </unordered-list>
+  </unordered-container>
+</lists>