FELIX-3201 applied the patch
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1197558 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/dependencymanager/core/src/main/java/org/apache/felix/dm/ServiceDependency.java b/dependencymanager/core/src/main/java/org/apache/felix/dm/ServiceDependency.java
index 2f6fea6..be9f8d5 100644
--- a/dependencymanager/core/src/main/java/org/apache/felix/dm/ServiceDependency.java
+++ b/dependencymanager/core/src/main/java/org/apache/felix/dm/ServiceDependency.java
@@ -136,6 +136,20 @@
/**
* Sets the callbacks for this service. These callbacks can be used as hooks whenever a
+ * dependency is added, changed or removed. When you specify callbacks, the auto
+ * configuration feature is automatically turned off, because we're assuming you don't
+ * need it in this case.
+ * @param added the method to call when a service was added
+ * @param changed the method to call when a service was changed
+ * @param removed the method to call when a service was removed
+ * @param swapped the method to call when the service was swapped due to addition or
+ * removal of an aspect
+ * @return this service dependency
+ */
+ public ServiceDependency setCallbacks(String added, String changed, String removed, String swapped);
+
+ /**
+ * Sets the callbacks for this service. These callbacks can be used as hooks whenever a
* dependency is added or removed. They are called on the instance you provide. When you
* specify callbacks, the auto configuration feature is automatically turned off, because
* we're assuming you don't need it in this case.
@@ -162,6 +176,21 @@
public ServiceDependency setCallbacks(Object instance, String added, String changed, String removed);
/**
+ * Sets the callbacks for this service. These callbacks can be used as hooks whenever a
+ * dependency is added, changed or removed. When you specify callbacks, the auto
+ * configuration feature is automatically turned off, because we're assuming you don't
+ * need it in this case.
+ * @param instance the instance to call the callbacks on
+ * @param added the method to call when a service was added
+ * @param changed the method to call when a service was changed
+ * @param removed the method to call when a service was removed
+ * @param swapped the method to call when the service was swapped due to addition or
+ * removal of an aspect
+ * @return this service dependency
+ */
+ public ServiceDependency setCallbacks(Object instance, String added, String changed, String removed, String swapped);
+
+ /**
* Sets propagation of the service dependency properties to the provided service properties. Any additional
* service properties specified directly are merged with these.
*/
diff --git a/dependencymanager/core/src/main/java/org/apache/felix/dm/ServiceUtil.java b/dependencymanager/core/src/main/java/org/apache/felix/dm/ServiceUtil.java
index ef6cecb..f257bfe 100644
--- a/dependencymanager/core/src/main/java/org/apache/felix/dm/ServiceUtil.java
+++ b/dependencymanager/core/src/main/java/org/apache/felix/dm/ServiceUtil.java
@@ -39,11 +39,23 @@
* @return the ranking
*/
public static int getRanking(ServiceReference ref) {
+ return getRankingAsInteger(ref).intValue();
+ }
+
+ /**
+ * Returns the service ranking of a service, based on its service reference. If
+ * the service has a property specifying its ranking, that will be returned. If
+ * not, the default ranking of zero will be returned.
+ *
+ * @param ref the service reference to determine the ranking for
+ * @return the ranking
+ */
+ public static Integer getRankingAsInteger(ServiceReference ref) {
Integer rank = (Integer) ref.getProperty(Constants.SERVICE_RANKING);
if (rank != null) {
- return rank.intValue();
+ return rank;
}
- return 0;
+ return new Integer(0);
}
/**
@@ -55,7 +67,19 @@
* @return the service ID
*/
public static long getServiceId(ServiceReference ref) {
- return getServiceIdObject(ref).longValue();
+ return getServiceIdAsLong(ref).longValue();
+ }
+
+ /**
+ * Returns the service ID of a service, based on its service reference. This
+ * method is aware of service aspects as defined by the dependency manager and
+ * will return the ID of the orginal service if you give it an aspect.
+ *
+ * @param ref the service reference to determine the service ID of
+ * @return the service ID
+ */
+ public static Long getServiceIdAsLong(ServiceReference ref) {
+ return getServiceIdObject(ref);
}
public static Long getServiceIdObject(ServiceReference ref) {
diff --git a/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/dependencies/ServiceDependencyImpl.java b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/dependencies/ServiceDependencyImpl.java
index 6f98c76..4d2f769 100644
--- a/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/dependencies/ServiceDependencyImpl.java
+++ b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/dependencies/ServiceDependencyImpl.java
@@ -27,8 +27,10 @@
import java.util.Dictionary;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
@@ -38,6 +40,7 @@
import org.apache.felix.dm.DependencyService;
import org.apache.felix.dm.InvocationUtil;
import org.apache.felix.dm.ServiceDependency;
+import org.apache.felix.dm.ServiceUtil;
import org.apache.felix.dm.impl.DefaultNullObject;
import org.apache.felix.dm.impl.Logger;
import org.apache.felix.dm.tracker.ServiceTracker;
@@ -67,6 +70,7 @@
private String m_callbackAdded;
private String m_callbackChanged;
private String m_callbackRemoved;
+ private String m_callbackSwapped;
private boolean m_autoConfig;
protected ServiceReference m_reference;
protected Object m_serviceInstance;
@@ -79,6 +83,7 @@
private Object m_propagateCallbackInstance;
private String m_propagateCallbackMethod;
private final Map m_sr = new HashMap(); /* <DependencyService, Set<Tuple<ServiceReference, Object>> */
+ private Map m_componentByRank = new HashMap(); /* <Component, Map<Long, Map<Integer, Tuple>>> */
private static final Comparator COMPARATOR = new Comparator() {
public int getRank(ServiceReference ref) {
@@ -530,11 +535,114 @@
}
added = set.add(new Tuple(reference, service));
}
- if (added) {
- invoke(dependencyService, reference, service, m_callbackAdded);
+ if (added) {
+ // when a changed callback is specified we might not call the added callback just yet
+ if (m_callbackSwapped != null) {
+ handleAspectAwareAdded(dependencyService, reference, service);
+ } else {
+ invoke(dependencyService, reference, service, m_callbackAdded);
+ }
}
}
+ private void handleAspectAwareAdded(DependencyService dependencyService, ServiceReference reference, Object service) {
+ if (componentIsDependencyManagerFactory(dependencyService)) {
+ // component is either aspect or adapter factory instance, these must be ignored.
+ return;
+ }
+ boolean invokeAdded = false;
+ Integer ranking = ServiceUtil.getRankingAsInteger(reference);
+ Tuple highestRankedService = null;
+ synchronized (m_componentByRank) {
+ // TODO would be nicer if there was a ServiceUtil method for this...
+ Long originalServiceId = ServiceUtil.getServiceIdAsLong(reference);
+ Map componentMap = (Map) m_componentByRank.get(dependencyService); /* <Long, Map<Integer, Tuple>> */
+ if (componentMap == null) {
+ // create new componentMap
+ componentMap = new HashMap(); /* <Long, Map<Integer, Tuple>> */
+ m_componentByRank.put(dependencyService, componentMap);
+ }
+ Map rankings = (Map) componentMap.get(originalServiceId); /* <Integer, Tuple> */
+ if (rankings == null) {
+ // new component added
+ rankings = new HashMap(); /* <Integer, Tuple> */
+ componentMap.put(originalServiceId, rankings);
+ rankings.put(ranking, new Tuple(reference, service));
+ invokeAdded = true;
+ }
+
+ if (!invokeAdded) {
+ highestRankedService = swapHighestRankedService(dependencyService, originalServiceId, reference, service, ranking);
+ }
+ }
+ if (invokeAdded) {
+ invoke(dependencyService, reference, service, m_callbackAdded);
+ } else {
+ invokeSwappedCallback(dependencyService, highestRankedService.getServiceReference(), highestRankedService.getService(), reference, service);
+ }
+ }
+
+ private boolean componentIsDependencyManagerFactory(DependencyService dependencyService) {
+ // TODO review if we can be a bit smarter with these package name checks
+ return dependencyService.getService() != null && dependencyService.getService().getClass().getName().startsWith("org.apache.felix.dm")
+ && !dependencyService.getService().getClass().getName().startsWith("org.apache.felix.dm.test");
+ }
+
+ private Tuple swapHighestRankedService(DependencyService dependencyService, Long serviceId, ServiceReference newReference, Object newService, Integer newRanking) {
+ // does a component with a higher ranking exists
+ synchronized (m_componentByRank) {
+ Map componentMap = (Map) m_componentByRank.get(dependencyService); /* <Long, Map<Integer, Tuple>> */
+ Map rankings = (Map) componentMap.get(serviceId); /* <Integer, Tuple> */
+ Entry highestEntry = getHighestRankedService(dependencyService, serviceId); /* <Integer, Tuple> */
+ rankings.remove(highestEntry.getKey());
+ rankings.put(newRanking, new Tuple(newReference, newService));
+ return (Tuple) highestEntry.getValue();
+ }
+ }
+
+ private Entry getHighestRankedService(DependencyService dependencyService, Long serviceId) { /* <Integer, Tuple> */
+ Entry highestEntry = null; /* <Integer, Tuple> */
+ Map componentMap = (Map) m_componentByRank.get(dependencyService); /* <Long, Map<Integer, Tuple>> */
+ Map rankings = (Map) componentMap.get(serviceId); /* <Integer, Tuple> */
+ if (rankings != null) {
+ for (Iterator entryIterator = rankings.entrySet().iterator(); entryIterator.hasNext(); ) { /* <Integer, Tuple> */
+ Entry mapEntry = (Entry) entryIterator.next();
+ if (highestEntry == null) {
+ highestEntry = mapEntry;
+ } else {
+ if (((Integer)mapEntry.getKey()).intValue() > ((Integer)highestEntry.getKey()).intValue()) {
+ highestEntry = mapEntry;
+ }
+ }
+ }
+ }
+ return highestEntry;
+ }
+
+
+
+ private boolean isLastService(DependencyService dependencyService, ServiceReference reference, Object object, Long serviceId) {
+ // get the collection of rankings
+ Map componentMap = (Map) m_componentByRank.get(dependencyService); /* <Long, Map<Integer, Tuple>> */
+
+ Map rankings = null; /* <Integer, Tuple> */
+ if (componentMap != null) {
+ rankings = (Map) componentMap.get(serviceId);
+ }
+ // if there is only one element left in the collection of rankings
+ // and this last element has the same ranking as the supplied service (in other words, it is the same)
+ // then this is the last service
+ // NOTE: it is possible that there is only one element, but that it's not equal to the supplied service,
+ // because an aspect on top of the original service is being removed (but the original service is still
+ // there). That in turn triggers:
+ // 1) a call to added(original-service)
+ // 2) that causes a swap
+ // 3) a call to removed(aspect-service) <-- that's what we're talking about
+ return (componentMap != null && rankings != null && rankings.size() == 1 && ((Entry)rankings.entrySet().iterator().next()).getKey()
+ .equals(ServiceUtil.getRankingAsInteger(reference)));
+ }
+
+
public void invokeChanged(DependencyService dependencyService, ServiceReference reference, Object service) {
invoke(dependencyService, reference, service, m_callbackChanged);
}
@@ -546,9 +654,44 @@
removed = (set != null && set.remove(new Tuple(reference, service)));
}
if (removed) {
- invoke(dependencyService, reference, service, m_callbackRemoved);
+ if (m_callbackSwapped != null) {
+ handleAspectAwareRemoved(dependencyService, reference, service);
+ } else {
+ invoke(dependencyService, reference, service, m_callbackRemoved);
+ }
}
}
+
+ private void handleAspectAwareRemoved(DependencyService dependencyService, ServiceReference reference, Object service) {
+ if (componentIsDependencyManagerFactory(dependencyService)) {
+ // component is either aspect or adapter factory instance, these must be ignored.
+ return;
+ }
+ Long serviceId = ServiceUtil.getServiceIdAsLong(reference);
+ synchronized (m_componentByRank) {
+ if (isLastService(dependencyService, reference, service, serviceId)) {
+ invoke(dependencyService, reference, service, m_callbackRemoved);
+ }
+ Long originalServiceId = ServiceUtil.getServiceIdAsLong(reference);
+ Map componentMap = (Map) m_componentByRank.get(dependencyService); /* <Long, Map<Integer, Tuple>> */
+ if (componentMap != null) {
+ Map rankings = (Map) componentMap.get(originalServiceId); /* <Integer, Tuple> */
+ for (Iterator entryIterator = rankings.entrySet().iterator(); entryIterator.hasNext(); ) {
+ Entry mapEntry = (Entry) entryIterator.next();
+ if (((Tuple)mapEntry.getValue()).getServiceReference().equals(reference)) {
+ // remove the reference
+ rankings.remove(mapEntry.getKey());
+ }
+ }
+ if (rankings.size() == 0) {
+ componentMap.remove(originalServiceId);
+ }
+ if (componentMap.size() == 0) {
+ m_componentByRank.remove(dependencyService);
+ }
+ }
+ }
+ }
public void invoke(DependencyService dependencyService, ServiceReference reference, Object service, String name) {
if (name != null) {
@@ -564,6 +707,15 @@
);
}
}
+
+ private void invokeSwappedCallback(DependencyService component, ServiceReference previousReference, Object previous, ServiceReference currentServiceReference,
+ Object current) {
+ component.invokeCallbackMethod(getCallbackInstances(component), m_callbackSwapped, new Class[][] { { m_trackedServiceName, m_trackedServiceName },
+ { Object.class, Object.class }, { ServiceReference.class, m_trackedServiceName, ServiceReference.class, m_trackedServiceName },
+ { ServiceReference.class, Object.class, ServiceReference.class, Object.class } }, new Object[][] { { previous, current },
+ { previous, current }, { previousReference, previous, currentServiceReference, current },
+ { previousReference, previous, currentServiceReference, current } });
+ }
protected synchronized boolean makeAvailable() {
if (!isAvailable()) {
@@ -765,7 +917,7 @@
* @return this service dependency
*/
public synchronized ServiceDependency setCallbacks(String added, String removed) {
- return setCallbacks(null, added, null, removed);
+ return setCallbacks((Object) null, added, null, removed);
}
/**
@@ -780,7 +932,23 @@
* @return this service dependency
*/
public synchronized ServiceDependency setCallbacks(String added, String changed, String removed) {
- return setCallbacks(null, added, changed, removed);
+ return setCallbacks((Object) null, added, changed, removed);
+ }
+
+ /**
+ * Sets the callbacks for this service. These callbacks can be used as hooks whenever a
+ * dependency is added, changed or removed. When you specify callbacks, the auto
+ * configuration feature is automatically turned off, because we're assuming you don't
+ * need it in this case.
+ * @param added the method to call when a service was added
+ * @param changed the method to call when a service was changed
+ * @param removed the method to call when a service was removed
+ * @param swapped the method to call when the service was swapped due to addition or
+ * removal of an aspect
+ * @return this service dependency
+ */
+ public synchronized ServiceDependency setCallbacks(String added, String changed, String removed, String swapped) {
+ return setCallbacks((Object) null, added, changed, removed, swapped);
}
/**
@@ -795,7 +963,7 @@
* @return this service dependency
*/
public synchronized ServiceDependency setCallbacks(Object instance, String added, String removed) {
- return setCallbacks(instance, added, null, removed);
+ return setCallbacks(instance, added, (String) null, removed);
}
/**
@@ -811,17 +979,35 @@
* @return this service dependency
*/
public synchronized ServiceDependency setCallbacks(Object instance, String added, String changed, String removed) {
+ return setCallbacks(instance, added, changed, removed, null);
+ }
+
+ /**
+ * Sets the callbacks for this service. These callbacks can be used as hooks whenever a
+ * dependency is added, changed or removed. When you specify callbacks, the auto
+ * configuration feature is automatically turned off, because we're assuming you don't
+ * need it in this case.
+ * @param instance the instance to call the callbacks on
+ * @param added the method to call when a service was added
+ * @param changed the method to call when a service was changed
+ * @param removed the method to call when a service was removed
+ * @param swapped the method to call when the service was swapped due to addition or
+ * removal of an aspect
+ * @return this service dependency
+ */
+ public synchronized ServiceDependency setCallbacks(Object instance, String added, String changed, String removed, String swapped) {
ensureNotActive();
// if at least one valid callback is specified, we turn off auto configuration, unless
// someone already explicitly invoked autoConfig
- if ((added != null || removed != null || changed != null) && ! m_autoConfigInvoked) {
+ if ((added != null || removed != null || changed != null || swapped != null) && ! m_autoConfigInvoked) {
setAutoConfig(false);
}
- m_callbackInstance = instance;
+ m_callbackInstance = instance;
m_callbackAdded = added;
m_callbackChanged = changed;
- m_callbackRemoved = removed;
- return this;
+ m_callbackRemoved = removed;
+ m_callbackSwapped = swapped;
+ return this;
}
private void ensureNotActive() {
@@ -945,6 +1131,5 @@
m_propagateCallbackMethod = method;
return this;
}
-
- // TODO add equals and hashCode methods, so you can compare dependencies
+
}