Move BP xml files to standard OSGI-INF/blueprint
[controller.git] / opendaylight / blueprint / src / main / java / org / opendaylight / controller / blueprint / BlueprintBundleTracker.java
index ed416c5fb4481cc75d9f9a062262b237823e1083..b79d3662f966b36aca51fd6fee3ba106cb951bd4 100644 (file)
@@ -11,17 +11,18 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Comparator;
 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.opendaylight.controller.config.api.ConfigSystemService;
+import org.opendaylight.yangtools.util.xml.UntrustedXML;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
@@ -30,9 +31,8 @@ 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.EventConstants;
-import org.osgi.service.event.Event;
-import org.osgi.service.event.EventHandler;
+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;
@@ -48,17 +48,20 @@ import org.slf4j.LoggerFactory;
  *
  * @author Thomas Pantelis
  */
-public class BlueprintBundleTracker implements BundleActivator, BundleTrackerCustomizer<Bundle>, EventHandler,
+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 ODL_CUSTOM_BLUEPRINT_FILE_PATH = "org/opendaylight/blueprint/";
+    private static final String STANDARD_BLUEPRINT_FILE_PATH = "OSGI-INF/blueprint/";
     private static final String BLUEPRINT_FLE_PATTERN = "*.xml";
     private static final long SYSTEM_BUNDLE_ID = 0;
 
-    private ServiceTracker<BlueprintExtenderService, BlueprintExtenderService> serviceTracker;
+    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;
@@ -69,9 +72,14 @@ public class BlueprintBundleTracker implements BundleActivator, BundleTrackerCus
      * Implemented from BundleActivator.
      */
     @Override
-    public void start(BundleContext context) {
+    public void start(final BundleContext context) {
         LOG.info("Starting {}", getClass().getSimpleName());
 
+        // CONTROLLER-1867: force UntrustedXML initialization, so that it uses our TCCL to initialize
+        UntrustedXML.newDocumentBuilder();
+
+        restartService = new BlueprintContainerRestartServiceImpl();
+
         bundleContext = context;
 
         registerBlueprintEventHandler(context);
@@ -80,59 +88,93 @@ public class BlueprintBundleTracker implements BundleActivator, BundleTrackerCus
 
         bundleTracker = new BundleTracker<>(context, Bundle.ACTIVE, this);
 
-        serviceTracker = new ServiceTracker<>(context, BlueprintExtenderService.class.getName(),
+        blueprintExtenderServiceTracker = new ServiceTracker<>(context, BlueprintExtenderService.class.getName(),
                 new ServiceTrackerCustomizer<BlueprintExtenderService, BlueprintExtenderService>() {
                     @Override
                     public BlueprintExtenderService addingService(
-                            ServiceReference<BlueprintExtenderService> reference) {
-                        blueprintExtenderService = reference.getBundle().getBundleContext().getService(reference);
-                        bundleTracker.open();
-
-                        context.addBundleListener(BlueprintBundleTracker.this);
+                            final ServiceReference<BlueprintExtenderService> reference) {
+                        return onBlueprintExtenderServiceAdded(reference);
+                    }
 
-                        LOG.debug("Got BlueprintExtenderService");
+                    @Override
+                    public void modifiedService(final ServiceReference<BlueprintExtenderService> reference,
+                            final BlueprintExtenderService service) {
+                    }
 
-                        restartService = new BlueprintContainerRestartServiceImpl(blueprintExtenderService);
-                        blueprintContainerRestartReg = context.registerService(
-                                BlueprintContainerRestartService.class.getName(), restartService, new Hashtable<>());
+                    @Override
+                    public void removedService(final ServiceReference<BlueprintExtenderService> reference,
+                            final BlueprintExtenderService service) {
+                    }
+                });
+        blueprintExtenderServiceTracker.open();
 
-                        return blueprintExtenderService;
+        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(ServiceReference<BlueprintExtenderService> reference,
-                            BlueprintExtenderService service) {
+                    public void modifiedService(final ServiceReference<QuiesceParticipant> reference,
+                                                final QuiesceParticipant service) {
                     }
 
                     @Override
-                    public void removedService(ServiceReference<BlueprintExtenderService> reference,
-                            BlueprintExtenderService service) {
+                    public void removedService(final ServiceReference<QuiesceParticipant> reference,
+                                               final QuiesceParticipant service) {
                     }
                 });
-        serviceTracker.open();
+        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 void registerNamespaceHandler(BundleContext context) {
+    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(BundleContext context) {
-        Dictionary<String, Object> props = new Hashtable<>();
-        props.put(org.osgi.service.event.EventConstants.EVENT_TOPIC,
-                new String[]{EventConstants.TOPIC_CREATED, EventConstants.TOPIC_FAILURE});
-        eventHandlerReg = context.registerService(EventHandler.class.getName(), this, props);
+    private void registerBlueprintEventHandler(final BundleContext context) {
+        eventHandlerReg = context.registerService(BlueprintListener.class.getName(), this, new Hashtable<>());
     }
 
     /**
      * Implemented from BundleActivator.
      */
     @Override
-    public void stop(BundleContext context) {
+    public void stop(final BundleContext context) {
         bundleTracker.close();
-        serviceTracker.close();
+        blueprintExtenderServiceTracker.close();
+        quiesceParticipantTracker.close();
 
         AriesFrameworkUtil.safeUnregisterService(eventHandlerReg);
         AriesFrameworkUtil.safeUnregisterService(namespaceReg);
@@ -143,10 +185,10 @@ public class BlueprintBundleTracker implements BundleActivator, BundleTrackerCus
      * Implemented from SynchronousBundleListener.
      */
     @Override
-    public void bundleChanged(BundleEvent event) {
+    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) {
+        if (event.getBundle().getBundleId() == SYSTEM_BUNDLE_ID && event.getType() == BundleEvent.STOPPING) {
             shutdownAllContainers();
         }
     }
@@ -155,24 +197,24 @@ public class BlueprintBundleTracker implements BundleActivator, BundleTrackerCus
      * Implemented from BundleActivator.
      */
     @Override
-    public Bundle addingBundle(Bundle bundle, BundleEvent event) {
+    public Bundle addingBundle(final Bundle bundle, final BundleEvent event) {
         modifiedBundle(bundle, event, bundle);
         return bundle;
     }
 
     /**
-     * Implemented from BundleActivator.
+     * Implemented from BundleTrackerCustomizer.
      */
     @Override
-    public void modifiedBundle(Bundle bundle, BundleEvent event, Bundle object) {
-        if(shuttingDown) {
+    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 (bundle.getState() == Bundle.ACTIVE) {
+            List<Object> paths = findBlueprintPaths(bundle, ODL_CUSTOM_BLUEPRINT_FILE_PATH);
 
-            if(!paths.isEmpty()) {
+            if (!paths.isEmpty()) {
                 LOG.info("Creating blueprint container for bundle {} with paths {}", bundle, paths);
 
                 blueprintExtenderService.createContainer(bundle, paths);
@@ -181,49 +223,54 @@ public class BlueprintBundleTracker implements BundleActivator, BundleTrackerCus
     }
 
     /**
-     * Implemented from BundleActivator.
+     * Implemented from BundleTrackerCustomizer.
      */
     @Override
-    public void removedBundle(Bundle bundle, BundleEvent event, Bundle object) {
+    public void removedBundle(final Bundle bundle, final BundleEvent event, final Bundle object) {
         // BlueprintExtenderService will handle this.
     }
 
     /**
-     * Implemented from EventHandler to listen for blueprint events.
+     * Implemented from BlueprintListener to listen for blueprint events.
      *
-     * @param event
+     * @param event the event to handle
      */
     @Override
-    public void handleEvent(Event event) {
-        if(EventConstants.TOPIC_CREATED.equals(event.getTopic())) {
-            LOG.info("Blueprint container for bundle {} was successfully created",
-                    event.getProperty(EventConstants.BUNDLE));
-        } else if(EventConstants.TOPIC_FAILURE.equals(event.getTopic())) {
-            // 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.getProperty(EventConstants.DEPENDENCIES) != null) {
-                Bundle bundle = (Bundle) event.getProperty(EventConstants.BUNDLE);
-
-                List<Object> paths = findBlueprintPaths(bundle);
-                if(!paths.isEmpty()) {
-                    LOG.warn("Blueprint container for bundle {} timed out waiting for dependencies - restarting it",
-                            event.getProperty(EventConstants.BUNDLE));
-
-                    restartService.restartContainer(bundle, paths);
-                }
+    public void blueprintEvent(final 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);
             }
         }
     }
 
+    static List<Object> findBlueprintPaths(final Bundle bundle) {
+        List<Object> paths = findBlueprintPaths(bundle, STANDARD_BLUEPRINT_FILE_PATH);
+        return !paths.isEmpty() ? paths : findBlueprintPaths(bundle, ODL_CUSTOM_BLUEPRINT_FILE_PATH);
+    }
+
     @SuppressWarnings({ "rawtypes", "unchecked" })
-    static List<Object> findBlueprintPaths(Bundle bundle) {
-        Enumeration<?> e = bundle.findEntries(BLUEPRINT_FILE_PATH, BLUEPRINT_FLE_PATTERN, false);
-        if(e == null) {
+    private static List<Object> findBlueprintPaths(final Bundle bundle, final String path) {
+        Enumeration<?> rntries = bundle.findEntries(path, BLUEPRINT_FLE_PATTERN, false);
+        if (rntries == null) {
             return Collections.emptyList();
         } else {
-            return Collections.list((Enumeration)e);
+            return Collections.list((Enumeration)rntries);
         }
     }
 
@@ -232,22 +279,16 @@ public class BlueprintBundleTracker implements BundleActivator, BundleTrackerCus
 
         restartService.close();
 
-        // Close all CSS modules first.
-        ConfigSystemService configSystem = getOSGiService(ConfigSystemService.class);
-        if(configSystem != null) {
-            configSystem.closeAllConfigModules();
-        }
-
         LOG.info("Shutting down all blueprint containers...");
 
         Collection<Bundle> containerBundles = new HashSet<>(Arrays.asList(bundleContext.getBundles()));
-        while(!containerBundles.isEmpty()) {
+        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)) {
+            for (Bundle bundle : getBundlesToDestroy(containerBundles)) {
                 containerBundles.remove(bundle);
                 BlueprintContainer container = blueprintExtenderService.getContainer(bundle);
-                if(container != null) {
+                if (container != null) {
                     blueprintExtenderService.destroyContainer(bundle, container);
                 }
             }
@@ -256,33 +297,28 @@ public class BlueprintBundleTracker implements BundleActivator, BundleTrackerCus
         LOG.info("Shutdown of blueprint containers complete");
     }
 
-    private List<Bundle> getBundlesToDestroy(Collection<Bundle> containerBundles) {
+    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) {
+        for (Bundle bundle : containerBundles) {
             ServiceReference<?>[] references = bundle.getRegisteredServices();
             int usage = 0;
-            if(references != null) {
-                for(ServiceReference<?> reference : references) {
+            if (references != null) {
+                for (ServiceReference<?> reference : references) {
                     usage += getServiceUsage(reference);
                 }
             }
 
             LOG.debug("Usage for bundle {} is {}", bundle, usage);
-            if(usage == 0) {
+            if (usage == 0) {
                 bundlesToDestroy.add(bundle);
             }
         }
 
-        if(!bundlesToDestroy.isEmpty()) {
-            Collections.sort(bundlesToDestroy, new Comparator<Bundle>() {
-                @Override
-                public int compare(Bundle b1, Bundle b2) {
-                    return (int) (b2.getLastModified() - b1.getLastModified());
-                }
-            });
+        if (!bundlesToDestroy.isEmpty()) {
+            bundlesToDestroy.sort((b1, b2) -> (int) (b2.getLastModified() - b1.getLastModified()));
 
             LOG.debug("Selected bundles {} for destroy (no services in use)", bundlesToDestroy);
         } else {
@@ -294,32 +330,9 @@ public class BlueprintBundleTracker implements BundleActivator, BundleTrackerCus
             // 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.
 
-            ServiceReference<?> ref = null;
-            for(Bundle bundle : containerBundles) {
-                ServiceReference<?>[] references = bundle.getRegisteredServices();
-                if(references == null) {
-                    continue;
-                }
-
-                for(ServiceReference<?> reference : references) {
-                    // We did check the service usage above 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(ref == null || reference.compareTo(ref) < 0) {
-                        LOG.debug("Currently selecting bundle {} for destroy (with reference {})", bundle, reference);
-                        ref = reference;
-                    }
-                }
-            }
-
-            if(ref != null) {
-                bundlesToDestroy.add(ref.getBundle());
+            Bundle bundle = findBundleWithHighestUsedServiceId(containerBundles);
+            if (bundle != null) {
+                bundlesToDestroy.add(bundle);
             }
 
             LOG.debug("Selected bundle {} for destroy (lowest ranking service or highest service ID)",
@@ -329,32 +342,36 @@ public class BlueprintBundleTracker implements BundleActivator, BundleTrackerCus
         return bundlesToDestroy;
     }
 
-    private static int getServiceUsage(ServiceReference<?> ref) {
-        Bundle[] usingBundles = ref.getUsingBundles();
-        return usingBundles != null ? usingBundles.length : 0;
-    }
-
-    private <T> T getOSGiService(Class<T> serviceInterface) {
-        try {
-            ServiceReference<T> serviceReference = bundleContext.getServiceReference(serviceInterface);
-            if(serviceReference == null) {
-                LOG.warn("{} service reference not found", serviceInterface.getSimpleName());
-                return null;
+    @Nullable
+    private Bundle findBundleWithHighestUsedServiceId(final Collection<Bundle> containerBundles) {
+        ServiceReference<?> highestServiceRef = null;
+        for (Bundle bundle : containerBundles) {
+            ServiceReference<?>[] references = bundle.getRegisteredServices();
+            if (references == null) {
+                continue;
             }
 
-            T service = bundleContext.getService(serviceReference);
-            if(service == null) {
-                // This could happen on shutdown if the service was already unregistered so we log as debug.
-                LOG.debug("{} service instance was not found", serviceInterface.getSimpleName());
-            }
+            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;
+                }
 
-            return service;
-        } catch(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 OSGi service {}", serviceInterface.getSimpleName(), e);
+                // 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 null;
+        return highestServiceRef == null ? null : highestServiceRef.getBundle();
+    }
+
+    private static int getServiceUsage(final ServiceReference<?> ref) {
+        Bundle[] usingBundles = ref.getUsingBundles();
+        return usingBundles != null ? usingBundles.length : 0;
     }
 }