+
+ private void shutdownAllContainers() {
+ shuttingDown = true;
+
+ 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()) {
+ // 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(Collection<Bundle> containerBundles) {
+ List<Bundle> bundlesToDestroy = new ArrayList<Bundle>();
+
+ // 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()) {
+ Collections.sort(bundlesToDestroy, new Comparator<Bundle>() {
+ @Override
+ public int compare(Bundle b1, Bundle b2) {
+ return (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.
+
+ 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());
+ }
+
+ LOG.debug("Selected bundle {} for destroy (lowest ranking service or highest service ID)",
+ bundlesToDestroy);
+ }
+
+ 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;
+ }
+
+ 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());
+ }
+
+ 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);
+ }
+
+ return null;
+ }