/****************************************************************************** * Copyright (c) 2006, 2010 VMware Inc., Oracle 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. * Oracle Inc. *****************************************************************************/ package org.eclipse.gemini.blueprint.extender.internal.dependencies.shutdown; import java.io.Serializable; import java.util.Comparator; 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.context.ConfigurableOsgiBundleApplicationContext; import org.eclipse.gemini.blueprint.service.exporter.OsgiServicePropertiesResolver; import org.eclipse.gemini.blueprint.util.OsgiServiceReferenceUtils; import org.springframework.util.ObjectUtils; /** * Null safe service-based dependency sorter for bundles. Sorts bundles based on their services using the following * algorithm: <p/> <ol> <li> if two bundles are connected (transitively) then the bundle that exports the service with * lowest ranking id, will be considered lower. </li> <li> if the ranks are equal, then the service id (which is * guaranteed to be unique) will be considered. </li> <li> if the bundles are not related then they will be sorted based * on their symbolic name. </li> </ol> * * @author Hal Hildebrand * @author Andy Piper * @author Costin Leau */ public class BundleDependencyComparator implements Comparator, Serializable { private static final long serialVersionUID = -108354908478230663L; private static final Log log = LogFactory.getLog(BundleDependencyComparator.class); /** * Simple method checking whether the given service reference points to a spring managed service or not. Checks for * * @param reference reference to the OSGi service * @return true if the service is spring managed, false otherwise */ public static boolean isSpringManagedService(ServiceReference reference) { if (reference == null) return false; return (reference.getProperty(OsgiServicePropertiesResolver.BEAN_NAME_PROPERTY_KEY) != null || reference.getProperty(OsgiServicePropertiesResolver.SPRING_DM_BEAN_NAME_PROPERTY_KEY) != null || reference.getProperty(ConfigurableOsgiBundleApplicationContext.APPLICATION_CONTEXT_SERVICE_PROPERTY_NAME) != null || reference.getProperty(ConfigurableOsgiBundleApplicationContext.SPRING_DM_APPLICATION_CONTEXT_SERVICE_PROPERTY_NAME) != null); } private static ServiceReference[] excludeNonSpringManagedServices(ServiceReference[] references) { if (ObjectUtils.isEmpty(references)) return references; int count = 0; for (int i = 0; i < references.length; i++) { if (!isSpringManagedService(references[i])) references[i] = null; else count++; } if (count == references.length) return references; ServiceReference[] refs = new ServiceReference[count]; int j = 0; for (int i = 0; i < references.length; i++) { if (references[i] != null) { refs[j] = references[i]; j++; } } return refs; } /** * Answer whether Bundle a is higher or lower depending on the ranking and id of its exported services. This is used * as a tie-breaker for circular references. */ protected static int compareUsingServiceRankingAndId(Bundle a, Bundle b) { ServiceReference[] aservices = excludeNonSpringManagedServices(a.getRegisteredServices()); ServiceReference[] bservices = excludeNonSpringManagedServices(b.getRegisteredServices()); boolean trace = log.isTraceEnabled(); // this case should not occur if (ObjectUtils.isEmpty(aservices) && ObjectUtils.isEmpty(bservices)) { if (trace) log.trace("both services have 0 services; sorting based on id"); return signum((int) (a.getBundleId() - b.getBundleId())); } else if (aservices == null) { return -1; } else if (bservices == null) { return 1; } // Look for the *lowest* highest ranked service in each bundle // i.e. take a look at each bundle, find the highest ranking service // and compare it to the other bundle // this means that the service with the highest ranking service will // be shutdown last int aRank = findHighestServiceRanking(aservices); int bRank = findHighestServiceRanking(bservices); // since we are looking for the minimum, invert the substraction // (the higher bundle is the one with the lowest rank) if (aRank != bRank) { int compare = -(bRank - aRank); if (trace) { int min = (compare > 0 ? (int) bRank : (int) aRank); log.trace("sorting based on lowest-service-ranking won by bundle" + (compare > 0 ? "1" : "2") + " w/ service id " + min); } return signum(-(bRank - aRank)); } // Look for the highest id in each bundle (i.e. started last). long aMaxId = findHighestServiceId(aservices); long bMaxId = findHighestServiceId(bservices); if (aMaxId != bMaxId) { int compare = (int) (bMaxId - aMaxId); if (trace) { int max = (compare > 0 ? (int) bMaxId : (int) aMaxId); log.trace("sorting based on highest-service-id won by bundle " + (compare > 0 ? "1" : "2") + " w/ service id " + max); } return signum(compare); } return signum((int) (a.getBundleId() - b.getBundleId())); } /** * Find the highest service ranking. This might come as unexpected however, since a bundle can export multiple * services, we have to find the minimum between the maximum services in each bundle - i.e. the bundle with the * highest service ranking will win. * * @param refs */ private static int findHighestServiceRanking(ServiceReference[] refs) { int maxRank = Integer.MIN_VALUE; for (int i = 0; i < refs.length; i++) { int currentRank = OsgiServiceReferenceUtils.getServiceRanking(refs[i]); if (currentRank > maxRank) maxRank = currentRank; } return maxRank; } private static long findHighestServiceId(ServiceReference[] refs) { long maxId = Long.MIN_VALUE; for (int i = 0; i < refs.length; i++) { long currentId = OsgiServiceReferenceUtils.getServiceId(refs[i]); if (currentId > maxId) maxId = currentId; } return maxId; } private static int signum(int a) { return a < 0 ? -1 : a == 0 ? 0 : 1; } public int compare(Object a, Object b) { return compareUsingServiceRankingAndId((Bundle) a, (Bundle) b); } }