/****************************************************************************** * Copyright (c) 2006, 2010 VMware Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Apache License v2.0 which accompanies this distribution. * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html and the Apache License v2.0 * is available at http://www.opensource.org/licenses/apache2.0.php. * You may elect to redistribute this code under either of these licenses. * * Contributors: * VMware Inc. *****************************************************************************/ package org.eclipse.gemini.blueprint.extender.internal.dependencies.shutdown; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.osgi.framework.Bundle; import org.osgi.framework.ServiceReference; import org.eclipse.gemini.blueprint.util.OsgiServiceReferenceUtils; import org.eclipse.gemini.blueprint.util.OsgiStringUtils; import org.springframework.util.ObjectUtils; /** * Utility for sorting out bundles during shutdown based on the OSGi 4.2 shutdown algorithm. Please see section 121.3.11 * in OSGi 4.2 release. Since sorting out the entire graph from the beginning is difficult (shutting down some bundles, * might allow others to be destroyed), this utility is meant to be called multiple times until the list is being * depleted. * * @author Costin Leau */ public abstract class ShutdownSorter { private static final Log log = LogFactory.getLog(ShutdownSorter.class); /** * Sorts the given bundles. The method extracts the bundles about to be destroyed from the given lists and returns * them to the user. Since shutting down a bundle can influence the destruction of the others, this method should be * called after all the returned bundles have been destroyed until the list is empty. * * @param managedBundles * @return */ public static Collection<Bundle> getBundles(Collection<Bundle> managedBundles) { List<Bundle> returned = null; try { // 1. eliminate unused bundles returned = unusedBundles(managedBundles); if (returned.isEmpty()) { // go to step 2, and pick the first bundle based on service properties returned = new ArrayList<Bundle>(1); returned.add(findBundleBasedOnServices(managedBundles)); } return returned; } finally { managedBundles.removeAll(returned); } } private static List<Bundle> unusedBundles(Collection<Bundle> unsorted) { List<Bundle> unused = new ArrayList<Bundle>(); boolean trace = log.isTraceEnabled(); for (Bundle bundle : unsorted) { String bundleToString = null; if (trace) { bundleToString = OsgiStringUtils.nullSafeSymbolicName(bundle); } ServiceReference[] services = bundle.getRegisteredServices(); if (ObjectUtils.isEmpty(services)) { if (trace) { log.trace("Bundle " + bundleToString + " has no registered services; added for shutdown"); } unused.add(bundle); } else { boolean unusedBundle = true; for (ServiceReference serviceReference : services) { Bundle[] usingBundles = serviceReference.getUsingBundles(); if (!ObjectUtils.isEmpty(usingBundles)) { if (trace) log.trace("Bundle " + bundleToString + " has registered services in use; postponing shutdown. The using bundles are " + Arrays.toString(usingBundles)); unusedBundle = false; break; } } if (unusedBundle) { if (trace) { log.trace("Bundle " + bundleToString + " has unused registered services; added for shutdown"); } unused.add(bundle); } } } Collections.sort(unused, ReverseBundleIdSorter.INSTANCE); return unused; } private static Bundle findBundleBasedOnServices(Collection<Bundle> managedBundles) { Bundle candidate = null; int ranking = 0; boolean tie = false; boolean trace = log.isTraceEnabled(); String bundleToString = null; for (Bundle bundle : managedBundles) { if (trace) { bundleToString = OsgiStringUtils.nullSafeSymbolicName(bundle); } int localRanking = getRegisteredServiceInUseLowestRanking(bundle); if (trace) { log.trace("Bundle " + bundleToString + " lowest ranking registered service is " + localRanking); } if (candidate == null) { candidate = bundle; ranking = localRanking; } else { if (localRanking < ranking) { candidate = bundle; tie = false; ranking = localRanking; } else if (localRanking == ranking) { tie = true; } } } // there's a tie, so search for the bundle with the highest service id if (tie) { if (trace) { log.trace("Ranking tie; Looking for the highest service id..."); } long serviceId = Long.MIN_VALUE; for (Bundle bundle : managedBundles) { if (trace) { bundleToString = OsgiStringUtils.nullSafeSymbolicName(bundle); } long localServiceId = getHighestServiceId(bundle); if (trace) { log.trace("Bundle " + bundleToString + " highest service id is " + localServiceId); } if (localServiceId > serviceId) { candidate = bundle; serviceId = localServiceId; } } if (trace) { log.trace("The bundle with the highest service id is " + OsgiStringUtils.nullSafeSymbolicName(candidate)); } } else { if (trace) { log.trace("No ranking tie. The bundle with the lowest ranking is " + OsgiStringUtils.nullSafeSymbolicName(candidate)); } } return candidate; } private static int getRegisteredServiceInUseLowestRanking(Bundle bundle) { ServiceReference[] services = bundle.getRegisteredServices(); int min = Integer.MAX_VALUE; if (!ObjectUtils.isEmpty(services)) { for (ServiceReference ref : services) { // make sure somebody is using the service if (!ObjectUtils.isEmpty(ref.getUsingBundles())) { int localRank = OsgiServiceReferenceUtils.getServiceRanking(ref); if (localRank < min) { min = localRank; } } } } return min; } private static long getHighestServiceId(Bundle bundle) { ServiceReference[] services = bundle.getRegisteredServices(); long max = Long.MIN_VALUE; if (!ObjectUtils.isEmpty(services)) { for (ServiceReference ref : services) { long id = OsgiServiceReferenceUtils.getServiceId(ref); if (id > max) { max = id; } } } return max; } static class ReverseBundleIdSorter implements Comparator<Bundle> { private static Comparator<Bundle> INSTANCE = new ReverseBundleIdSorter(); public int compare(Bundle o1, Bundle o2) { return (int) (o2.getBundleId() - o1.getBundleId()); } } }