FELIX-4305:

- Added toString() method in Tuple inner class. 
- Added "void invoke(Object[] callbackInstances, DependencyService dependencyService, ServiceReference reference, Object service, String name)" 
method, which can now be used by the AspectServiceImpl class.
- Added "void invokeSwappedCallback(...)" method, which can now be used by the AspectServiceImpl class.
- Use ds.autoConfig() and ds.propagate() methods instead of ds.dependencyChanged() method.
- Don't propagate added/changed/removed service properties if the events are not for us (see handleAspectAwareXXX methods).
- The invokeAdded/invokeChanged/invokeRemoved/handleAspectAwareXXX methods are now returning a boolean in order to indicate
if the callbacks have really been invoked, or have been ignored.



git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1551351 13f79535-47bb-0310-9956-ffa450edef68
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 14be11a..5ec6b37 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
@@ -139,7 +139,11 @@
         public int hashCode() {
             return m_serviceReference.hashCode();
         }
-    }
+        
+        public String toString() {
+            return "{" + m_serviceReference.getProperty(Constants.SERVICE_ID) + "=" + m_service + "}";
+        }  
+   }
 
     /**
      * Entry to wrap service properties behind a Map.
@@ -694,8 +698,51 @@
         m_propagateCallbackMethod = method;
         return this;
     }
+    
+    public void invoke(Object[] callbackInstances, DependencyService dependencyService, ServiceReference reference, Object service, String name) {
+        if (m_debug) {
+            m_logger.log(Logger.LOG_DEBUG, "[" + m_debugKey + "] invoke: " + name);
+        }
+        if (name != null) {
+            dependencyService.invokeCallbackMethod(callbackInstances, name, new Class[][]{
+                    {Component.class, ServiceReference.class, m_trackedServiceName},
+                    {Component.class, ServiceReference.class, Object.class}, {Component.class, ServiceReference.class},
+                    {Component.class, m_trackedServiceName}, {Component.class, Object.class}, {Component.class},
+                    {Component.class, Map.class, m_trackedServiceName}, {ServiceReference.class, m_trackedServiceName},
+                    {ServiceReference.class, Object.class}, {ServiceReference.class}, {m_trackedServiceName}, {Object.class}, {},
+                    {Map.class, m_trackedServiceName}}, new Object[][]{{dependencyService, reference, service},
+                    {dependencyService, reference, service}, {dependencyService, reference}, {dependencyService, service},
+                    {dependencyService, service}, {dependencyService}, {dependencyService, new ServicePropertiesMap(reference), service},
+                    {reference, service}, {reference, service}, {reference}, {service}, {service}, {},
+                    {new ServicePropertiesMap(reference), service}});
+        }
+    }
 
-    // --------------------------------------- Protected methods -------------------------------------------------------------------
+    public void invokeSwappedCallback(Object[] callbackInstances, DependencyService component, ServiceReference previousReference, Object previous,
+            ServiceReference currentServiceReference, Object current, String swapCallback) {
+        // sanity check on the service references
+        Integer oldRank = (Integer) previousReference.getProperty(Constants.SERVICE_RANKING);
+        Integer newRank = (Integer) currentServiceReference.getProperty(Constants.SERVICE_RANKING);
+
+        if (oldRank != null && newRank != null && oldRank.equals(newRank)) {
+            throw new IllegalStateException("Attempt to swap a service for a service with the same rank! previousReference: "
+                    + previousReference + ", currentReference: " + currentServiceReference);
+        }
+
+        component.invokeCallbackMethod(callbackInstances, swapCallback, 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},
+                {Component.class, m_trackedServiceName, m_trackedServiceName}, {Component.class, Object.class, Object.class},
+                {Component.class, ServiceReference.class, m_trackedServiceName, ServiceReference.class, m_trackedServiceName},
+                {Component.class, ServiceReference.class, Object.class, ServiceReference.class, Object.class}}, new Object[][]{
+                {previous, current}, {previous, current}, {previousReference, previous, currentServiceReference, current},
+                {previousReference, previous, currentServiceReference, current}, {component, previous, current},
+                {component, previous, current}, {component, previousReference, previous, currentServiceReference, current},
+                {component, previousReference, previous, currentServiceReference, current}});
+    }
+
+   // --------------------------------------- Protected methods -------------------------------------------------------------------
 
     protected synchronized boolean makeAvailable() {
         if (!isAvailable()) {
@@ -791,7 +838,7 @@
                     if (m_debug) {
                         m_logger.log(Logger.LOG_DEBUG, "[" + m_debugKey + "] invoke added: " + ref);       
                     }
-                    invokeAdded(ds, ref, service); //**
+                    invokeAdded(ds, ref, service);
                 }
                 // The dependency callback will be deferred until all required dependency are available.
                 if (m_debug) {
@@ -806,24 +853,28 @@
                     if (m_debug) {
                         m_logger.log(Logger.LOG_DEBUG, "[" + m_debugKey + "] invoke added: " + ref);
                     }
-                    invokeAdded(ds, ref, service); //**
+                    invokeAdded(ds, ref, service);
                 }
             } else {
                 if (m_debug) {
-                    m_logger.log(Logger.LOG_DEBUG, "[" + m_debugKey + "] dependency changed: " + ref);
+                    m_logger.log(Logger.LOG_DEBUG, "[" + m_debugKey + "] dependency added (was available): " + ref);
                 }
-                ds.dependencyChanged(this); //**
+                // First, inject the added service in autoconfig field, if any.
+                ds.autoConfig(this);
+                
                 // At this point, either the dependency is optional (meaning that the service has been started,
                 // because if not, then our dependency would not be active); or the dependency is required,
                 // meaning that either the service is not yet started, or already started.
                 // In all cases, we have to inject the required dependency.
-
                 // we only try to invoke the method here if we are really already instantiated
                 if (ds.isInstantiated() && ds.getCompositionInstances().length > 0) {
                     if (m_debug) {
                         m_logger.log(Logger.LOG_DEBUG, "[" + m_debugKey + "] invoke added: " + ref);
                     }
-                    invokeAdded(ds, ref, service); //**
+                    if (invokeAdded(ds, ref, service)) {
+                	// Propagate (if needed) all "setPropagate" dependencies to the dependency service.
+                	ds.propagate(this);
+                    }
                 }
             }
         }
@@ -836,16 +887,21 @@
         }
         for (int i = 0; i < services.length; i++) {
             DependencyService ds = (DependencyService) services[i];
-            ds.dependencyChanged(this);
+            ds.autoConfig(this);
             if (ds.isInstantiated()) {
-                invokeChanged(ds, ref, service);
+                if (invokeChanged(ds, ref, service)) {
+                    // The "change" or "swap" callback has been invoked (if not it means that the modified service 
+                    // is for a lower ranked aspect to which we are not interested in).
+                    // Now, propagate (if needed) changed properties to dependency service properties.
+                    ds.propagate(this);
+                }
             }
         }
     }
 
     private void doRemovedService(ServiceReference ref, Object service) {
         if (m_debug) {
-            m_logger.log(Logger.LOG_DEBUG, "[" + m_debugKey + "] removedservice: " + ref + ", rank: " + ref.getProperty("service.ranking"));
+            m_logger.log(Logger.LOG_DEBUG, "[" + m_debugKey + "] removedService: " + ref + ", rank: " + ref.getProperty("service.ranking"));
         }
         boolean makeUnavailable = makeUnavailable();
         if (m_debug) {
@@ -866,8 +922,17 @@
                     invokeRemoved(ds, ref, service);
                 }
             } else {
-                ds.dependencyChanged(this);
-                invokeRemoved(ds, ref, service);
+                // Some dependencies are still available: first inject the remaining highest ranked dependency
+                // in component class field, if the dependency is configured in autoconfig mode.
+                ds.autoConfig(this);
+                // Next, invoke "removed" callback. If the dependency is aspect aware, we only invoke removed cb
+                // if the removed service is the highest ranked service. Note that if the cb is not called, we don't
+                // propagate the remaining dependency properties.
+                if (invokeRemoved(ds, ref, service)) {
+                    // Finally, since we have lost one dependency, we have to possibly propagate the highest ranked 
+                    // dependency available.
+                    ds.propagate(this);
+                }
             }
         }
         // unget what we got in addingService (see ServiceTracker 701.4.1)
@@ -1021,7 +1086,11 @@
         return m_defaultImplementationInstance;
     }
 
-    private void invokeAdded(DependencyService dependencyService, ServiceReference reference, Object service) {
+    /**
+     * Invoke added or swap callback.
+     * @return true if one of the swap/added callbacks has been invoked, false if not, meaning that the added dependency is not the highest ranked one.
+     */
+    private boolean invokeAdded(DependencyService dependencyService, ServiceReference reference, Object service) {
         // We are already serialized.
         if (m_debug) {
             m_logger.log(Logger.LOG_DEBUG, "[" + m_debugKey + "] invoke added");
@@ -1038,18 +1107,31 @@
         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);
+                return handleAspectAwareAdded(dependencyService, reference, service);
             } else {
                 invoke(dependencyService, reference, service, m_callbackAdded);
+                return true;
             }
         }
+        return false;
     }
 
-    private void invokeChanged(DependencyService dependencyService, ServiceReference reference, Object service) {
+    private boolean invokeChanged(DependencyService dependencyService, ServiceReference reference, Object service) {
+        if (m_callbackSwapped != null) {
+            if (m_debug) {
+                m_logger.log(Logger.LOG_DEBUG, "[" + m_debugKey + "] handleAspectAwareChanged on " + service);
+            }
+            return handleAspectAwareChanged(dependencyService, reference, service);
+        } 
         invoke(dependencyService, reference, service, m_callbackChanged);
+        return true;
     }
 
-    private void invokeRemoved(DependencyService dependencyService, ServiceReference reference, Object service) {
+    /*
+     * Invoke the removed or the swap callback.
+     * @return true if the swap or the removed callback has  been called, false if not.
+     */
+    private boolean invokeRemoved(DependencyService dependencyService, ServiceReference reference, Object service) {
         if (m_debug) {
             m_logger.log(Logger.LOG_DEBUG, "[" + m_debugKey + "] invoke removed");
         }
@@ -1063,40 +1145,31 @@
         }
         if (removed) {
             if (m_callbackSwapped != null) {
-                handleAspectAwareRemoved(dependencyService, reference, service);
+                return handleAspectAwareRemoved(dependencyService, reference, service);
             } else {
                 invoke(dependencyService, reference, service, m_callbackRemoved);
+                return true;
             }
         }
+        return false;
     }
 
     private void invoke(DependencyService dependencyService, ServiceReference reference, Object service, String name) {
-        if (m_debug) {
-            m_logger.log(Logger.LOG_DEBUG, "[" + m_debugKey + "] invoke: " + name);
-        }
-        if (name != null) {
-            dependencyService.invokeCallbackMethod(getCallbackInstances(dependencyService), name, new Class[][]{
-                    {Component.class, ServiceReference.class, m_trackedServiceName},
-                    {Component.class, ServiceReference.class, Object.class}, {Component.class, ServiceReference.class},
-                    {Component.class, m_trackedServiceName}, {Component.class, Object.class}, {Component.class},
-                    {Component.class, Map.class, m_trackedServiceName}, {ServiceReference.class, m_trackedServiceName},
-                    {ServiceReference.class, Object.class}, {ServiceReference.class}, {m_trackedServiceName}, {Object.class}, {},
-                    {Map.class, m_trackedServiceName}}, new Object[][]{{dependencyService, reference, service},
-                    {dependencyService, reference, service}, {dependencyService, reference}, {dependencyService, service},
-                    {dependencyService, service}, {dependencyService}, {dependencyService, new ServicePropertiesMap(reference), service},
-                    {reference, service}, {reference, service}, {reference}, {service}, {service}, {},
-                    {new ServicePropertiesMap(reference), service}});
-        }
+        invoke(getCallbackInstances(dependencyService), dependencyService, reference, service, name);
     }
 
-    private void handleAspectAwareAdded(final DependencyService dependencyService, final ServiceReference reference, final Object service) {
+    /**
+     * Invoke added or swap callback for aspect aware service dependency.
+     * @return true if one of the swap/added callbacks has been invoked, false if not, meaning that the added dependency is not the highest ranked one.
+     */
+    private boolean handleAspectAwareAdded(final DependencyService dependencyService, final ServiceReference reference, final Object service) {
         // At this point, we are already serialized: no need to synchronized.
         if (m_debug) {
             m_logger.log(Logger.LOG_DEBUG, "[" + m_debugKey + "] aspectawareadded: " + reference.getProperty("service.ranking"));
         }
         if (componentIsDependencyManagerFactory(dependencyService)) {
             // component is either aspect or adapter factory instance, these must be ignored.
-            return;
+            return false;
         }
         boolean invokeAdded = false;
         boolean invokeSwapped = false;
@@ -1104,7 +1177,7 @@
         Tuple newHighestRankedService = null;
         Tuple prevHighestRankedService = null;
         Map rankings = null;
-        //synchronized (m_componentByRank) { // don't synchronize: we are serialized and we can now invoke callbacks
+
         Long originalServiceId = ServiceUtil.getServiceIdAsLong(reference);
         Map componentMap = (Map) m_componentByRank.get(dependencyService); /* <Long, Map<Integer, Tuple>> */
         if (componentMap == null) {
@@ -1156,6 +1229,7 @@
             }
             // We can safely invoke callback since we are already serialized.
             invoke(dependencyService, reference, service, m_callbackAdded);
+            return true;
         } else if (invokeSwapped) {
             if (m_debug) {
                 m_logger.log(Logger.LOG_DEBUG, "[" + m_debugKey + "] invoke swapped: " + newHighestRankedService.getServiceReference().getProperty("service.ranking") + " replacing " + 
@@ -1164,8 +1238,9 @@
             // We can safely invoke callback since we are already serialized.
             invokeSwappedCallback(dependencyService, prevHighestRankedService.getServiceReference(), prevHighestRankedService.getService(),
                     newHighestRankedService.getServiceReference(), newHighestRankedService.getService());
+            return true;
         }
-        //} 
+        return false;
     }
     
     private boolean componentIsDependencyManagerFactory(DependencyService dependencyService) {
@@ -1234,13 +1309,64 @@
                 .getKey().equals(ServiceUtil.getRankingAsInteger(reference)));
     }
 
-    private void handleAspectAwareRemoved(DependencyService dependencyService, ServiceReference reference, Object service) {
+    private boolean handleAspectAwareChanged(DependencyService dependencyService, ServiceReference reference, Object service) {
+        if (m_debug) {
+            m_logger.log(Logger.LOG_DEBUG, "[" + m_debugKey + "] aspectawareChanged: service.ranking=" + reference.getProperty("service.ranking"));
+        }
+        if (componentIsDependencyManagerFactory(dependencyService)) {
+            // component is either aspect or adapter factory instance, these must be ignored.
+            return false;
+        }
+        boolean invokeChanged = false;
+
+        // Determine current highest ranked service: we'll take into account the change event only if it
+        // comes from the highest ranked service.
+        Long serviceId = ServiceUtil.getServiceIdAsLong(reference);
+        Map componentMap = (Map) m_componentByRank.get(dependencyService); /* <Long, Map<Integer, Tuple>> */
+        if (componentMap != null) {
+            Map rankings = (Map) componentMap.get(serviceId); /* <Integer, Tuple> */
+            if (rankings != null) {
+                Entry highestEntry = getHighestRankedService(dependencyService, serviceId); /* <Integer, Tuple> */
+                if (m_debug) {
+                    m_logger.log(Logger.LOG_DEBUG, "[" + m_debugKey + "] highest service ref:" + highestEntry);
+                }
+                if (highestEntry == null) {
+                    invokeChanged = true;
+                } else {
+                    Tuple tuple = (Tuple) highestEntry.getValue();
+                    if (reference.equals(tuple.getServiceReference())) {
+                        // The changed service is the highest ranked service: we can handle the modification event.
+                        invokeChanged = true;
+                    }
+                } 
+            }
+        }
+
+        if (m_debug) {
+            m_logger.log(Logger.LOG_DEBUG, "[" + m_debugKey + "] invokeChanged=" + invokeChanged);
+        }
+        
+        if (invokeChanged) {
+            if (m_debug) {
+                m_logger.log(Logger.LOG_DEBUG, "[" + m_debugKey + "] invoke changed: ref ranking=" + reference.getProperty("service.ranking"));
+            }
+            invoke(dependencyService, reference, service, m_callbackChanged);
+            return true;
+        }
+        return false;
+    }
+
+    /*
+     * handles aspect aware removed service.
+     * @return true if the swap or the removed callback has  been called, false if not.
+     */
+   private boolean handleAspectAwareRemoved(DependencyService dependencyService, ServiceReference reference, Object service) {
         if (m_debug) {
             m_logger.log(Logger.LOG_DEBUG, "[" + m_debugKey + "] aspectawareremoved: " + reference.getProperty("service.ranking"));
         }
         if (componentIsDependencyManagerFactory(dependencyService)) {
             // component is either aspect or adapter factory instance, these must be ignored.
-            return;
+            return false;
         }
         // we might need to swap here too!
         boolean invokeRemoved = false;
@@ -1249,8 +1375,8 @@
         Tuple newHighestRankedService = null;
         boolean invokeSwapped = false;
         Map rankings = null;
-        // synchronized (m_componentByRank) { // don't synchronize: we are serialized and we invoke callbacks
         Long originalServiceId = ServiceUtil.getServiceIdAsLong(reference);
+
         if (isLastService(dependencyService, reference, service, serviceId)) {
             invokeRemoved = true;
         } else {
@@ -1301,6 +1427,7 @@
             }
             // We can safely invoke callback, since we are already serialized
             invoke(dependencyService, reference, service, m_callbackRemoved);
+            return true;
         } else if (invokeSwapped) {
             if (m_debug) {
                 m_logger.log(Logger.LOG_DEBUG, "[" + m_debugKey + "] invoke swapped: " + newHighestRankedService.getServiceReference().getProperty("service.ranking") + " replacing " + 
@@ -1309,32 +1436,15 @@
             // We can safely invoke callback, since we are already serialized
             invokeSwappedCallback(dependencyService, prevHighestRankedService.getServiceReference(), prevHighestRankedService.getService(),
                     newHighestRankedService.getServiceReference(), newHighestRankedService.getService());
+            return true;
         }
-        //}
+        
+        return false;
     }
 
     private void invokeSwappedCallback(DependencyService component, ServiceReference previousReference, Object previous,
             ServiceReference currentServiceReference, Object current) {
-        // sanity check on the service references
-        Integer oldRank = (Integer) previousReference.getProperty(Constants.SERVICE_RANKING);
-        Integer newRank = (Integer) currentServiceReference.getProperty(Constants.SERVICE_RANKING);
-
-        if (oldRank != null && newRank != null && oldRank.equals(newRank)) {
-            throw new IllegalStateException("Attempt to swap a service for a service with the same rank! previousReference: "
-                    + previousReference + ", currentReference: " + currentServiceReference);
-        }
-
-        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},
-                {Component.class, m_trackedServiceName, m_trackedServiceName}, {Component.class, Object.class, Object.class},
-                {Component.class, ServiceReference.class, m_trackedServiceName, ServiceReference.class, m_trackedServiceName},
-                {Component.class, ServiceReference.class, Object.class, ServiceReference.class, Object.class}}, new Object[][]{
-                {previous, current}, {previous, current}, {previousReference, previous, currentServiceReference, current},
-                {previousReference, previous, currentServiceReference, current}, {component, previous, current},
-                {component, previous, current}, {component, previousReference, previous, currentServiceReference, current},
-                {component, previousReference, previous, currentServiceReference, current}});
+        invokeSwappedCallback(getCallbackInstances(component), component, previousReference, previous, currentServiceReference, current, m_callbackSwapped);
     }
 
     private synchronized boolean makeUnavailable() {