*/
package org.opendaylight.controller.blueprint;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
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.List;
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.eclipse.jdt.annotation.Nullable;
+import org.gaul.modernizer_maven_annotations.SuppressModernizer;
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;
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;
*
* @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;
* 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);
bundleTracker = new BundleTracker<>(context, Bundle.ACTIVE, this);
- serviceTracker = new ServiceTracker<>(context, BlueprintExtenderService.class.getName(),
+ blueprintExtenderServiceTracker = new ServiceTracker<>(context, BlueprintExtenderService.class,
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,
+ 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();
+ }
+
+ @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD",
+ justification = "https://github.com/spotbugs/spotbugs/issues/811")
+ private QuiesceParticipant onQuiesceParticipantAdded(final ServiceReference<QuiesceParticipant> reference) {
+ quiesceParticipant = reference.getBundle().getBundleContext().getService(reference);
+
+ LOG.debug("Got QuiesceParticipant");
+
+ restartService.setQuiesceParticipant(quiesceParticipant);
+
+ return quiesceParticipant;
+ }
+
+ @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD",
+ justification = "https://github.com/spotbugs/spotbugs/issues/811")
+ 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,
+ restartService, null);
+
+ return blueprintExtenderService;
}
- private void registerNamespaceHandler(BundleContext context) {
- Dictionary<String, Object> props = new Hashtable<>();
+ private void registerNamespaceHandler(final BundleContext context) {
+ Dictionary<String, Object> props = emptyDict();
props.put("osgi.service.blueprint.namespace", OpendaylightNamespaceHandler.NAMESPACE_1_0_0);
- namespaceReg = context.registerService(NamespaceHandler.class.getName(),
- new OpendaylightNamespaceHandler(), props);
+ namespaceReg = context.registerService(NamespaceHandler.class, new OpendaylightNamespaceHandler(), props);
+ }
+
+ private void registerBlueprintEventHandler(final BundleContext context) {
+ eventHandlerReg = context.registerService(BlueprintListener.class, this, null);
}
- 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);
+ @SuppressModernizer
+ private static Dictionary<String, Object> emptyDict() {
+ return 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);
* 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();
}
}
* 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);
}
/**
- * 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);
}
}
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);
}
}
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 {
// 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)",
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;
+ private @Nullable 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;
}
}