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
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@885687 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/dependencies/BundleDependency.java b/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/dependencies/BundleDependency.java
index 88cf970..42f5cfb 100644
--- a/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/dependencies/BundleDependency.java
+++ b/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/dependencies/BundleDependency.java
@@ -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;
+ }
}
- m_tracker.open();
- System.out.println("START BD " + m_tracker);
+ if (needsStarting) {
+ m_tracker.open();
+ }
}
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 {
- m_service.dependencyChanged(this);
- invokeRemoved(bundle);
+ else {
+ ds.dependencyChanged(this);
+ invokeRemoved(ds, 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();
}
- 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;
+ else {
+ return new Object[] { m_callbackInstance };
+ }
}
+
/**
* 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
diff --git a/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/dependencies/ConfigurationDependency.java b/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/dependencies/ConfigurationDependency.java
index 7cefc08..116d13e 100644
--- a/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/dependencies/ConfigurationDependency.java
+++ b/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/dependencies/ConfigurationDependency.java
@@ -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;
+ m_settings = settings;
}
- if (oldSettings == null && settings == null) {
- // CM has started but our configuration is not still present in the CM database: ignore
- return;
- }
-
- 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.");
}
}
diff --git a/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/dependencies/ServiceDependency.java b/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/dependencies/ServiceDependency.java
index 618882d..8ccec84 100644
--- a/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/dependencies/ServiceDependency.java
+++ b/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/dependencies/ServiceDependency.java
@@ -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();
diff --git a/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/impl/ServiceImpl.java b/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/impl/ServiceImpl.java
index d853fcf..17c5100 100644
--- a/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/impl/ServiceImpl.java
+++ b/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/impl/ServiceImpl.java
@@ -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();
+ }
+ }
}
}
diff --git a/dependencymanager/test/src/test/java/org/apache/felix/dependencymanager/test/BundleDependencyTest.java b/dependencymanager/test/src/test/java/org/apache/felix/dependencymanager/test/BundleDependencyTest.java
index 923d990..508d0b1 100644
--- a/dependencymanager/test/src/test/java/org/apache/felix/dependencymanager/test/BundleDependencyTest.java
+++ b/dependencymanager/test/src/test/java/org/apache/felix/dependencymanager/test/BundleDependencyTest.java
@@ -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);
}
}
diff --git a/dependencymanager/test/src/test/java/org/apache/felix/dependencymanager/test/SharingDependenciesWithMultipleServicesTest.java b/dependencymanager/test/src/test/java/org/apache/felix/dependencymanager/test/SharingDependenciesWithMultipleServicesTest.java
new file mode 100644
index 0000000..147bcab
--- /dev/null
+++ b/dependencymanager/test/src/test/java/org/apache/felix/dependencymanager/test/SharingDependenciesWithMultipleServicesTest.java
@@ -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);
+ }
+ }
+}