+ return Collections.list((Enumeration)rntries);
+ }
+ }
+
+ 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<>();
+
+ // 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, (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.
+
+ 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);