/******************************************************************************
* 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);
}
}