You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by ma...@apache.org on 2009/12/01 09:37:11 UTC

svn commit: r885687 - in /felix/trunk/dependencymanager: core/src/main/java/org/apache/felix/dependencymanager/dependencies/ core/src/main/java/org/apache/felix/dependencymanager/impl/ test/src/test/java/org/apache/felix/dependencymanager/test/

Author: marrs
Date: Tue Dec  1 08:37:10 2009
New Revision: 885687

URL: http://svn.apache.org/viewvc?rev=885687&view=rev
Log:
adapted service, configuration and bundle dependencies to allow them to be added to more than one service so you can share them efficiently, added basic tests for those scenarios

Added:
    felix/trunk/dependencymanager/test/src/test/java/org/apache/felix/dependencymanager/test/SharingDependenciesWithMultipleServicesTest.java
Modified:
    felix/trunk/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/dependencies/BundleDependency.java
    felix/trunk/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/dependencies/ConfigurationDependency.java
    felix/trunk/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/dependencies/ServiceDependency.java
    felix/trunk/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/impl/ServiceImpl.java
    felix/trunk/dependencymanager/test/src/test/java/org/apache/felix/dependencymanager/test/BundleDependencyTest.java

Modified: felix/trunk/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/dependencies/BundleDependency.java
URL: http://svn.apache.org/viewvc/felix/trunk/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/dependencies/BundleDependency.java?rev=885687&r1=885686&r2=885687&view=diff
==============================================================================
--- felix/trunk/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/dependencies/BundleDependency.java (original)
+++ felix/trunk/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/dependencies/BundleDependency.java Tue Dec  1 08:37:10 2009
@@ -18,12 +18,13 @@
  */
 package org.apache.felix.dependencymanager.dependencies;
 
+import java.util.ArrayList;
 import java.util.Dictionary;
+import java.util.List;
 
 import org.apache.felix.dependencymanager.Dependency;
 import org.apache.felix.dependencymanager.DependencyService;
 import org.apache.felix.dependencymanager.impl.Logger;
-import org.apache.felix.dependencymanager.impl.ServiceImpl;
 import org.apache.felix.dependencymanager.management.ServiceComponentDependency;
 import org.apache.felix.dependencymanager.tracker.BundleTracker;
 import org.apache.felix.dependencymanager.tracker.BundleTrackerCustomizer;
@@ -38,7 +39,7 @@
 	private boolean m_isStarted;
 	private BundleTracker m_tracker;
 	private int m_stateMask = Bundle.INSTALLED | Bundle.RESOLVED | Bundle.ACTIVE;
-	private DependencyService m_service;
+	private List m_services = new ArrayList();
 	private boolean m_isAvailable;
 	
     private Object m_callbackInstance;
@@ -66,32 +67,47 @@
 
 
     public void start(DependencyService service) {
+        boolean needsStarting = false;
 		synchronized (this) {
-			if (m_isStarted) {
-				throw new IllegalStateException("Dependency was already started." + getName());
-			}
-			m_service = service;
-			m_tracker = new BundleTracker(m_context, m_stateMask, this);
-			m_isStarted = true;
+		    m_services.add(service);
+		    if (!m_isStarted) {
+    			m_tracker = new BundleTracker(m_context, m_stateMask, this);
+    			m_isStarted = true;
+    			needsStarting = true;
+		    }
+		}
+		if (needsStarting) {
+		    m_tracker.open();
 		}
-		m_tracker.open();
-		System.out.println("START BD " + m_tracker);
 	}
 
 	public void stop(DependencyService service) {
+	    boolean needsStopping = false;
         synchronized (this) {
-            if (!m_isStarted) {
-                throw new IllegalStateException("Dependency was not started.");
+            if (m_services.size() == 1 && m_services.contains(service)) {
+                m_isStarted = false;
+                needsStopping = true;
             }
-            m_isStarted = false;
         }
-        m_tracker.close();
-        m_tracker = null;
+        if (needsStopping) {
+            m_tracker.close();
+            m_tracker = null;
+            m_services.remove(service);
+        }            
 	}
 
 	public String getName() {
-		// TODO Auto-generated method stub
-		return null;
+        StringBuilder sb = new StringBuilder();
+        sb.append(m_bundleInstance.getSymbolicName());
+        sb.append(' ');
+        sb.append(m_bundleInstance.getVersion());
+        sb.append(' ');
+        sb.append(Integer.toString(m_stateMask, 2));
+        if (m_filter != null) {
+            sb.append(' ');
+            sb.append(m_filter.toString());
+        }
+        return sb.toString();
 	}
 
 	public int getState() {
@@ -100,12 +116,10 @@
 	}
 
 	public String getType() {
-		// TODO Auto-generated method stub
-		return null;
+		return "bundle";
 	}
 
 	public Object addingBundle(Bundle bundle, BundleEvent event) {
-		System.out.println("ADDING " + bundle + " " + event);
 		// if we don't like a bundle, we could reject it here by returning null
 		if (m_bundleId >= 0 && m_bundleId != bundle.getBundleId()) {
 			return null;
@@ -113,9 +127,7 @@
 		Filter filter = m_filter;
 		if (filter != null) {
 			Dictionary headers = bundle.getHeaders();
-//			System.out.println("HEADERS: " + headers);
 			if (!m_filter.match(headers)) {
-			    System.out.println("NO MATCH: " + bundle);
 				return null;
 			}
 		}
@@ -123,39 +135,49 @@
 	}
 	
 	public void addedBundle(Bundle bundle, BundleEvent event, Object object) {
-		System.out.println("ADDED " + bundle + " " + event);
-        if (makeAvailable()) {
-            m_service.dependencyAvailable(this);
-            if (!isRequired()) {
-                invokeAdded(bundle);
-            }
-        }
-        else {
-            m_service.dependencyChanged(this);
-            invokeAdded(bundle);
-        }
+	    boolean makeAvailable = makeAvailable();
+	    Object[] services = m_services.toArray();
+	    for (int i = 0; i < services.length; i++) {
+	        DependencyService ds = (DependencyService) services[i];
+	        if (makeAvailable) {
+	            ds.dependencyAvailable(this);
+	            if (!isRequired()) {
+	                invokeAdded(ds, bundle);
+	            }
+	        }
+	        else {
+	            ds.dependencyChanged(this);
+	            invokeAdded(ds, bundle);
+	        }
+	    }
 	}
 
 	public void modifiedBundle(Bundle bundle, BundleEvent event, Object object) {
-		System.out.println("MODIFIED " + bundle + " " + event);
-        m_service.dependencyChanged(this);
-        // only invoke the changed callback if the service itself is "active"
-        if (m_service.isRegistered()) {
-            invokeChanged(bundle);
+		Object[] services = m_services.toArray();
+        for (int i = 0; i < services.length; i++) {
+            DependencyService ds = (DependencyService) services[i];
+            ds.dependencyChanged(this);
+            if (ds.isRegistered()) {
+                invokeChanged(ds, bundle);
+            }
         }
 	}
 
 	public void removedBundle(Bundle bundle, BundleEvent event, Object object) {
-		System.out.println("REMOVED " + bundle + " " + event);
-        if (makeUnavailable()) {
-            m_service.dependencyUnavailable(this);
-            if (!isRequired()) {
-                invokeRemoved(bundle);
+		boolean makeUnavailable = makeUnavailable();
+        Object[] services = m_services.toArray();
+        for (int i = 0; i < services.length; i++) {
+            DependencyService ds = (DependencyService) services[i];
+            if (makeUnavailable) {
+                ds.dependencyUnavailable(this);
+                if (!isRequired()) {
+                    invokeRemoved(ds, bundle);
+                }
+            }
+            else {
+                ds.dependencyChanged(this);
+                invokeRemoved(ds, bundle);
             }
-        }
-        else {
-            m_service.dependencyChanged(this);
-            invokeRemoved(bundle);
         }
 	}
 	
@@ -170,18 +192,13 @@
     private synchronized boolean makeUnavailable() {
         if ((isAvailable()) && (m_tracker.getTrackingCount() == 0)) {
             m_isAvailable = false;
-
             return true;
         }
         return false;
     }
     
-    public void invokeAdded() {
-        invokeAdded(m_bundleInstance);
-    }
-    
-    public void invokeAdded(Bundle service) {
-        Object[] callbackInstances = getCallbackInstances();
+    public void invokeAdded(DependencyService dependencyService, Bundle service) {
+        Object[] callbackInstances = getCallbackInstances(dependencyService);
         if ((callbackInstances != null) && (m_callbackAdded != null)) {
             invokeCallbackMethod(callbackInstances, m_callbackAdded, 
                 new Class[][] {{Bundle.class}, {Object.class}, {}},
@@ -190,8 +207,8 @@
         }
     }
 
-    public void invokeChanged(Bundle service) {
-        Object[] callbackInstances = getCallbackInstances();
+    public void invokeChanged(DependencyService dependencyService, Bundle service) {
+        Object[] callbackInstances = getCallbackInstances(dependencyService);
         if ((callbackInstances != null) && (m_callbackChanged != null)) {
             invokeCallbackMethod(callbackInstances, m_callbackChanged, 
                 new Class[][] {{Bundle.class}, {Object.class}, {}},
@@ -200,12 +217,8 @@
         }
     }
     
-    public void invokeRemoved() {
-        invokeRemoved(m_bundleInstance);
-    }
-    
-    public void invokeRemoved(Bundle service) {
-        Object[] callbackInstances = getCallbackInstances();
+    public void invokeRemoved(DependencyService dependencyService, Bundle service) {
+        Object[] callbackInstances = getCallbackInstances(dependencyService);
         if ((callbackInstances != null) && (m_callbackRemoved != null)) {
             invokeCallbackMethod(callbackInstances, m_callbackRemoved,
               new Class[][] {{Bundle.class}, {Object.class}, {}},
@@ -213,17 +226,16 @@
             );
         }
     }
-    
-    private synchronized Object[] getCallbackInstances() {
-        Object[] callbackInstances = ((ServiceImpl) m_service).getCompositionInstances();
+
+    private synchronized Object[] getCallbackInstances(DependencyService dependencyService) {
         if (m_callbackInstance == null) {
-            return callbackInstances;
+            return dependencyService.getCompositionInstances();
+        }
+        else {
+            return new Object[] { m_callbackInstance };
         }
-        Object[] res = new Object[callbackInstances.length + 1];
-        res[0] = m_callbackInstance; //this could also be extended to an array...?
-        System.arraycopy(callbackInstances, 0, res, 1, callbackInstances.length);
-        return res;
     }
+    
     /**
      * Sets the callbacks for this service. These callbacks can be used as hooks whenever a
      * dependency is added or removed. When you specify callbacks, the auto configuration 

Modified: felix/trunk/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/dependencies/ConfigurationDependency.java
URL: http://svn.apache.org/viewvc/felix/trunk/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/dependencies/ConfigurationDependency.java?rev=885687&r1=885686&r2=885687&view=diff
==============================================================================
--- felix/trunk/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/dependencies/ConfigurationDependency.java (original)
+++ felix/trunk/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/dependencies/ConfigurationDependency.java Tue Dec  1 08:37:10 2009
@@ -20,8 +20,12 @@
 
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.util.ArrayList;
 import java.util.Dictionary;
+import java.util.HashSet;
+import java.util.List;
 import java.util.Properties;
+import java.util.Set;
 
 import org.apache.felix.dependencymanager.Dependency;
 import org.apache.felix.dependencymanager.DependencyService;
@@ -57,11 +61,13 @@
 	private BundleContext m_context;
 	private String m_pid;
 	private ServiceRegistration m_registration;
-	private volatile DependencyService m_service;
+    protected List m_services = new ArrayList();
 	private Dictionary m_settings;
 	private boolean m_propagate;
 	private final Logger m_logger;
     private String m_callback;
+    private boolean m_isStarted;
+	private final Set m_updateInvokedCache = new HashSet();
 	
 	public ConfigurationDependency(BundleContext context, Logger logger) {
 		m_context = context;
@@ -99,15 +105,34 @@
 	}
 	
 	public void start(DependencyService service) {
-		m_service = service;
-		Properties props = new Properties();
-		props.put(Constants.SERVICE_PID, m_pid);
-		m_registration = m_context.registerService(ManagedService.class.getName(), this, props);
+	    boolean needsStarting = false;
+	    synchronized (this) {
+	        m_services.add(service);
+	        if (!m_isStarted) {
+	            m_isStarted = true;
+                needsStarting = true;
+	        }
+	    }
+	    if (needsStarting) {
+	        Properties props = new Properties();
+	        props.put(Constants.SERVICE_PID, m_pid);
+	        m_registration = m_context.registerService(ManagedService.class.getName(), this, props);
+	    }
 	}
 
 	public void stop(DependencyService service) {
-		m_registration.unregister();
-		m_service = null;
+        boolean needsStopping = false;
+        synchronized (this) {
+            if (m_services.size() == 1 && m_services.contains(service)) {
+                m_isStarted = false;
+                needsStopping = true;
+            }
+        }
+        if (needsStopping) {
+            m_registration.unregister();
+            m_registration = null;
+            m_services.remove(service);
+        }
 	}
 
         public Dependency setCallback(String callback) {
@@ -116,36 +141,71 @@
 	}
 
 	public void updated(Dictionary settings) throws ConfigurationException {
-		// if non-null settings come in, we have to instantiate the service and
-		// apply these settings
-		m_service.initService();
-		Object service = m_service.getService();
-				
-		Dictionary oldSettings = null; 
+	    m_updateInvokedCache.clear();
+	    
+	    Dictionary oldSettings = null; 
+	    synchronized (this) {
+	        oldSettings = m_settings;
+	    }
+	    
+	    if (oldSettings == null && settings == null) {
+	        // CM has started but our configuration is not still present in the CM database: ignore
+	        return;
+	    }
+
+	    Object[] services = m_services.toArray();
+        for (int i = 0; i < services.length; i++) {
+            DependencyService ds = (DependencyService) services[i];
+            // if non-null settings come in, we have to instantiate the service and
+            // apply these settings
+            ds.initService();
+            Object service = ds.getService();
+
+            if (service != null) {
+                invokeUpdate(ds, service, settings);
+            }
+            else {
+                m_logger.log(Logger.LOG_ERROR, "Service " + ds + " with configuration dependency " + this + " could not be instantiated.");
+                return;
+            }
+        }
+
 		synchronized (this) {
-			oldSettings = m_settings;
-		}
-		
-		if (oldSettings == null && settings == null) {
-	       // CM has started but our configuration is not still present in the CM database: ignore
-	       return;
+			m_settings = settings;
 		}
 		
-        if (service != null) {
-          	String callback = (m_callback == null) ? "updated" : m_callback;
-      	  	Method m;
-			try {
-			  	m = service.getClass().getDeclaredMethod(callback, new Class[] { Dictionary.class });
-			  	m.setAccessible(true);
-			  	// if exception is thrown here, what does that mean for the
-			  	// state of this dependency? how smart do we want to be??
-			  	// it's okay like this, if the new settings contain errors, we
-			  	// remain in the state we were, assuming that any error causes
-			  	// the "old" configuration to stay in effect.
-			  	// CM will log any thrown exceptions.
-			  	m.invoke(service, new Object[] { settings });
-			} 
-      	  	catch (InvocationTargetException e) {
+        for (int i = 0; i < services.length; i++) {
+            DependencyService ds = (DependencyService) services[i];
+            // If these settings did not cause a configuration exception, we determine if they have 
+            // caused the dependency state to change
+            if ((oldSettings == null) && (settings != null)) {
+                ds.dependencyAvailable(this);
+            }
+            if ((oldSettings != null) && (settings == null)) {
+                ds.dependencyUnavailable(this);
+            }
+            if ((oldSettings != null) && (settings != null)) {
+                ds.dependencyChanged(this);
+            }
+        }
+	}
+
+    public void invokeUpdate(DependencyService ds, Object service, Dictionary settings) throws ConfigurationException {
+        if (m_updateInvokedCache.add(ds)) {
+            String callback = (m_callback == null) ? "updated" : m_callback;
+            Method m;
+            try {
+                m = service.getClass().getDeclaredMethod(callback, new Class[] { Dictionary.class });
+                m.setAccessible(true);
+                // if exception is thrown here, what does that mean for the
+                // state of this dependency? how smart do we want to be??
+                // it's okay like this, if the new settings contain errors, we
+                // remain in the state we were, assuming that any error causes
+                // the "old" configuration to stay in effect.
+                // CM will log any thrown exceptions.
+                m.invoke(service, new Object[] { settings });
+            } 
+            catch (InvocationTargetException e) {
                 // The component has thrown an exception during it's callback invocation.
                 if (e.getTargetException() instanceof ConfigurationException) {
                     // the callback threw an OSGi ConfigurationException: just re-throw it.
@@ -153,35 +213,15 @@
                 }
                 else {
                     // wrap the callback exception into a ConfigurationException.
-                    throw new ConfigurationException(null, "Service " + m_service + " with " + this.toString() + " could not be updated", e.getTargetException());
+                    throw new ConfigurationException(null, "Service " + ds + " with " + this.toString() + " could not be updated", e.getTargetException());
                 }
             }
             catch (Throwable t) {
                 // wrap any other exception as a ConfigurationException.
-                throw new ConfigurationException(null, "Service " + m_service + " with " + this.toString() + " could not be updated", t);
+                throw new ConfigurationException(null, "Service " + ds + " with " + this.toString() + " could not be updated", t);
             }
         }
-        else {
-            m_logger.log(Logger.LOG_ERROR, "Service " + m_service + " with configuration dependency " + this + " could not be instantiated.");
-            return;
-        }
-
-		// If these settings did not cause a configuration exception, we determine if they have 
-		// caused the dependency state to change
-		synchronized (this) {
-			m_settings = settings;
-		}
-
-		if ((oldSettings == null) && (settings != null)) {
-			m_service.dependencyAvailable(this);
-		}
-		if ((oldSettings != null) && (settings == null)) {
-			m_service.dependencyUnavailable(this);
-		}
-		if ((oldSettings != null) && (settings != null)) {
-			m_service.dependencyChanged(this);
-		}
-	}
+    }
 
 	/**
 	 * Sets the <code>service.pid</code> of the configuration you
@@ -205,7 +245,7 @@
 	}
 	
 	private void ensureNotActive() {
-	  	if (m_service != null) {
+	  	if (m_services != null && m_services.size() > 0) {
 	  	  throw new IllegalStateException("Cannot modify state while active.");
 	  	}
     }

Modified: felix/trunk/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/dependencies/ServiceDependency.java
URL: http://svn.apache.org/viewvc/felix/trunk/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/dependencies/ServiceDependency.java?rev=885687&r1=885686&r2=885687&view=diff
==============================================================================
--- felix/trunk/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/dependencies/ServiceDependency.java (original)
+++ felix/trunk/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/dependencies/ServiceDependency.java Tue Dec  1 08:37:10 2009
@@ -361,8 +361,7 @@
     public void stop(DependencyService service) {
         boolean needsStopping = false;
         synchronized (this) {
-            m_services.remove(service);
-            if (m_services.size() == 0) {
+            if (m_services.size() == 1 && m_services.contains(service)) {
                 m_isStarted = false;
                 needsStopping = true;
             }
@@ -370,6 +369,7 @@
         if (needsStopping) {
             m_tracker.close();
             m_tracker = null;
+            m_services.remove(service);
         }
     }
 
@@ -718,6 +718,7 @@
         StringBuilder sb = new StringBuilder();
         sb.append(m_trackedServiceName.getName());
         if (m_trackedServiceFilterUnmodified != null) {
+            sb.append(' ');
             sb.append(m_trackedServiceFilterUnmodified);
         }
         return sb.toString();

Modified: felix/trunk/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/impl/ServiceImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/impl/ServiceImpl.java?rev=885687&r1=885686&r2=885687&view=diff
==============================================================================
--- felix/trunk/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/impl/ServiceImpl.java (original)
+++ felix/trunk/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/impl/ServiceImpl.java Tue Dec  1 08:37:10 2009
@@ -48,6 +48,7 @@
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.cm.ConfigurationException;
 
 /**
  * Service implementation.
@@ -910,6 +911,16 @@
         else {
             instances = new Object[] { m_serviceInstance };
         }
+        // TODO remove this test code; there are definitely cases where some instances in this array can be
+        // null, but it's not always harmful (in fact it's not possible to determine that here), this also happens
+        // when you start tracking required dependencies... it's probably safe not to include these null's in the
+        // array in the first place
+//        for (int i = 0; i < instances.length; i++) {
+//            if (instances[i] == null) {
+//                System.out.println("GetCompositionInstances had a null instance at index " + i + " dumping stack:");
+//                Thread.dumpStack();
+//            }
+//        }
         return instances;
     }
 
@@ -947,12 +958,13 @@
                     else {
                         // for optional services, we do an "ad-hoc" lookup to inject the service if it is
                         // already available even though the tracker has not yet been started
+                        
                         // TODO !!! configureImplementation(sd.getInterface(), sd.lookupService(), sd.getAutoConfigName());
                     }
                 }
                 // for required dependencies, we invoke any callbacks here
                 if (bd.isRequired()) {
-                    bd.invokeAdded();
+                    bd.invokeAdded(this, bd.getBundle());
                 }
             }
             else if (dependency instanceof ResourceDependency) {
@@ -964,6 +976,7 @@
                     else {
                         // for optional services, we do an "ad-hoc" lookup to inject the service if it is
                         // already available even though the tracker has not yet been started
+                        
                         // TODO !!! configureImplementation(sd.getInterface(), sd.lookupService(), sd.getAutoConfigName());
                     }
                 }
@@ -972,6 +985,19 @@
                     bd.invokeAdded();
                 }
             }
+            else if (dependency instanceof ConfigurationDependency) {
+                ConfigurationDependency cd = (ConfigurationDependency) dependency;
+                // for configuration dependencies, we invoke updated
+                try {
+                    cd.invokeUpdate(this, this.getService(), cd.getConfiguration());
+                }
+                catch (ConfigurationException e) {
+                    // if this happens, it's definitely an inconsistency
+                    // when sharing configuration dependencies between services, all implementations
+                    // should accept the same configurations
+                    e.printStackTrace();
+                }
+            }
         }
     }
 

Modified: felix/trunk/dependencymanager/test/src/test/java/org/apache/felix/dependencymanager/test/BundleDependencyTest.java
URL: http://svn.apache.org/viewvc/felix/trunk/dependencymanager/test/src/test/java/org/apache/felix/dependencymanager/test/BundleDependencyTest.java?rev=885687&r1=885686&r2=885687&view=diff
==============================================================================
--- felix/trunk/dependencymanager/test/src/test/java/org/apache/felix/dependencymanager/test/BundleDependencyTest.java (original)
+++ felix/trunk/dependencymanager/test/src/test/java/org/apache/felix/dependencymanager/test/BundleDependencyTest.java Tue Dec  1 08:37:10 2009
@@ -55,7 +55,7 @@
     }
     
     static class Consumer {
-        private int m_count = 0;
+        private volatile int m_count = 0;
 
         public void add(Bundle b) {
             Assert.assertNotNull("bundle instance must not be null", b);
@@ -71,7 +71,7 @@
         }
         
         public void doubleCheck() {
-            Assert.assertTrue("all bundles we found should have been removed again", m_count == 0);
+            Assert.assertEquals("all bundles we found should have been removed again", 0, m_count);
         }
     }
     

Added: felix/trunk/dependencymanager/test/src/test/java/org/apache/felix/dependencymanager/test/SharingDependenciesWithMultipleServicesTest.java
URL: http://svn.apache.org/viewvc/felix/trunk/dependencymanager/test/src/test/java/org/apache/felix/dependencymanager/test/SharingDependenciesWithMultipleServicesTest.java?rev=885687&view=auto
==============================================================================
--- felix/trunk/dependencymanager/test/src/test/java/org/apache/felix/dependencymanager/test/SharingDependenciesWithMultipleServicesTest.java (added)
+++ felix/trunk/dependencymanager/test/src/test/java/org/apache/felix/dependencymanager/test/SharingDependenciesWithMultipleServicesTest.java Tue Dec  1 08:37:10 2009
@@ -0,0 +1,184 @@
+package org.apache.felix.dependencymanager.test;
+
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.options;
+import static org.ops4j.pax.exam.CoreOptions.provision;
+
+import java.util.Dictionary;
+import java.util.Properties;
+
+import org.apache.felix.dependencymanager.DependencyManager;
+import org.apache.felix.dependencymanager.Service;
+import org.apache.felix.dependencymanager.dependencies.BundleDependency;
+import org.apache.felix.dependencymanager.dependencies.ConfigurationDependency;
+import org.apache.felix.dependencymanager.dependencies.ServiceDependency;
+import org.apache.felix.dependencymanager.impl.Logger;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.Configuration;
+import org.ops4j.pax.exam.junit.JUnit4TestRunner;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
+
+@RunWith(JUnit4TestRunner.class)
+public class SharingDependenciesWithMultipleServicesTest {
+    @Configuration
+    public static Option[] configuration() {
+        return options(
+            provision(
+                mavenBundle().groupId("org.osgi").artifactId("org.osgi.compendium").version("4.2.0"),
+                mavenBundle().groupId("org.apache.felix").artifactId("org.apache.felix.dependencymanager").versionAsInProject(),
+                mavenBundle().groupId("org.apache.felix").artifactId("org.apache.felix.configadmin").version("1.2.4")
+            )
+        );
+    }    
+    
+    @Test
+    public void testShareServiceDependencyWithMultipleServices(BundleContext context) {
+        DependencyManager m = new DependencyManager(context, new Logger(context));
+        // helper class that ensures certain steps get executed in sequence
+        Ensure e = new Ensure();
+        // create a service provider and consumer
+        Service provider = m.createService().setImplementation(new ServiceProvider()).setInterface(ServiceInterface.class.getName(), null);
+        ServiceDependency dependency = m.createServiceDependency().setService(ServiceInterface.class).setRequired(true);
+        Service consumer1 = m.createService().setImplementation(new ServiceConsumer(e, 1)).add(dependency);
+        Service consumer2 = m.createService().setImplementation(new ServiceConsumer(e, 4)).add(dependency);
+        
+        m.add(provider);
+        m.add(consumer1);
+        e.waitForStep(3, 2000);
+        m.add(consumer2);
+        e.waitForStep(6, 2000);
+        m.remove(provider);
+        m.remove(consumer1);
+        m.remove(consumer2);
+    }
+    
+    @Test
+    public void testShareConfigurationDependencyWithMultipleServices(BundleContext context) {
+        DependencyManager m = new DependencyManager(context, new Logger(context));
+        // helper class that ensures certain steps get executed in sequence
+        Ensure e = new Ensure();
+        // create a service provider and consumer
+        Service provider = m.createService().setImplementation(new ConfigurationProvider(e)).add(m.createServiceDependency().setService(ConfigurationAdmin.class).setRequired(true));
+        ConfigurationDependency dependency = m.createConfigurationDependency().setPid("test");
+        Service consumer1 = m.createService().setImplementation(new ConfigurationConsumer(e, 2)).add(dependency);
+        Service consumer2 = m.createService().setImplementation(new ConfigurationConsumer(e, 3)).add(dependency);
+        
+        // add the configuration provider that should publish the configuration as step 1
+        m.add(provider);
+        // add the first consumer, and wait until its updated() method is invoked
+        m.add(consumer1);
+        e.waitForStep(2, 2000);
+        // add the second consumer, and wait until its updated() method is invoked
+        m.add(consumer2);
+        e.waitForStep(3, 2000);
+        // break down the test again
+        m.remove(provider);
+        m.remove(consumer1);
+        m.remove(consumer2);
+    }
+    
+    @Test
+    public void testShareBundleDependencyWithMultipleServices(BundleContext context) {
+        DependencyManager m = new DependencyManager(context, new Logger(context));
+        // helper class that ensures certain steps get executed in sequence
+        Ensure e = new Ensure();
+        // create a service provider and consumer
+        BundleDependency dependency = m.createBundleDependency().setFilter("(Bundle-SymbolicName=org.apache.felix.dependencymanager)").setRequired(true);
+        Service consumer1 = m.createService().setImplementation(new BundleConsumer(e, 1)).add(dependency);
+        Service consumer2 = m.createService().setImplementation(new BundleConsumer(e, 2)).add(dependency);
+        
+        m.add(consumer1);
+        e.waitForStep(1, 2000);
+        m.add(consumer2);
+        e.waitForStep(2, 2000);
+        m.remove(consumer2);
+        m.remove(consumer1);
+    }
+    
+    static interface ServiceInterface {
+        public void invoke(Runnable r);
+    }
+
+    static class ServiceProvider implements ServiceInterface {
+        public void invoke(Runnable r) {
+            r.run();
+        }
+    }
+    
+    static class ServiceConsumer implements Runnable {
+        private volatile ServiceInterface m_service;
+        private final Ensure m_ensure;
+        private int m_step;
+
+        public ServiceConsumer(Ensure e, int step) {
+            m_ensure = e;
+            m_step = step;
+        }
+
+        public void start() {
+            Thread t = new Thread(this);
+            t.start();
+        }
+
+        public void run() {
+            m_ensure.step(m_step);
+            m_service.invoke(new Runnable() { public void run() { m_ensure.step(m_step + 1); } });
+            m_ensure.step(m_step + 2);
+        }
+    }
+    
+    static class ConfigurationConsumer implements ManagedService {
+        private final Ensure m_ensure;
+        private int m_step;
+
+        public ConfigurationConsumer(Ensure e, int step) {
+            m_ensure = e;
+            m_step = step;
+        }
+
+        public void updated(Dictionary properties) throws ConfigurationException {
+            if (properties != null) {
+                m_ensure.step(m_step);
+            }
+        }
+    }
+    
+    static class ConfigurationProvider {
+        private final Ensure m_ensure;
+        private volatile ConfigurationAdmin m_configAdmin;
+        
+        public ConfigurationProvider(Ensure ensure) {
+            m_ensure = ensure;
+        }
+        
+        public void init() {
+            try {
+                org.osgi.service.cm.Configuration conf = m_configAdmin.getConfiguration("test", null);
+                conf.update(new Properties() {{ put("testkey", "testvalue"); }} );
+                m_ensure.step(1);
+            }
+            catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+    }
+    
+    static class BundleConsumer {
+        private final Ensure m_ensure;
+        private int m_step;
+
+        public BundleConsumer(Ensure e, int step) {
+            m_ensure = e;
+            m_step = step;
+        }
+        
+        public void start() {
+            m_ensure.step(m_step);
+        }
+    }
+}