--- /dev/null
+<?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>
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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();
+ }
+ }
+}
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+
+}
--- /dev/null
+/*
+ * 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));
+ }
+
+}
--- /dev/null
+/*
+ * 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;
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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());
+ }
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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 + ")";
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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
+}
--- /dev/null
+<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>
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+<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>