moved new dm4 from sandbox to trunk.
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1663056 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AbstractServiceDependencyTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AbstractServiceDependencyTest.java
new file mode 100644
index 0000000..fb0bbe0
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AbstractServiceDependencyTest.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import org.junit.Assert;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class AbstractServiceDependencyTest extends TestBase {
+ public void testAbstractClassDependency() {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ // create a service provider and consumer
+ Component sp = m.createComponent()
+ .setInterface(ServiceAbstract.class.getName(), null)
+ .setImplementation(new ServiceProvider(e))
+ ;
+ Component sc = m.createComponent()
+ .setImplementation(new ServiceConsumer(e))
+ .add(m.createServiceDependency()
+ .setService(ServiceAbstract.class)
+ .setRequired(true)
+ .setCallbacks("bind", "unbind")
+ );
+ m.add(sp);
+ m.add(sc);
+ m.remove(sp);
+ // ensure we executed all steps inside the component instance
+ e.step(8);
+ m.clear();
+ }
+
+ static abstract class ServiceAbstract {
+ public abstract void invoke();
+ }
+
+ static class ServiceProvider extends ServiceAbstract {
+ private final Ensure m_ensure;
+ public ServiceProvider(Ensure e) {
+ m_ensure = e;
+ }
+
+ public void start() {
+ m_ensure.step(1);
+ }
+
+ public void invoke() {
+ m_ensure.step(4);
+ }
+
+ public void stop() {
+ m_ensure.step(7);
+ }
+ }
+
+ static class ServiceConsumer {
+ private volatile ServiceAbstract m_service;
+ private final Ensure m_ensure;
+
+ public ServiceConsumer(Ensure e) {
+ m_ensure = e;
+ }
+
+ public void bind(ServiceAbstract service) {
+ m_ensure.step(2);
+ m_service = service;
+ }
+
+ public void start() {
+ m_ensure.step(3);
+ m_service.invoke();
+ }
+
+ public void stop() {
+ m_ensure.step(5);
+ }
+
+ public void unbind(ServiceAbstract service) {
+ System.out.println("UNBINDDDDDDDDDDDDDDDDDDDDDDDDDDD");
+ Assert.assertEquals(m_service, service);
+ m_ensure.step(6);
+ }
+ }
+}
\ No newline at end of file
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AdapterAndConsumerTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AdapterAndConsumerTest.java
new file mode 100644
index 0000000..93949ee
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AdapterAndConsumerTest.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class AdapterAndConsumerTest extends TestBase {
+
+ public void testServiceWithAdapterAndConsumer() {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+
+ Component provider = m.createComponent()
+ .setInterface(OriginalService.class.getName(), null)
+ .setImplementation(new ServiceProvider(e));
+
+ Component consumer = m.createComponent()
+ .setImplementation(new ServiceConsumer(e))
+ .add(m.createServiceDependency()
+ .setService(AdaptedService.class)
+ .setRequired(true)
+ );
+
+ Component adapter = m.createAdapterService(OriginalService.class, null)
+ .setInterface(AdaptedService.class.getName(), null)
+ .setImplementation(ServiceAdapter.class);
+
+ // add the provider and the adapter
+ m.add(provider);
+ m.add(adapter);
+ // add a consumer that will invoke the adapter
+ // which will in turn invoke the original provider
+ m.add(consumer);
+ // now validate that both have been invoked in the right order
+ e.waitForStep(2, 5000);
+ // remove the provider again
+ m.remove(provider);
+ // ensure that the consumer is stopped
+ e.waitForStep(3, 5000);
+ // remove adapter and consumer
+ m.remove(adapter);
+ m.remove(consumer);
+ }
+
+ static interface OriginalService {
+ public void invoke();
+ }
+
+ static interface AdaptedService {
+ public void invoke();
+ }
+
+ static class ServiceProvider implements OriginalService {
+ private final Ensure m_ensure;
+ public ServiceProvider(Ensure e) {
+ m_ensure = e;
+ }
+ public void invoke() {
+ m_ensure.step(2);
+ }
+ }
+
+ public static class ServiceAdapter implements AdaptedService {
+ private volatile OriginalService m_originalService;
+
+ public void start() { System.out.println("start"); }
+ public void stop() { System.out.println("stop"); }
+ public void invoke() {
+ m_originalService.invoke();
+ }
+ }
+
+ static class ServiceConsumer {
+ private volatile AdaptedService m_service;
+ private final Ensure m_ensure;
+
+ public ServiceConsumer(Ensure e) {
+ m_ensure = e;
+ }
+ public void start() {
+ m_ensure.step(1);
+ m_service.invoke();
+ }
+ public void stop() {
+ m_ensure.step(3);
+ }
+ }
+}
+
+
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AdapterWithCallbackInstanceTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AdapterWithCallbackInstanceTest.java
new file mode 100644
index 0000000..a7073f5
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AdapterWithCallbackInstanceTest.java
@@ -0,0 +1,161 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.junit.Assert;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+import org.osgi.framework.ServiceRegistration;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class AdapterWithCallbackInstanceTest extends TestBase {
+
+ public void testServiceWithAdapterAndConsumer() {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+
+ ServiceProvider serviceProvider = new ServiceProvider(e);
+ Component provider = m.createComponent()
+ .setInterface(OriginalService.class.getName(), null)
+ .setImplementation(serviceProvider);
+
+ Component consumer = m.createComponent()
+ .setImplementation(new ServiceConsumer(e))
+ .add(m.createServiceDependency()
+ .setService(AdaptedService.class)
+ .setRequired(true)
+ );
+
+ ServiceAdapterCallbackInstance callbackInstance = new ServiceAdapterCallbackInstance(e);
+ Component adapter = m.createAdapterService(OriginalService.class, null, "m_originalService",
+ callbackInstance, "set", "changed","unset", null, true)
+ .setInterface(AdaptedService.class.getName(), null)
+ .setImplementation(new ServiceAdapter(e));
+
+ // add the provider and the adapter
+ m.add(provider);
+ m.add(adapter);
+ // Checks if the callbackInstances is called, and if the adapter start method is called
+ e.waitForStep(2, 5000);
+
+ // add a consumer that will invoke the adapter
+ // which will in turn invoke the original provider
+ m.add(consumer);
+ // now validate that both have been invoked in the right order
+ e.waitForStep(4, 5000);
+
+ // change the service properties of the provider, and check that the adapter callback instance is changed.
+ serviceProvider.changeServiceProperties();
+ e.waitForStep(5, 5000);
+
+ // remove the provider
+ m.remove(provider);
+ // ensure that the consumer is stopped, the adapter callback is called in its unset method, and the adapter is stopped.
+ e.waitForStep(8, 5000);
+ // remove adapter and consumer
+ m.remove(adapter);
+ m.remove(consumer);
+ }
+
+ static interface OriginalService {
+ public void invoke();
+ }
+
+ static interface AdaptedService {
+ public void invoke();
+ }
+
+ static class ServiceProvider implements OriginalService {
+ private final Ensure m_ensure;
+ private volatile ServiceRegistration m_registration; // auto injected when started.
+ public ServiceProvider(Ensure e) {
+ m_ensure = e;
+ }
+ public void changeServiceProperties() {
+ Hashtable<String, String> props = new Hashtable<>();
+ props.put("foo", "bar");
+ m_registration.setProperties(props);
+ }
+ public void invoke() {
+ m_ensure.step(4);
+ }
+ }
+
+ public static class ServiceAdapter implements AdaptedService {
+ private volatile OriginalService m_originalService;
+ private final Ensure m_ensure;
+
+ public ServiceAdapter(Ensure e) {
+ m_ensure = e;
+ }
+
+ public void start() { m_ensure.step(2); }
+ public void stop() { m_ensure.step(7); }
+ public void invoke() {
+ m_originalService.invoke();
+ }
+ }
+
+ public static class ServiceAdapterCallbackInstance {
+ private final Ensure m_ensure;
+ public ServiceAdapterCallbackInstance(Ensure e) {
+ m_ensure = e;
+ }
+
+ public void set(OriginalService m_originalService) {
+ m_ensure.step(1);
+ }
+
+ public void changed(Map<String, String> props, OriginalService m_originalService) {
+ Assert.assertEquals("bar", props.get("foo"));
+ m_ensure.step(5);
+ }
+
+ public void unset(Map<String, String> props, OriginalService m_originalService) {
+ m_ensure.step(8);
+ }
+ }
+
+ static class ServiceConsumer {
+ private volatile AdaptedService m_service;
+ private final Ensure m_ensure;
+
+ public ServiceConsumer(Ensure e) {
+ m_ensure = e;
+ }
+ public void start() {
+ m_ensure.step(3);
+ m_service.invoke();
+ }
+ public void stop() {
+ m_ensure.step(6);
+ }
+ }
+}
+
+
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AdapterWithConfigurationAndMetaType.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AdapterWithConfigurationAndMetaType.java
new file mode 100644
index 0000000..68eedd6
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AdapterWithConfigurationAndMetaType.java
@@ -0,0 +1,183 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import java.io.IOException;
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.junit.Assert;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.metatype.AttributeDefinition;
+import org.osgi.service.metatype.MetaTypeInformation;
+import org.osgi.service.metatype.MetaTypeService;
+import org.osgi.service.metatype.ObjectClassDefinition;
+
+/**
+ * Tests an Adapter which adapts A To B interface.
+ * And the Adapter also depends on a Configuration Dependency with MetaType support.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class AdapterWithConfigurationAndMetaType extends TestBase {
+ final static String PID = "AdapterWithConfigurationAndMetaType";
+ final static String PID_HEADING = "English Dictionary";
+ final static String PID_DESC = "Configuration for the EnglishDictionary Service";
+ final static String WORDS_HEADING = "English words";
+ final static String WORDS_DESC = "Declare here some valid English words";
+ final static String WORDS_PROPERTY = "words";
+
+ public void testAdapterWithConfigurationDependencyAndMetaType() {
+ DependencyManager m = getDM();
+ Ensure e = new Ensure();
+
+ m.add(m.createAdapterService(A.class, null)
+ .setInterface(B.class.getName(), null)
+ .setImplementation(new BImpl(e))
+ .add(m.createConfigurationDependency()
+ .setPid(PID)
+ .setHeading(PID_HEADING)
+ .setDescription(PID_DESC)
+ .add(m.createPropertyMetaData()
+ .setCardinality(Integer.MAX_VALUE)
+ .setType(String.class)
+ .setHeading(WORDS_HEADING)
+ .setDescription(WORDS_DESC)
+ .setDefaults(new String[] {"hello", "world"})
+ .setId(WORDS_PROPERTY))));
+
+ m.add(m.createComponent()
+ .setInterface(A.class.getName(), null)
+ .setImplementation(new AImpl()));
+
+ Component configurator = m.createComponent()
+ .setImplementation(new Configurator(e))
+ .add(m.createServiceDependency().setService(ConfigurationAdmin.class).setRequired(true))
+ .add(m.createServiceDependency().setService(MetaTypeService.class).setRequired(true));
+ m.add(configurator);
+
+ // Ensures that all components are started
+ e.waitForStep(4, 5000);
+
+ // now stop configurator, and ensure that all components have been stopped
+ m.remove(configurator);
+ e.waitForStep(7, 5000);
+ m.clear();
+ }
+
+ public interface A {
+ }
+
+ public interface B {
+ }
+
+ public class AImpl implements A {
+ }
+
+ public class Configurator {
+ volatile MetaTypeService m_metaType;
+ volatile ConfigurationAdmin m_cm;
+ volatile BundleContext m_ctx;
+ final Ensure m_ensure;
+ Configuration m_conf;
+
+ Configurator(Ensure ensure) {
+ m_ensure = ensure;
+ }
+
+ void start() {
+ m_ensure.step(1);
+ checkMetaTypeAndConfigure();
+ }
+
+ void stop() {
+ m_ensure.step(5);
+ if (m_conf != null) {
+ try {
+ m_ensure.step(6);
+ m_conf.delete();
+ }
+ catch (IOException e) {
+ m_ensure.throwable(e);
+ }
+ }
+ }
+
+ void checkMetaTypeAndConfigure() {
+ MetaTypeInformation info = m_metaType.getMetaTypeInformation(m_ctx.getBundle());
+ Assert.assertNotNull(info);
+ Assert.assertEquals(PID, info.getPids()[0]);
+ ObjectClassDefinition ocd = info.getObjectClassDefinition(PID, null);
+ Assert.assertNotNull(ocd);
+ Assert.assertEquals(PID_HEADING, ocd.getName());
+ Assert.assertEquals(PID_DESC, ocd.getDescription());
+ AttributeDefinition[] defs = ocd.getAttributeDefinitions(ObjectClassDefinition.ALL);
+ Assert.assertNotNull(defs);
+ Assert.assertEquals(1, defs.length);
+ Assert.assertEquals(WORDS_HEADING, defs[0].getName());
+ Assert.assertEquals(WORDS_DESC, defs[0].getDescription());
+ Assert.assertEquals(WORDS_PROPERTY, defs[0].getID());
+ m_ensure.step(2);
+
+ try {
+ m_conf = m_cm.getConfiguration(PID, null);
+ Hashtable<String, String> props = new Hashtable<>();
+ props.put("foo", "bar");
+ m_conf.update(props);
+ } catch (Throwable t) {
+ m_ensure.throwable(t);
+ }
+ }
+ }
+
+ public class BImpl implements B, A {
+ final Ensure m_ensure;
+ volatile A m_a;
+ Dictionary<String, String> m_conf;
+
+ public BImpl(Ensure e) {
+ m_ensure = e;
+ }
+
+ public void updated(Dictionary<String, String> conf) {
+ if (conf != null) {
+ m_ensure.step(3);
+ m_conf = conf;
+ }
+ }
+
+ public void start() {
+ Assert.assertNotNull(m_a);
+ Assert.assertNotNull(m_conf);
+ Assert.assertEquals("bar", m_conf.get("foo"));
+ m_ensure.step(4);
+ }
+
+ public void stop() {
+ m_ensure.step(7);
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AdapterWithExtraDependenciesTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AdapterWithExtraDependenciesTest.java
new file mode 100644
index 0000000..a0c6fb1
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AdapterWithExtraDependenciesTest.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class AdapterWithExtraDependenciesTest extends TestBase {
+ public void testAdapterWithExtraDependenciesAndCallbacks() {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+
+ // create a service adapter that adapts to services S1 and has an optional dependency on services S2
+ Component sa = m.createAdapterService(S1.class, null)
+ .setImplementation(SA.class)
+ .add(m.createServiceDependency().setService(S2.class).setCallbacks("add", "remove"));
+ m.add(sa);
+
+ // create a service S1, which triggers the creation of the first adapter instance (A1)
+ Component s1 = m.createComponent().setInterface(S1.class.getName(), null).setImplementation(new S1Impl());
+ m.add(s1);
+
+ // create a service S2, which will be added to A1
+ Component s2 = m.createComponent().setInterface(S2.class.getName(), null).setImplementation(new S2Impl(e));
+ m.add(s2);
+
+ // create a second service S1, which triggers the creation of the second adapter instance (A2)
+ Component s1b = m.createComponent().setInterface(S1.class.getName(), null).setImplementation(new S1Impl());
+ m.add(s1b);
+
+ // observe that S2 is also added to A2
+ e.waitForStep(2, 5000);
+
+ // remove S2 again
+ m.remove(s2);
+
+ // make sure both adapters have their "remove" callbacks invoked
+ e.waitForStep(4, 5000);
+
+ m.remove(s1);
+ m.remove(sa);
+ m.clear();
+ }
+
+ static interface S1 {
+ }
+ static interface S2 {
+ public void invoke();
+ }
+ static class S1Impl implements S1 {
+ }
+ static class S2Impl implements S2 {
+
+ private final Ensure m_e;
+
+ public S2Impl(Ensure e) {
+ m_e = e;
+ }
+
+ public void invoke() {
+ m_e.step();
+ }
+ }
+
+ public static class SA {
+ volatile S2 s2;
+
+ public SA() {
+ System.out.println("Adapter created");
+ }
+ public void init() {
+ System.out.println("Adapter init " + s2);
+ }
+ public void add(S2 s) {
+ System.out.println("adding " + s);
+ s.invoke();
+ }
+ public void remove(S2 s) {
+ System.out.println("removing " + s);
+ s.invoke();
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AdapterWithInstanceBoundDependencyParallelTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AdapterWithInstanceBoundDependencyParallelTest.java
new file mode 100644
index 0000000..4fe3edd
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AdapterWithInstanceBoundDependencyParallelTest.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class AdapterWithInstanceBoundDependencyParallelTest extends AdapterWithInstanceBoundDependencyTest {
+ public AdapterWithInstanceBoundDependencyParallelTest() {
+ setParallel();
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AdapterWithInstanceBoundDependencyTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AdapterWithInstanceBoundDependencyTest.java
new file mode 100644
index 0000000..77645a3
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AdapterWithInstanceBoundDependencyTest.java
@@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class AdapterWithInstanceBoundDependencyTest extends TestBase {
+ public void testInstanceBoundDependency() {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ // create a service provider and consumer
+ Component sp = m.createComponent()
+ .setInterface(ServiceInterface.class.getName(), null)
+ .setImplementation(new ServiceProvider(e));
+ Component sp2 = m.createComponent()
+ .setInterface(ServiceInterface2.class.getName(), null)
+ .setImplementation(new ServiceProvider2(e));
+ Component sc = m.createComponent()
+ .setImplementation(new ServiceConsumer(e))
+ .add(m.createServiceDependency()
+ .setService(ServiceInterface3.class)
+ .setRequired(true));
+ Component sa = m.createAdapterService(ServiceInterface.class, null)
+ .setInterface(ServiceInterface3.class.getName(), null)
+ .setImplementation(new ServiceAdapter(e));
+ m.add(sc);
+ m.add(sp);
+ m.add(sp2);
+ m.add(sa);
+ e.waitForStep(5, 15000);
+ // cleanup
+ m.remove(sa);
+ m.remove(sp2);
+ m.remove(sp);
+ m.remove(sc);
+ m.clear();
+ e.waitForStep(9, 5000); // make sure all components are stopped
+ }
+
+ static interface ServiceInterface {
+ public void invoke();
+ }
+
+ static interface ServiceInterface2 {
+ public void invoke();
+ }
+
+ static interface ServiceInterface3 {
+ public void invoke();
+ }
+
+ static class ServiceProvider2 implements ServiceInterface2 {
+ private final Ensure m_ensure;
+
+ public ServiceProvider2(Ensure ensure) {
+ m_ensure = ensure;
+ }
+
+ public void invoke() {
+ m_ensure.step(4);
+ }
+
+ public void stop() {
+ m_ensure.step();
+ }
+ }
+
+ static class ServiceProvider implements ServiceInterface {
+ private final Ensure m_ensure;
+ public ServiceProvider(Ensure e) {
+ m_ensure = e;
+ }
+ public void invoke() {
+ m_ensure.step(5);
+ }
+ public void stop() {
+ m_ensure.step();
+ }
+ }
+
+ static class ServiceAdapter implements ServiceInterface3 {
+ private Ensure m_ensure;
+ private volatile ServiceInterface m_originalService;
+ private volatile ServiceInterface2 m_injectedService;
+ private volatile Component m_service;
+ private volatile DependencyManager m_manager;
+
+ public ServiceAdapter(Ensure e) {
+ m_ensure = e;
+ }
+ public void init() {
+ m_ensure.step(1);
+ m_service.add(m_manager.createServiceDependency().setRequired(true).setService(ServiceInterface2.class));
+ }
+ public void start() {
+ m_ensure.step(2);
+ }
+ public void invoke() {
+ m_ensure.step(3);
+ m_injectedService.invoke();
+ m_originalService.invoke();
+ }
+
+ public void stop() {
+ m_ensure.step();
+ }
+ }
+
+ static class ServiceConsumer implements Runnable {
+ volatile ServiceInterface3 m_service;
+ final Ensure m_ensure;
+
+ ServiceConsumer(Ensure e) {
+ m_ensure = e;
+ }
+
+ public void init() {
+ Thread t = new Thread(this);
+ t.start();
+ }
+
+ public void run() {
+ m_service.invoke();
+ }
+ public void stop() {
+ m_ensure.step();
+ }
+ }
+}
+
+
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AdapterWithModifiedInstanceBoundDependencyTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AdapterWithModifiedInstanceBoundDependencyTest.java
new file mode 100644
index 0000000..46ecf74
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AdapterWithModifiedInstanceBoundDependencyTest.java
@@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.junit.Assert;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+
+/**
+ * Test for FELIX-4334 issue.
+ *
+ * Three components: A, B and C
+ *
+ * - A provided with property foo=bar
+ * - B adapts A, B has no filters on A, and B.init() method adds an instance bound required dependency to C.
+ * - C depends on A(foo=bar)
+ * - Now someone modifies the service properties of A: foo=bar2
+ * - As a result of that, C becomes unavailable and is unbound from B.
+ * - Since B has an instance bound required dependency to C: B should not be destroyed: it should be called in B.stop(), B.remove(C), B.change(A, "foo=bar2))
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+@SuppressWarnings({"unchecked", "rawtypes"})
+public class AdapterWithModifiedInstanceBoundDependencyTest extends TestBase {
+ public static interface A {
+ }
+
+ static class AImpl implements A {
+ final Ensure m_e;
+ AImpl(Ensure e) {
+ m_e = e;
+ }
+ }
+
+ public static interface C {
+ }
+
+ static class CImpl implements C {
+ volatile A m_a;
+ }
+
+ public static interface B {
+ }
+
+ static class BImpl implements B {
+ final Ensure m_e;
+ volatile A m_a;
+ volatile C m_c;
+
+ BImpl(Ensure e) {
+ m_e = e;
+ }
+
+ public void add(A a) {
+ m_e.step(1);
+ }
+
+ void init(Component c) {
+ m_e.step(2);
+ DependencyManager dm = c.getDependencyManager();
+ c.add(dm.createServiceDependency().setService(C.class).setRequired(true).setCallbacks("add", "remove"));
+ }
+
+ public void add(C c) {
+ m_e.step(3);
+ }
+
+ public void start() {
+ m_e.step(4);
+ }
+
+ public void stop() { // C becomes unsatisfied when A properties are changed to foo=bar2
+ m_e.step(5);
+ }
+
+ public void remove(C c) {
+ m_e.step(6);
+ }
+
+ public void change(Map properties, A a) {
+ Assert.assertEquals("bar2", properties.get("foo"));
+ m_e.step(7);
+ }
+
+ public void destroy() {
+ m_e.step(8);
+ }
+
+ public void remove(A a) {
+ m_e.step(9);
+ }
+ }
+
+ public void testAdapterWithChangedInstanceBoundDependency() {
+ DependencyManager m = getDM();
+ Ensure e = new Ensure();
+
+ Dictionary props = new Hashtable();
+ props.put("foo", "bar");
+ Component a = m.createComponent()
+ .setImplementation(new AImpl(e))
+ .setInterface(A.class.getName(), props);
+
+ Component b = m.createAdapterService(A.class, null, "add", "change", "remove")
+ .setInterface(B.class.getName(), null)
+ .setImplementation(new BImpl(e));
+
+ Component c = m.createComponent()
+ .setImplementation(new CImpl())
+ .setInterface(C.class.getName(), null)
+ .add(m.createServiceDependency().setService(A.class, "(foo=bar)").setRequired(true));
+
+ m.add(a);
+ m.add(c);
+ m.add(b);
+
+ e.waitForStep(4, 5000);
+
+ System.out.println("changing A props ...");
+ props = new Hashtable();
+ props.put("foo", "bar2");
+ a.setServiceProperties(props);
+
+ e.waitForStep(7, 5000);
+
+ m.remove(c);
+ m.remove(a);
+ m.remove(b);
+
+ e.waitForStep(9, 5000);
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AdapterWithPropagationTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AdapterWithPropagationTest.java
new file mode 100644
index 0000000..29ed79e
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AdapterWithPropagationTest.java
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.junit.Assert;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+
+/**
+ * Checks if a service adapter propagates its service properties, if
+ * the adapted service properties are changed:
+ *
+ * S1Impl provides S
+ * S1Adapter adapts S1Impl(S) to S2
+ * S3 depends on S2
+ *
+ * So, when S1Impl service properties are changed, S1Adapter shall propagate the changed properties to S3.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+@SuppressWarnings({"unchecked", "rawtypes"})
+public class AdapterWithPropagationTest extends TestBase {
+ public static interface S1 {}
+
+ static class S1Impl implements S1 {
+ private Ensure m_ensure;
+ public S1Impl(Ensure e) {
+ m_ensure = e;
+ }
+
+ public void start() {
+ m_ensure.step(1);
+ }
+ }
+
+ public static interface S2 {}
+
+ static class S1Adapter implements S2 {
+ private Ensure m_ensure;
+ public S1Adapter(Ensure e) {
+ m_ensure = e;
+ }
+
+ public void add(Map properties, S1 s1) {
+ Assert.assertTrue("v1".equals(properties.get("p1")));
+ Assert.assertTrue("v2overriden".equals(properties.get("p2")));
+ m_ensure.step(2);
+ }
+
+ public void change(Map properties, S1 s1) {
+ Assert.assertTrue("v1modified".equals(properties.get("p1")));
+ Assert.assertTrue("v2overriden".equals(properties.get("p2")));
+ m_ensure.step(4);
+ }
+ }
+
+ static class S3 {
+ private final Ensure m_ensure;
+
+ public S3(Ensure e) {
+ m_ensure = e;
+ }
+
+ public void add(Map properties, S2 s2) {
+ Assert.assertTrue("v1".equals(properties.get("p1")));
+ Assert.assertTrue("v2".equals(properties.get("p2"))); // s1 should not override adapter service properties
+ m_ensure.step(3);
+ }
+
+ public void change(Map properties, S2 s2) {
+ Assert.assertTrue("v1modified".equals(properties.get("p1")));
+ Assert.assertTrue("v2".equals(properties.get("p2"))); // s1 should not override adapter service properties
+ m_ensure.step(5);
+ }
+ }
+
+ public void testAdapterWithPropagation() {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+
+ Dictionary s1Properties = new Hashtable();
+ s1Properties.put("p1", "v1");
+ s1Properties.put("p2", "v2overriden"); // should not override adapter
+ Component s1 = m.createComponent()
+ .setImplementation(new S1Impl(e))
+ .setInterface(S1.class.getName(), s1Properties);
+
+ Dictionary s1AdapterProperties = new Hashtable();
+ s1AdapterProperties.put("p2", "v2");
+ Component s1Adapter = m.createAdapterService(S1.class, null, "add", "change", null)
+ .setInterface(S2.class.getName(), s1AdapterProperties)
+ .setImplementation(new S1Adapter(e));
+
+ Component s3 = m.createComponent()
+ .setImplementation(new S3(e))
+ .add(m.createServiceDependency()
+ .setService(S2.class)
+ .setRequired(true)
+ .setCallbacks("add", "change", null));
+
+
+ m.add(s1);
+ m.add(s1Adapter);
+ m.add(s3);
+
+ e.waitForStep(3, 5000);
+
+ s1Properties = new Hashtable();
+ s1Properties.put("p1", "v1modified");
+ s1Properties.put("p2", "v2overriden");
+ s1.setServiceProperties(s1Properties);
+
+ e.waitForStep(5, 5000);
+
+ m.clear();
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AdapterWithoutPropagationTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AdapterWithoutPropagationTest.java
new file mode 100644
index 0000000..d50bbd3
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AdapterWithoutPropagationTest.java
@@ -0,0 +1,137 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.junit.Assert;
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+import org.osgi.framework.ServiceRegistration;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class AdapterWithoutPropagationTest extends TestBase {
+
+ public void testAdapterNoPropagate() {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+
+ // The provider has a "foo=bar" property
+ Hashtable<String, String> props = new Hashtable<>();
+ props.put("foo", "bar");
+ ServiceProvider serviceProvider = new ServiceProvider(e);
+ Component provider = m.createComponent()
+ .setInterface(OriginalService.class.getName(), props).setImplementation(serviceProvider);
+
+ // The Adapter will see the "foo=bar" property from the adaptee
+ Component adapter = m.createAdapterService(OriginalService.class, null, null,
+ null, "set", "change", null, null, false)
+ .setInterface(AdaptedService.class.getName(), null)
+ .setImplementation(new ServiceAdapter(e));
+
+ // The consumer depends on the AdaptedService, but won't see foo=bar property from the adaptee
+ Component consumer = m.createComponent()
+ .setImplementation(new ServiceConsumer(e))
+ .add(m.createServiceDependency()
+ .setService(AdaptedService.class)
+ .setRequired(true)
+ .setCallbacks("set", "change", null)
+ );
+
+ // add the provider and the adapter
+ m.add(provider);
+ m.add(adapter);
+ // Checks if the adapter has been started and has seen the adaptee properties
+ e.waitForStep(1, 5000);
+
+ // add a consumer that must not see the adaptee service properties
+ m.add(consumer);
+ e.waitForStep(2, 5000);
+
+ // change the service properties of the provider, and check that the adapter callback instance is caled.
+ serviceProvider.changeServiceProperties();
+ e.waitForStep(3, 5000);
+
+ // cleanup
+ m.clear();
+ }
+
+ static interface OriginalService {
+ }
+
+ static interface AdaptedService {
+ }
+
+ static class ServiceProvider implements OriginalService {
+ private volatile ServiceRegistration m_registration; // auto injected when started.
+ public ServiceProvider(Ensure e) {
+ }
+ public void changeServiceProperties() {
+ Hashtable<String, String> props = new Hashtable<>();
+ props.put("foo", "bar2");
+ m_registration.setProperties(props);
+ }
+ }
+
+ public static class ServiceAdapter implements AdaptedService {
+ private final Ensure m_ensure;
+
+ public ServiceAdapter(Ensure e) {
+ m_ensure = e;
+ }
+
+ public void set(OriginalService adaptee, Dictionary<String, String> props) {
+ Assert.assertEquals("bar", props.get("foo"));
+ m_ensure.step(1);
+ }
+
+ void change(OriginalService adapted, Dictionary<String, String> props) {
+ Assert.assertEquals("bar2", props.get("foo"));
+ m_ensure.step(3);
+ }
+ }
+
+ static class ServiceConsumer {
+ @SuppressWarnings("unused")
+ private volatile AdaptedService m_service;
+ private final Ensure m_ensure;
+
+ public ServiceConsumer(Ensure e) {
+ m_ensure = e;
+ }
+
+ void set(AdaptedService adapted, Dictionary<String, String> props) {
+ Assert.assertNull(props.get("foo"));
+ m_ensure.step(2);
+ }
+
+ void change(AdaptedService adapted, Dictionary<String, String> props) {
+ Assert.assertNull(props.get("foo"));
+ Assert.fail("Change callback should not be called");
+ }
+ }
+}
+
+
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AspectBaseTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AspectBaseTest.java
new file mode 100644
index 0000000..e4d2d73
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AspectBaseTest.java
@@ -0,0 +1,227 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Properties;
+
+import org.junit.Assert;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.ServiceUtil;
+import org.apache.felix.dm.itest.util.TestBase;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+@SuppressWarnings({"unchecked", "rawtypes", "unused"})
+public class AspectBaseTest extends TestBase {
+
+ public void testSingleAspect() {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+
+ // create a service provider and consumer
+ ServiceProvider p = new ServiceProvider(e, "a");
+ ServiceConsumer c = new ServiceConsumer(e);
+ Hashtable<String, Object> props = new Hashtable<>();
+ props.put("name", "a");
+ Component sp = m.createComponent()
+ .setInterface(ServiceInterface.class.getName(), props)
+ .setImplementation(p);
+ Component sc = m.createComponent()
+ .setImplementation(c)
+ .add(m.createServiceDependency()
+ .setService(ServiceInterface.class)
+ .setRequired(true)
+ .setCallbacks("add", "remove")
+ .setAutoConfig("m_service")
+ );
+ Component sa = m.createAspectService(ServiceInterface.class, null, 20, null)
+ .setImplementation(ServiceAspect.class);
+ m.add(sc);
+ m.add(sp);
+ // after the provider was added, the consumer's add should have been invoked once
+ e.waitForStep(1, 2000);
+ Assert.assertEquals("a", c.invoke());
+ m.add(sa);
+ // after the aspect was added, the consumer should get and add for the aspect and a remove
+ // for the original service
+ e.waitForStep(3, 2000);
+ Assert.assertEquals("aa", c.invoke());
+ m.remove(sa);
+ // removing the aspect again should give a remove and add
+ e.waitForStep(5, 2000);
+ Assert.assertEquals("a", c.invoke());
+ m.remove(sp);
+ // finally removing the original service should give a remove
+ e.waitForStep(6, 2000);
+ m.remove(sc);
+ e.step(7);
+ }
+
+ @SuppressWarnings("serial")
+ public void testSingleAspectThatAlreadyExisted() {
+ DependencyManager m = new DependencyManager(context);
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+
+ // create a service provider and consumer
+ ServiceProvider p = new ServiceProvider(e, "a");
+ ServiceConsumer c = new ServiceConsumer(e);
+ Component sp = m.createComponent().setImplementation(p).setInterface(ServiceInterface.class.getName(), new Hashtable() {{ put("name", "a"); }});
+ Component sc = m.createComponent().setImplementation(c).add(m.createServiceDependency().setService(ServiceInterface.class).setRequired(true).setCallbacks("add", "remove").setAutoConfig("m_service"));
+ Component sa = m.createAspectService(ServiceInterface.class, null, 20, null).setImplementation(ServiceAspect.class);
+ // we first add the aspect
+ m.add(sa);
+ // then the service provider
+ m.add(sp);
+ // finally the consumer
+ m.add(sc);
+
+ Assert.assertEquals("aa", c.invoke());
+
+ // now the consumer's added should be invoked once, as the aspect is already available and should
+ // directly hide the original service
+ e.waitForStep(1, 2000);
+ e.step(2);
+
+ m.remove(sa);
+ // after removing the aspect, the consumer should get the original service back, so
+ // remove and add will be invoked
+ e.waitForStep(4, 2000);
+
+ Assert.assertEquals("a", c.invoke());
+
+ m.remove(sp);
+ // after removing the original service, the consumer's remove should be called once
+ e.waitForStep(5, 2000);
+
+ m.remove(sc);
+ e.step(6);
+ }
+
+ @SuppressWarnings("serial")
+ public void testMultipleAspects() {
+ DependencyManager m = new DependencyManager(context);
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+
+ // create service providers and consumers
+ ServiceConsumer c = new ServiceConsumer(e);
+ Component sp = m.createComponent().setImplementation(new ServiceProvider(e, "a")).setInterface(ServiceInterface.class.getName(), new Hashtable() {{ put("name", "a"); }});
+ Component sp2 = m.createComponent().setImplementation(new ServiceProvider(e, "b")).setInterface(ServiceInterface.class.getName(), new Hashtable() {{ put("name", "b"); }});
+ Component sc = m.createComponent().setImplementation(c).add(m.createServiceDependency().setService(ServiceInterface.class).setRequired(true).setCallbacks("add", "remove"));
+ Component sa = m.createAspectService(ServiceInterface.class, null, 20, null).setImplementation(ServiceAspect.class);
+ Component sa2 = m.createAspectService(ServiceInterface.class, null, 10, null).setImplementation(ServiceAspect.class);
+ m.add(sp);
+ m.add(sp2);
+ m.add(sa);
+ m.add(sa2);
+ m.add(sc);
+ // the consumer will monitor progress, it should get it's add invoked twice, once for every
+ // (highest) aspect
+ e.waitForStep(2, 2000);
+ e.step(3);
+
+ // now invoke all services the consumer collected
+ List<String> list = c.invokeAll();
+ // and make sure both of them are correctly invoked
+ Assert.assertTrue(list.size() == 2);
+ Assert.assertTrue(list.contains("aaa"));
+ Assert.assertTrue(list.contains("bbb"));
+
+ m.remove(sc);
+ // removing the consumer now should get its removed method invoked twice
+ e.waitForStep(5, 2000);
+ e.step(6);
+ m.remove(sa2);
+ m.remove(sa);
+ m.remove(sp2);
+ m.remove(sp);
+ e.step(7);
+ }
+
+ public static interface ServiceInterface {
+ public String invoke(String input);
+ }
+
+ public static class ServiceProvider implements ServiceInterface {
+ private final Ensure m_ensure;
+ private final String m_name;
+ public ServiceProvider(Ensure e, String name) {
+ m_ensure = e;
+ m_name = name;
+ }
+ public String invoke(String input) {
+ return input + m_name;
+ }
+ }
+
+ public static class ServiceAspect implements ServiceInterface {
+ private volatile ServiceInterface m_originalService;
+ private volatile ServiceRegistration m_registration;
+
+ public String invoke(String input) {
+ String result = m_originalService.invoke(input);
+ String property = (String) m_registration.getReference().getProperty("name");
+ return result + property;
+ }
+ }
+
+ public static class ServiceConsumer {
+ private final Ensure m_ensure;
+ private volatile ServiceInterface m_service;
+ private List<ServiceInterface> m_services = new ArrayList<ServiceInterface>();
+
+ public ServiceConsumer(Ensure e) {
+ m_ensure = e;
+ }
+
+ public void add(ServiceReference ref, ServiceInterface si) {
+ System.out.println("add: " + ServiceUtil.toString(ref));
+ m_services.add(si);
+ m_ensure.step();
+ }
+
+ public void remove(ServiceReference ref, ServiceInterface si) {
+ System.out.println("rem: " + ServiceUtil.toString(ref));
+ m_services.remove(si);
+ m_ensure.step();
+ }
+
+ public String invoke() {
+ return m_service.invoke("");
+ }
+
+ public List<String> invokeAll() {
+ List<String> results = new ArrayList<String>();
+ for (ServiceInterface si : m_services) {
+ results.add(si.invoke(""));
+ }
+ return results;
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AspectChainTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AspectChainTest.java
new file mode 100644
index 0000000..4d524fd
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AspectChainTest.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class AspectChainTest extends TestBase {
+
+ public void testBuildAspectChain() {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ // create a service provider and consumer
+ Component sp = m.createComponent().setImplementation(new ServiceProvider(e)).setInterface(ServiceInterface.class.getName(), null);
+ Component sc = m.createComponent().setImplementation(new ServiceConsumer(e)).add(m.createServiceDependency().setService(ServiceInterface.class).setRequired(true));
+ Component sa2 = m.createAspectService(ServiceInterface.class, null, 20, null).setImplementation(new ServiceAspect(e, 3));
+ Component sa3 = m.createAspectService(ServiceInterface.class, null, 30, null).setImplementation(new ServiceAspect(e, 2));
+ Component sa1 = m.createAspectService(ServiceInterface.class, null, 10, null).setImplementation(new ServiceAspect(e, 4));
+ m.add(sc);
+
+ m.add(sp);
+ m.add(sa2);
+ m.add(sa3);
+ m.add(sa1);
+ e.step();
+ e.waitForStep(5, 5000);
+
+ m.remove(sa3);
+ m.remove(sa2);
+ m.remove(sa1);
+ m.remove(sp);
+
+ m.remove(sc);
+ }
+
+ static interface ServiceInterface {
+ public void invoke(Runnable run);
+ }
+
+ static class ServiceProvider implements ServiceInterface {
+ @SuppressWarnings("unused")
+ private final Ensure m_ensure;
+ public ServiceProvider(Ensure e) {
+ m_ensure = e;
+ }
+ public void invoke(Runnable run) {
+ run.run();
+ }
+ }
+
+ static class ServiceAspect implements ServiceInterface {
+ private final Ensure m_ensure;
+ private volatile ServiceInterface m_parentService;
+ private final int m_step;
+
+ public ServiceAspect(Ensure e, int step) {
+ m_ensure = e;
+ m_step = step;
+ }
+ public void start() {
+ }
+
+ public void invoke(Runnable run) {
+ m_ensure.step(m_step);
+ m_parentService.invoke(run);
+ }
+
+ public void stop() {
+ }
+ }
+
+ static class ServiceConsumer implements Runnable {
+ private volatile ServiceInterface m_service;
+ private final Ensure m_ensure;
+
+ public ServiceConsumer(Ensure e) {
+ m_ensure = e;
+ }
+
+ public void init() {
+ Thread t = new Thread(this);
+ t.start();
+ }
+
+ public void run() {
+ m_ensure.waitForStep(1, 2000);
+ m_service.invoke(Ensure.createRunnableStep(m_ensure, 5));
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AspectDynamicsTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AspectDynamicsTest.java
new file mode 100644
index 0000000..7177edb
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AspectDynamicsTest.java
@@ -0,0 +1,194 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class AspectDynamicsTest extends TestBase {
+
+ public void testDynamicallyAddAndRemoveAspect() {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ Ensure aspectStopEnsure = new Ensure();
+ // create a service provider and consumer
+ Component provider = m.createComponent().setImplementation(new ServiceProvider(e)).setInterface(ServiceInterface.class.getName(), null);
+ Component provider2 = m.createComponent().setImplementation(new ServiceProvider2(e)).setInterface(ServiceInterface2.class.getName(), null);
+ Component consumer = m.createComponent().setImplementation(new ServiceConsumer(e))
+ .add(m.createServiceDependency().setService(ServiceInterface.class).setRequired(true).setCallbacks("add", null, null, "swap"));
+ Component aspect = m.createAspectService(ServiceInterface.class, null, 1, null).setImplementation(new ServiceAspect(e, aspectStopEnsure));
+
+ m.add(consumer);
+ m.add(provider);
+ // the consumer should invoke the provider here, and when done, arrive at step 3
+ // finally wait for step 6 before continuing
+ e.waitForStep(3, 15000);
+
+ m.add(aspect);
+ // after adding the aspect, we wait for its init to be invoked, arriving at
+ // step 4 after an instance bound dependency was added (on a service provided by
+ // provider 2)
+ e.waitForStep(4, 15000);
+
+ m.add(provider2);
+
+ // after adding provider 2, we should now see the client being swapped, so
+ // we wait for step 5 to happen
+ e.waitForStep(5, 15000);
+
+ // now we continue with step 6, which will trigger the next part of the consumer's
+ // run method to be executed
+ e.step(6);
+
+ // invoking step 7, 8 and 9 when invoking the aspect which in turn invokes the
+ // dependency and the original service, so we wait for that to finish here, which
+ // is after step 10 has been reached (the client will now wait for step 12)
+ e.waitForStep(10, 15000);
+
+ m.remove(aspect);
+ aspectStopEnsure.waitForStep(1, 15000);
+ // removing the aspect should trigger step 11 (in the swap method of the consumer)
+ e.waitForStep(11, 15000);
+
+ // step 12 triggers the client to continue
+ e.step(12);
+
+ // wait for step 13, the final invocation of the provided service (without aspect)
+ e.waitForStep(13, 15000);
+
+ // clean up
+ m.remove(provider2);
+ m.remove(provider);
+ m.remove(consumer);
+ e.waitForStep(16, 15000);
+ m.clear();
+ }
+
+ static interface ServiceInterface {
+ public void invoke(Runnable run);
+ }
+
+ static interface ServiceInterface2 {
+ public void invoke();
+ }
+
+ static class ServiceProvider2 implements ServiceInterface2 {
+ private final Ensure m_ensure;
+
+ public ServiceProvider2(Ensure ensure) {
+ m_ensure = ensure;
+ }
+
+ public void invoke() {
+ m_ensure.step(9);
+ }
+ public void stop() {
+ m_ensure.step();
+ }
+ }
+
+ static class ServiceProvider implements ServiceInterface {
+ private final Ensure m_ensure;
+ public ServiceProvider(Ensure e) {
+ m_ensure = e;
+ }
+ public void invoke(Runnable run) {
+ run.run();
+ }
+ public void stop() {
+ m_ensure.step();
+ }
+ }
+
+ static class ServiceAspect implements ServiceInterface {
+ private final Ensure m_ensure;
+ private volatile ServiceInterface m_originalService;
+ private volatile ServiceInterface2 m_injectedService;
+ private volatile Component m_service;
+ private volatile DependencyManager m_manager;
+ private final Ensure m_stopEnsure;
+
+ public ServiceAspect(Ensure e, Ensure stopEnsure) {
+ m_ensure = e;
+ m_stopEnsure = stopEnsure;
+ }
+ public void init() {
+ m_service.add(m_manager.createServiceDependency()
+ .setService(ServiceInterface2.class)
+ .setRequired(true)
+ );
+ m_ensure.step(4);
+ }
+
+ public void invoke(Runnable run) {
+ m_ensure.step(7);
+ m_originalService.invoke(run);
+ m_injectedService.invoke();
+ }
+
+ public void stop() {
+ m_stopEnsure.step(1);
+ }
+ }
+
+ static class ServiceConsumer implements Runnable {
+ private volatile ServiceInterface m_service;
+ private final Ensure m_ensure;
+ private final Ensure.Steps m_swapSteps = new Ensure.Steps(5, 11);
+
+ public ServiceConsumer(Ensure e) {
+ m_ensure = e;
+ }
+
+ void add(ServiceInterface service) {
+ m_service = service;
+ }
+
+ void swap(ServiceInterface oldService, ServiceInterface newService) {
+ System.out.println("swap: old=" + oldService + ", new=" + newService);
+ m_ensure.steps(m_swapSteps);
+ m_service = newService;
+ }
+
+ public void init() {
+ Thread t = new Thread(this);
+ t.start();
+ }
+
+ public void run() {
+ m_ensure.step(1);
+ m_service.invoke(Ensure.createRunnableStep(m_ensure, 2));
+ m_ensure.step(3);
+ m_ensure.waitForStep(6, 15000);
+ m_service.invoke(Ensure.createRunnableStep(m_ensure, 8));
+ m_ensure.step(10);
+ m_ensure.waitForStep(12, 15000);
+ m_service.invoke(Ensure.createRunnableStep(m_ensure, 13));
+ }
+
+ public void stop() {
+ m_ensure.step();
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AspectRaceParallelTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AspectRaceParallelTest.java
new file mode 100644
index 0000000..1559a75
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AspectRaceParallelTest.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class AspectRaceParallelTest extends AspectRaceTest {
+ public AspectRaceParallelTest() {
+ setParallel();
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AspectRaceTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AspectRaceTest.java
new file mode 100644
index 0000000..e8f6e0d
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AspectRaceTest.java
@@ -0,0 +1,324 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.junit.Assert;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * This class validates that some aspect aware services are correctly managed and ordered when components and aspects are
+ * registered concurrently.
+ *
+ * By default, this class uses a custom threadpool, but a subclass may override this class and call "setParallel()" method, in
+ * this case we won't use any threadpool, since calling setParallel() method means we are using a parallel Dependency Manager.
+ *
+ * @see AspectRaceParallelTest
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class AspectRaceTest extends TestBase {
+ final static int SERVICES = 3;
+ final static int ASPECTS_PER_SERVICE = 10;
+ final static int ITERATIONS = 1000;
+ final AtomicInteger m_IDGenerator = new AtomicInteger();
+ ExecutorService m_threadpool;
+
+ public void testConcurrentAspects() {
+ try {
+ warn("starting aspect race test");
+ initThreadPool(); // only if setParallel() has not been called (only if a parallel DM is not used).
+
+ for (int loop = 1; loop <= ITERATIONS; loop++) {
+ // Perform concurrent injections of "S" service and S aspects into the Controller component;
+ debug("Iteration: " + loop);
+
+ // Use a helper class to wait for components to be started/stopped.
+ int count = 1 /* for controller */ + SERVICES + (SERVICES * ASPECTS_PER_SERVICE);
+ ComponentTracker tracker = new ComponentTracker(count, count);
+
+ // Create the components (controller / services / aspects)
+ Controller controller = new Controller();
+ Factory f = new Factory();
+ f.createComponents(controller, tracker);
+
+ // Activate the components asynchronously
+ f.registerComponents();
+
+ // Wait for the components to be started (using the tracker)
+ if (!tracker.awaitStarted(5000)) {
+ throw new IllegalStateException("Could not start components timely.");
+ }
+
+ // Check aspect chains consistency.
+ controller.checkConsistency();
+
+ // unregister all services and aspects.
+ f.unregisterComponents();
+
+ // use component tracker to wait for all components to be stopped.
+ if (!tracker.awaitStopped(5000)) {
+ throw new IllegalStateException("Could not stop components timely.");
+ }
+
+ if ((loop) % 50 == 0) {
+ warn("Performed " + loop + " tests.");
+ }
+
+ if (super.errorsLogged()) {
+ throw new IllegalStateException("Race test interrupted (some error occured, see previous logs)");
+ }
+ }
+ }
+
+ catch (Throwable t) {
+ error("Test failed", t);
+ Assert.fail("Test failed: " + t.getMessage());
+ } finally {
+ m_dm.clear();
+ shutdownThreadPool();
+ }
+ }
+
+ private void initThreadPool() {
+ // Create a threadpool only if setParallel() method has not been called.
+ if (! m_parallel) {
+ int cores = Math.max(16, Runtime.getRuntime().availableProcessors());
+ m_threadpool = Executors.newFixedThreadPool(cores);
+ }
+ }
+
+ void shutdownThreadPool() {
+ if (! m_parallel && m_threadpool != null) {
+ m_threadpool.shutdown();
+ try {
+ m_threadpool.awaitTermination(60, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+
+ public interface S {
+ void invoke(Ensure e);
+
+ int getRank();
+ }
+
+ public static class SImpl implements S {
+
+ SImpl() {
+ }
+
+ public void invoke(Ensure e) {
+ e.step(1);
+ }
+
+ public String toString() {
+ return "SImpl";
+ }
+
+ @Override
+ public int getRank() {
+ return Integer.MIN_VALUE;
+ }
+ }
+
+ public class SAspect implements S {
+ volatile S m_next;
+ final int m_rank;
+ volatile Component m_component;
+
+ SAspect(int rank) {
+ m_rank = rank;
+ }
+
+ public synchronized void added(S s) {
+ debug("aspect.added: this rank=%d, next rank=%d", getRank(), s.getRank());
+ m_next = s;
+ }
+
+ public synchronized void swap(S oldS, S newS) {
+ debug("aspect.swap: this rank=%d, old rank=%d, next rank=%d", getRank(), oldS.getRank(), newS.getRank());
+ m_next = newS;
+ }
+
+ public synchronized void removed(S s) {
+ debug("aspect.remove: this rank=%d, removed rank=%d", getRank(), s.getRank());
+ m_next = null;
+ }
+
+ public synchronized void invoke(Ensure e) {
+ debug("aspect.invoke: this rank=%d, next rank=%d", this.getRank(), m_next.getRank());
+ Assert.assertTrue(m_rank > m_next.getRank());
+ m_next.invoke(e);
+ }
+
+ public String toString() {
+ return "[Aspect/rank=" + m_rank + "], next="
+ + ((m_next != null) ? m_next : "null");
+ }
+
+ @Override
+ public int getRank() {
+ return m_rank;
+ }
+ }
+
+ class Factory {
+ int m_serviceId;
+ Component m_controller;
+ final ConcurrentLinkedQueue<Component> m_services = new ConcurrentLinkedQueue<Component>();
+ final ConcurrentLinkedQueue<Component> m_aspects = new ConcurrentLinkedQueue<Component>();
+
+ private void createComponents(Controller controller, ComponentTracker tracker) {
+ // create the controller
+ int controllerID = m_IDGenerator.incrementAndGet();
+ m_controller = m_dm.createComponent()
+ .setImplementation(controller)
+ .setComposition("getComposition")
+ .add(tracker);
+ for (int i = 0; i < SERVICES; i ++) {
+ m_controller.add(m_dm.createServiceDependency()
+ .setService(S.class, "(controller.id=" + controllerID + ")")
+ .setCallbacks("bind", null, "unbind", "swap")
+ .setRequired(true));
+ }
+
+ // create the services
+ for (int i = 1; i <= SERVICES; i++) {
+ int aspectId = m_IDGenerator.incrementAndGet();
+ Component s = m_dm.createComponent();
+ Hashtable<String, String> props = new Hashtable<String, String>();
+ props.put("controller.id", String.valueOf(controllerID));
+ props.put("aspect.id", String.valueOf(aspectId));
+ s.setInterface(S.class.getName(), props)
+ .setImplementation(new SImpl());
+ s.add(tracker);
+ m_services.add(s);
+
+ // create the aspects for that service
+ for (int j = 1; j <= ASPECTS_PER_SERVICE; j++) {
+ final int rank = j;
+ SAspect sa = new SAspect(rank);
+ Component a =
+ m_dm.createAspectService(S.class, "(aspect.id=" + aspectId + ")", rank, "added", null, "removed", "swap")
+ .setImplementation(sa);
+ a.add(tracker);
+ m_aspects.add(a);
+ }
+ }
+ }
+
+ public void registerComponents() {
+ // If setParallel() has been called (we are using a parallel dependency manager), then no needs to use a custom thread pool.
+ if (m_parallel) { // using a parallel DM.
+ for (final Component s : m_services) {
+ m_dm.add(s);
+ }
+ m_dm.add(m_controller);
+ for (final Component a : m_aspects) {
+ m_dm.add(a);
+ }
+ } else {
+ for (final Component s : m_services) {
+ m_threadpool.execute(new Runnable() {
+ public void run() {
+ m_dm.add(s);
+ }
+ });
+ }
+ m_threadpool.execute(new Runnable() {
+ public void run() {
+ m_dm.add(m_controller);
+ }
+ });
+ for (final Component a : m_aspects) {
+ m_threadpool.execute(new Runnable() {
+ public void run() {
+ m_dm.add(a);
+ }
+ });
+ }
+ }
+ }
+
+ public void unregisterComponents() throws InterruptedException, InvalidSyntaxException {
+ m_dm.remove(m_controller);
+ for (final Component s : m_services) {
+ m_dm.remove(s);
+ }
+ for (final Component a : m_aspects) {
+ m_dm.remove(a);
+ }
+ }
+ }
+
+ public class Controller {
+ final Composition m_compo = new Composition();
+ final HashSet<S> m_services = new HashSet<S>();
+
+ Object[] getComposition() {
+ return new Object[] { this, m_compo };
+ }
+
+ synchronized void bind(ServiceReference sr, Object service) {
+ debug("controller.bind: %s", service);
+ S s = (S) service;
+ m_services.add(s);
+ debug("bind: service count after bind: %d", m_services.size());
+ }
+
+ synchronized void swap(S previous, S current) {
+ debug("controller.swap: previous=%s, current=%s", previous, current);
+ if (!m_services.remove(previous)) {
+ debug("swap: unknow previous service: " + previous);
+ }
+ m_services.add(current);
+ debug("controller.swap: service count after swap: %d", m_services.size());
+ }
+
+ synchronized void unbind(S a) {
+ debug("unbind " + a);
+ m_services.remove(a);
+ }
+
+ synchronized void checkConsistency() {
+ debug("service count: %d", m_services.size());
+ for (S s : m_services) {
+ info("checking service: %s", s);
+ Ensure ensure = new Ensure(false);
+ s.invoke(ensure);
+ }
+ }
+ }
+
+ public static class Composition {
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AspectServiceDependencyTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AspectServiceDependencyTest.java
new file mode 100644
index 0000000..365ee31
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AspectServiceDependencyTest.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class AspectServiceDependencyTest extends TestBase {
+ public void testServiceRegistrationAndConsumption() {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ // create a service provider and consumer
+ Component sp = m.createComponent().setImplementation(new ServiceProvider(e)).setInterface(ServiceInterface.class.getName(), null);
+ Component sc = m.createComponent().setImplementation(new ServiceConsumer(e)).add(m.createServiceDependency()
+ .setService(ServiceInterface.class)
+ .setCallbacks("add", "remove")
+ .setRequired(true));
+ Component asp = m.createAspectService(ServiceInterface.class, null, 100)
+ .setImplementation(ServiceProviderAspect.class);
+ m.add(sp);
+ m.add(sc);
+ m.add(asp);
+ m.remove(asp);
+ m.remove(sc);
+ m.remove(sp);
+
+ // ensure we executed all steps inside the component instance
+ e.step(8);
+ }
+
+ static interface ServiceInterface {
+ public void invoke(String caller);
+ }
+
+ static class ServiceProvider implements ServiceInterface {
+ private final Ensure m_ensure;
+ public ServiceProvider(Ensure e) {
+ m_ensure = e;
+ }
+ public void invoke(String caller) {
+ if (caller.equals("consumer.init")) {
+ m_ensure.step(3);
+ } else if (caller.equals("aspect.consumer.add")) {
+ m_ensure.step(5);
+ }
+ }
+ }
+
+ static class ServiceProviderAspect implements ServiceInterface {
+ private volatile ServiceInterface m_service;
+
+ public ServiceProviderAspect() {
+ }
+
+ @Override
+ public void invoke(String caller) {
+ m_service.invoke("aspect." + caller);
+ }
+ }
+
+ static class ServiceConsumer {
+ private volatile ServiceInterface m_service;
+ private final Ensure m_ensure;
+ private int addCount = 0;
+
+ public ServiceConsumer(Ensure e) {
+ m_ensure = e;
+ }
+
+ public void init() {
+ m_ensure.step(2);
+ m_service.invoke("consumer.init");
+ }
+
+ public void destroy() {
+ m_ensure.step(7);
+ }
+
+ public void add(ServiceInterface service) {
+ m_service = service;
+ switch (addCount) {
+ case 0: m_ensure.step(1);
+ break;
+ case 1: m_ensure.step(4);
+ // aspect had been added
+ m_service.invoke("consumer.add");
+ break;
+ case 2: m_ensure.step(6);
+ break;
+ default:
+ }
+ addCount ++;
+ }
+ public void remove(ServiceInterface service) {
+ m_service = null;
+ }
+ }
+
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AspectServiceDependencyWithSwapCallbackTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AspectServiceDependencyWithSwapCallbackTest.java
new file mode 100644
index 0000000..03ce7c1
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AspectServiceDependencyWithSwapCallbackTest.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class AspectServiceDependencyWithSwapCallbackTest extends TestBase {
+ public void testServiceRegistrationAndConsumption() {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ // create a service provider and consumer
+ Component sp = m.createComponent().setImplementation(new ServiceProvider(e)).setInterface(ServiceInterface.class.getName(), null);
+ Component sc = m.createComponent().setImplementation(new ServiceConsumer(e)).add(m.createServiceDependency()
+ .setService(ServiceInterface.class)
+ .setCallbacks("add", null, "remove", "swap")
+ .setRequired(true));
+ Component asp = m.createAspectService(ServiceInterface.class, null, 100)
+ .setImplementation(ServiceProviderAspect.class);
+ m.add(sp);
+ m.add(sc);
+ m.add(asp);
+ m.remove(asp);
+ m.remove(sc);
+ m.remove(sp);
+
+ // ensure we executed all steps inside the component instance
+ e.step(7);
+ }
+
+ static interface ServiceInterface {
+ public void invoke(String caller);
+ }
+
+ static class ServiceProvider implements ServiceInterface {
+ private final Ensure m_ensure;
+ public ServiceProvider(Ensure e) {
+ m_ensure = e;
+ }
+ public void invoke(String caller) {
+ if (caller.equals("consumer.init")) {
+ m_ensure.step(3);
+ } else if (caller.equals("aspect.consumer.add")) {
+ m_ensure.step(5);
+ }
+ }
+ }
+
+ static class ServiceProviderAspect implements ServiceInterface {
+ private volatile ServiceInterface m_service;
+
+ public ServiceProviderAspect() {
+ }
+
+ @Override
+ public void invoke(String caller) {
+ m_service.invoke("aspect." + caller);
+ }
+ }
+
+ static class ServiceConsumer {
+ private volatile ServiceInterface m_service;
+ private final Ensure m_ensure;
+ private int swapCount = 0;
+
+ public ServiceConsumer(Ensure e) {
+ m_ensure = e;
+ }
+
+ public void init() {
+ m_ensure.step(2);
+ m_service.invoke("consumer.init");
+ }
+
+ public void destroy() {
+ m_ensure.step(6);
+ }
+
+ public void add(ServiceInterface service) {
+ m_service = service;
+ m_ensure.step(1);
+ }
+
+ public void remove(ServiceInterface service) {
+ m_service = null;
+ }
+
+ public void swap(ServiceInterface previous, ServiceInterface current) {
+ switch (swapCount) {
+ case 0: m_ensure.step(4);
+ break;
+ case 1: m_ensure.step(5);
+ break;
+ default:
+ }
+ m_service = current;
+ swapCount ++;
+ }
+ }
+
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AspectWhiteboardTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AspectWhiteboardTest.java
new file mode 100644
index 0000000..4e4181d
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AspectWhiteboardTest.java
@@ -0,0 +1,201 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Assert;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.ServiceUtil;
+import org.apache.felix.dm.itest.util.TestBase;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+@SuppressWarnings({"rawtypes", "unchecked", "unused"})
+public class AspectWhiteboardTest extends TestBase {
+
+ public void testWhiteboardConsumer() {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ // create service providers and consumer
+ Component sp1 = m.createComponent().setImplementation(new ServiceProvider(e)).setInterface(ServiceInterface.class.getName(), null);
+ Component sp2 = m.createComponent().setImplementation(new ServiceProvider(e)).setInterface(ServiceInterface.class.getName(), null);
+ ServiceConsumer sci = new ServiceConsumer(e);
+ Component sc = m.createComponent().setImplementation(sci).add(m.createServiceDependency().setService(ServiceInterface.class).setRequired(false).setCallbacks("add", "remove"));
+ Component sa2 = m.createAspectService(ServiceInterface.class, null, 20, null).setImplementation(new ServiceAspect(e, 3));
+ Component sa1 = m.createAspectService(ServiceInterface.class, null, 10, null).setImplementation(new ServiceAspect(e, 4));
+
+ // start with a service consumer
+ System.out.println("Adding consumer");
+ m.add(sc);
+
+ // then add two providers, so the consumer will see two services
+ System.out.println("Adding 2 providers");
+ m.add(sp1);
+ m.add(sp2);
+
+ // make sure consumer sees both services
+ Assert.assertEquals(2, sci.services());
+
+ // add an aspect with ranking 20
+ System.out.println("Adding aspect with rank 20");
+ m.add(sa2);
+
+ // make sure the consumer sees the two new aspects and no longer sees the two original services
+ Assert.assertEquals(2, sci.services());
+ Assert.assertEquals(20, sci.highestRanking());
+ Assert.assertEquals(20, sci.lowestRanking());
+
+ // add an aspect with ranking 10
+ System.out.println("Adding aspect with rank 10");
+ m.add(sa1);
+
+ // make sure the consumer still sees the two aspects with ranking 20
+ Assert.assertEquals(2, sci.services());
+ Assert.assertEquals(20, sci.highestRanking());
+ Assert.assertEquals(20, sci.lowestRanking());
+
+ // remove the aspect with ranking 20
+ System.out.println("Removing aspect with rank 20");
+ m.remove(sa2);
+
+ // make sure the consumer now sees the aspects with ranking 10
+ Assert.assertEquals(2, sci.services());
+ Assert.assertEquals(10, sci.highestRanking());
+ Assert.assertEquals(10, sci.lowestRanking());
+
+ // remove one of the original services
+ System.out.println("Removing 1 service");
+ m.remove(sp1);
+
+ // make sure the aspect of that service goes away
+ Assert.assertEquals(1, sci.services());
+ Assert.assertEquals(10, sci.highestRanking());
+ Assert.assertEquals(10, sci.lowestRanking());
+
+ // remove the aspect with ranking 10
+ System.out.println("Removing aspect with rank 10");
+ m.remove(sa1);
+
+ // make sure only the original service remains
+ Assert.assertEquals(1, sci.services());
+ Assert.assertEquals(0, sci.highestRanking());
+ Assert.assertEquals(0, sci.lowestRanking());
+
+ System.out.println("Done with test");
+
+ // end of test
+ m.remove(sa2);
+ m.remove(sp2);
+ m.remove(sc);
+ }
+
+ static interface ServiceInterface {
+ public void invoke(Runnable run);
+ }
+
+ static class ServiceProvider implements ServiceInterface {
+ private final Ensure m_ensure;
+ public ServiceProvider(Ensure e) {
+ m_ensure = e;
+ }
+ public void invoke(Runnable run) {
+ run.run();
+ }
+ }
+
+ static class ServiceAspect implements ServiceInterface {
+ private final Ensure m_ensure;
+ private volatile ServiceInterface m_parentService;
+ private final int m_step;
+
+ public ServiceAspect(Ensure e, int step) {
+ m_ensure = e;
+ m_step = step;
+ }
+ public void start() {
+ }
+
+ public void invoke(Runnable run) {
+ m_ensure.step(m_step);
+ m_parentService.invoke(run);
+ }
+
+ public void stop() {
+ }
+ }
+
+ static class ServiceConsumer implements Runnable {
+ private List m_services = new ArrayList();
+ private final Ensure m_ensure;
+
+ public ServiceConsumer(Ensure e) {
+ m_ensure = e;
+ }
+
+ public void init() {
+ Thread t = new Thread(this);
+ t.start();
+ }
+
+ public void run() {
+ }
+
+ public int services() {
+ return m_services.size();
+ }
+
+ public int highestRanking() {
+ int ranking = Integer.MIN_VALUE;
+ for (int i = 0; i < m_services.size(); i++) {
+ ServiceReference ref = (ServiceReference) m_services.get(i);
+ Integer r = (Integer) ref.getProperty(Constants.SERVICE_RANKING);
+ int rank = r == null ? 0 : r.intValue();
+ ranking = Math.max(ranking, rank);
+ }
+ return ranking;
+ }
+ public int lowestRanking() {
+ int ranking = Integer.MAX_VALUE;
+ for (int i = 0; i < m_services.size(); i++) {
+ ServiceReference ref = (ServiceReference) m_services.get(i);
+ Integer r = (Integer) ref.getProperty(Constants.SERVICE_RANKING);
+ int rank = r == null ? 0 : r.intValue();
+ ranking = Math.min(ranking, rank);
+ }
+ return ranking;
+ }
+
+ public void add(ServiceReference ref, ServiceInterface svc) {
+ System.out.println("Added: " + ServiceUtil.toString(ref));
+ m_services.add(ref);
+ }
+ public void remove(ServiceReference ref, ServiceInterface svc) {
+ System.out.println("Removed: " + ServiceUtil.toString(ref));
+ m_services.remove(ref);
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AspectWithCallbacksServiceDependencyTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AspectWithCallbacksServiceDependencyTest.java
new file mode 100644
index 0000000..c5116f2
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AspectWithCallbacksServiceDependencyTest.java
@@ -0,0 +1,134 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class AspectWithCallbacksServiceDependencyTest extends TestBase {
+ public void testServiceRegistrationAndConsumption() {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ // create a service provider and consumer
+ Component sp = m.createComponent().setImplementation(new ServiceProvider(e)).setInterface(ServiceInterface.class.getName(), null);
+ Component sc = m.createComponent().setImplementation(new ServiceConsumer(e)).add(m.createServiceDependency()
+ .setService(ServiceInterface.class)
+ .setCallbacks("add", "remove")
+ .setRequired(true));
+ Component asp = m.createAspectService(ServiceInterface.class, null, 100, "add", null, "remove", "swap")
+ .setImplementation(ServiceProviderAspect.class);
+ m.add(sp);
+ m.add(sc);
+ m.add(asp);
+ m.remove(asp);
+ m.remove(sc);
+ m.remove(sp);
+
+ // ensure we executed all steps inside the component instance
+ e.step(8);
+ }
+
+ static interface ServiceInterface {
+ public void invoke(String caller);
+ }
+
+ static class ServiceProvider implements ServiceInterface {
+ private final Ensure m_ensure;
+ public ServiceProvider(Ensure e) {
+ m_ensure = e;
+ }
+ public void invoke(String caller) {
+ if (caller.equals("consumer.init")) {
+ m_ensure.step(3);
+ } else if (caller.equals("aspect.consumer.add")) {
+ m_ensure.step(5);
+ }
+ }
+ }
+
+ static class ServiceProviderAspect implements ServiceInterface {
+ private volatile ServiceInterface m_service;
+
+ public ServiceProviderAspect() {
+ }
+
+ @Override
+ public void invoke(String caller) {
+ m_service.invoke("aspect." + caller);
+ }
+
+ public void add(ServiceInterface service) {
+ m_service = service;
+ }
+
+ public void remove(ServiceInterface service) {
+ m_service = null;
+ }
+
+ public void swap(ServiceInterface previous, ServiceInterface current) {
+ m_service = current;
+ }
+ }
+
+ static class ServiceConsumer {
+ private volatile ServiceInterface m_service;
+ private final Ensure m_ensure;
+ private int addCount = 0;
+
+ public ServiceConsumer(Ensure e) {
+ m_ensure = e;
+ }
+
+ public void init() {
+ m_ensure.step(2);
+ m_service.invoke("consumer.init");
+ }
+
+ public void destroy() {
+ m_ensure.step(7);
+ }
+
+ public void add(ServiceInterface service) {
+ m_service = service;
+ switch (addCount) {
+ case 0: m_ensure.step(1);
+ break;
+ case 1: m_ensure.step(4);
+ // aspect had been added
+ m_service.invoke("consumer.add");
+ break;
+ case 2: m_ensure.step(6);
+ break;
+ default:
+ }
+ addCount ++;
+ }
+ public void remove(ServiceInterface service) {
+ m_service = null;
+ }
+ }
+
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AspectWithPropagationTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AspectWithPropagationTest.java
new file mode 100644
index 0000000..f90c264
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AspectWithPropagationTest.java
@@ -0,0 +1,763 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+
+import org.junit.Assert;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.ServiceUtil;
+import org.apache.felix.dm.itest.util.TestBase;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+
+/**
+ * Test for aspects with service properties propagations.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+@SuppressWarnings({"rawtypes", "unchecked", "unused"})
+public class AspectWithPropagationTest extends TestBase {
+ private final static int ASPECTS = 3;
+ private final Set<Integer> _randoms = new HashSet<Integer>();
+ private final Random _rnd = new Random();
+ private static Ensure m_invokeStep;
+ private static Ensure m_changeStep;
+
+ /**
+ * This test does the following:
+ *
+ * - Create S service with property "p=s"
+ * - Create SA (aspect of S) with property "p=aspect"
+ * - Create Client, depending on S (actually, on SA).
+ * - Client should see SA with properties p=aspect
+ * - Change S service property with "p=smodified": the Client should be changed with SA(p=aspect)
+ * - Change aspect service property with "p=aspectmodified": The client should be changed with SA(p=aspectmodified)
+ */
+ public void testAspectsWithPropagationNotOverriding() {
+ System.out.println("----------- Running testAspectsWithPropagationNotOverriding ...");
+ DependencyManager m = getDM();
+ m_invokeStep = new Ensure();
+
+ // Create our original "S" service.
+ S s = new S() {
+ public void invoke() {
+ }
+ };
+ Dictionary props = new Hashtable();
+ props.put("p", "s");
+ Component sComp = m.createComponent()
+ .setImplementation(s)
+ .setInterface(S.class.getName(), props);
+
+ // Create SA (aspect of S)
+ S sa = new S() {
+ volatile S m_s;
+ public void invoke() {
+ }
+ };
+ Component saComp = m.createAspectService(S.class, null, 1).setImplementation(sa);
+ props = new Hashtable();
+ props.put("p", "aspect");
+ saComp.setServiceProperties(props);
+
+ // Create client depending on S
+ Object client = new Object() {
+ int m_changeCount;
+ void add(Map props, S s) {
+ Assert.assertEquals("aspect", props.get("p"));
+ m_invokeStep.step(1);
+ }
+
+ void change(Map props, S s) {
+ switch (++m_changeCount) {
+ case 1:
+ Assert.assertEquals("aspect", props.get("p"));
+ m_invokeStep.step(2);
+ break;
+ case 2:
+ Assert.assertEquals("aspectmodified", props.get("p"));
+ m_invokeStep.step(3);
+ }
+ }
+ };
+ Component clientComp = m.createComponent()
+ .add(m.createServiceDependency()
+ .setService(S.class)
+ .setRequired(true)
+ .setCallbacks("add", "change", null))
+ .setImplementation(client);
+
+ // Add components in dependency manager
+ m.add(sComp);
+ m.add(saComp);
+ m.add(clientComp);
+
+ // client should have been added with SA aspect
+ m_invokeStep.waitForStep(1, 5000);
+
+ // now change s "p=s" to "p=smodified": client should not see it
+ props = new Hashtable();
+ props.put("p", "smodified");
+ sComp.setServiceProperties(props);
+ m_invokeStep.waitForStep(2, 5000);
+
+ // now change sa aspect "p=aspect" to "p=aspectmodified": client should see it
+ props = new Hashtable();
+ props.put("p", "aspectmodified");
+ saComp.setServiceProperties(props);
+ m_invokeStep.waitForStep(3, 5000);
+
+ // remove components
+ m.remove(clientComp);
+ m.remove(saComp);
+ m.remove(sComp);
+ }
+
+ /**
+ * This test does the following:
+ *
+ * - Create S service
+ * - Create some S Aspects
+ * - Create a Client, depending on S (actually, on the top-level S aspect)
+ * - Client has a "change" callback in order to track S service properties modifications.
+ * - First, invoke Client.invoke(): all S aspects, and finally original S service must be invoked orderly.
+ * - Modify S original service properties, and check if all aspects, and the client has been orderly called in their "change" callback.
+ * - Modify the First lowest ranked aspect (rank=1), and check if all aspects, and client have been orderly called in their "change" callback.
+ */
+ public void testAspectsWithPropagation() {
+ System.out.println("----------- Running testAspectsWithPropagation ...");
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ m_invokeStep = new Ensure();
+
+ // Create our original "S" service.
+ Dictionary props = new Hashtable();
+ props.put("foo", "bar");
+ Component s = m.createComponent()
+ .setImplementation(new SImpl())
+ .setInterface(S.class.getName(), props);
+
+ // Create an aspect aware client, depending on "S" service.
+ Client clientImpl;
+ Component client = m.createComponent()
+ .setImplementation((clientImpl = new Client()))
+ .add(m.createServiceDependency()
+ .setService(S.class)
+ .setRequired(true)
+ .setCallbacks("add", "change", "remove", "swap"));
+
+ // Create some "S" aspects
+ Component[] aspects = new Component[ASPECTS];
+ for (int rank = 1; rank <= ASPECTS; rank ++) {
+ aspects[rank-1] = m.createAspectService(S.class, null, rank, "add", "change", "remove", "swap")
+ .setImplementation(new A("A" + rank, rank));
+ props = new Hashtable();
+ props.put("a" + rank, "v" + rank);
+ aspects[rank-1].setServiceProperties(props);
+ }
+
+ // Register client
+ m.add(client);
+
+ // Randomly register aspects and original service
+ boolean originalServiceAdded = false;
+ for (int i = 0; i < ASPECTS; i ++) {
+ int index = getRandomAspect();
+ m.add(aspects[index]);
+ if (! originalServiceAdded && _rnd.nextBoolean()) {
+ m.add(s);
+ originalServiceAdded = true;
+ }
+ }
+ if (! originalServiceAdded) {
+ m.add(s);
+ }
+
+ // All set, check if client has inherited from top level aspect properties + original service properties
+ Map check = new HashMap();
+ check.put("foo", "bar");
+ for (int i = 1; i < (ASPECTS - 1); i ++) {
+ check.put("a" + i, null); // we must not inherit from lower ranks, only from the top-level aspect.
+ }
+ check.put("a" + ASPECTS, "v" + ASPECTS);
+ checkServiceProperties(check, clientImpl.getServiceProperties());
+
+ // Now invoke client, which orderly calls all aspects in the chain, and finally the original service "S".
+ System.out.println("-------------------------- Invoking client.");
+ clientImpl.invoke();
+ m_invokeStep.waitForStep(ASPECTS+1, 5000);
+
+ // Now, change original service "S" properties: this will orderly trigger "change" callbacks on aspects, and on client.
+ System.out.println("-------------------------- Modifying original service properties.");
+ m_changeStep = new Ensure();
+ props = new Hashtable();
+ props.put("foo", "barModified");
+ s.setServiceProperties(props);
+
+ // Check if aspects and client have been orderly called in their "changed" callback
+ m_changeStep.waitForStep(ASPECTS+1, 5000);
+
+ // Check if modified "foo" original service property has been propagated
+ check = new HashMap();
+ check.put("foo", "barModified");
+ for (int i = 1; i < (ASPECTS - 1); i ++) {
+ check.put("a" + i, null); // we must not inherit from lower ranks, only from the top-level aspect.
+ }
+ check.put("a" + ASPECTS, "v" + ASPECTS); // we only see top-level aspect service properties
+ checkServiceProperties(check, clientImpl.getServiceProperties());
+
+ // Now, change the top-level ranked aspect: it must propagate to all upper aspects, as well as to the client
+ System.out.println("-------------------------- Modifying top-level aspect service properties.");
+
+ m_changeStep = new Ensure();
+ for (int i = 1; i <= ASPECTS; i ++) {
+ m_changeStep.step(i); // only client has to be changed.
+ }
+ props = new Hashtable();
+ props.put("a" + ASPECTS, "v" + ASPECTS + "-Modified");
+ aspects[ASPECTS-1].setServiceProperties(props); // That triggers change callbacks for upper aspects (with rank >= 2)
+ m_changeStep.waitForStep(ASPECTS+1, 5000); // check if client have been changed.
+
+ // Check if top level aspect service properties have been propagated up to the client.
+ check = new HashMap();
+ check.put("foo", "barModified");
+ for (int i = 1; i < (ASPECTS - 1); i ++) {
+ check.put("a" + i, null); // we must not inherit from lower ranks, only from the top-level aspect.
+ }
+ check.put("a" + ASPECTS, "v" + ASPECTS + "-Modified");
+ checkServiceProperties(check, clientImpl.getServiceProperties());
+
+ // Clear all components.
+ m_changeStep = null;
+ m.clear();
+ }
+
+ /**
+ * This test does the following:
+ *
+ * - Create S service
+ * - Create some S Aspects without any callbacks (add/change/remove/swap)
+ * - Create a Client, depending on S (actually, on the top-level S aspect)
+ * - Client has a "change" callack in order to track S service properties modifications.
+ * - First, invoke Client.invoke(): all S aspects, and finally original S service must be invoked orderly.
+ * - Modify S original service properties, and check if the client has been called in its "change" callback.
+ */
+ public void testAspectsWithPropagationAndNoCallbacks() {
+ System.out.println("----------- Running testAspectsWithPropagation ...");
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ m_invokeStep = new Ensure();
+
+ // Create our original "S" service.
+ Dictionary props = new Hashtable();
+ props.put("foo", "bar");
+ Component s = m.createComponent()
+ .setImplementation(new SImpl())
+ .setInterface(S.class.getName(), props);
+
+ // Create an aspect aware client, depending on "S" service.
+ Client clientImpl;
+ Component client = m.createComponent()
+ .setImplementation((clientImpl = new Client()))
+ .add(m.createServiceDependency()
+ .setService(S.class)
+ .setRequired(true)
+ .setCallbacks("add", "change", "remove"));
+
+ // Create some "S" aspects
+ Component[] aspects = new Component[ASPECTS];
+ for (int rank = 1; rank <= ASPECTS; rank ++) {
+ aspects[rank-1] = m.createAspectService(S.class, null, rank)
+ .setImplementation(new A("A" + rank, rank));
+ props = new Hashtable();
+ props.put("a" + rank, "v" + rank);
+ aspects[rank-1].setServiceProperties(props);
+ }
+
+ // Register client
+ m.add(client);
+
+ // Randomly register aspects and original service
+ boolean originalServiceAdded = false;
+ for (int i = 0; i < ASPECTS; i ++) {
+ int index = getRandomAspect();
+ m.add(aspects[index]);
+ if (! originalServiceAdded && _rnd.nextBoolean()) {
+ m.add(s);
+ originalServiceAdded = true;
+ }
+ }
+ if (! originalServiceAdded) {
+ m.add(s);
+ }
+
+ // All set, check if client has inherited from top level aspect properties + original service properties
+ Map check = new HashMap();
+ check.put("foo", "bar");
+ for (int i = 1; i < (ASPECTS - 1); i ++) {
+ check.put("a" + i, null); // we must not inherit from lower ranks, only from the top-level aspect.
+ }
+ check.put("a" + ASPECTS, "v" + ASPECTS);
+ checkServiceProperties(check, clientImpl.getServiceProperties());
+
+ // Now invoke client, which orderly calls all aspects in the chain, and finally the original service "S".
+ System.out.println("-------------------------- Invoking client.");
+ clientImpl.invoke();
+ m_invokeStep.waitForStep(ASPECTS+1, 5000);
+
+ // Now, change original service "S" properties: this will orderly trigger "change" callbacks on aspects, and on client.
+ System.out.println("-------------------------- Modifying original service properties.");
+ m_changeStep = new Ensure();
+ for (int i = 1; i <= ASPECTS; i ++) {
+ m_changeStep.step(i); // skip aspects, which have no "change" callbacks.
+ }
+ props = new Hashtable();
+ props.put("foo", "barModified");
+ s.setServiceProperties(props);
+
+ // Check if aspects and client have been orderly called in their "changed" callback
+ m_changeStep.waitForStep(ASPECTS+1, 5000);
+
+ // Check if modified "foo" original service property has been propagated
+ check = new HashMap();
+ check.put("foo", "barModified");
+ for (int i = 1; i < (ASPECTS - 1); i ++) {
+ check.put("a" + i, null); // we must not inherit from lower ranks, only from the top-level aspect.
+ }
+ check.put("a" + ASPECTS, "v" + ASPECTS); // we only see top-level aspect service properties
+ checkServiceProperties(check, clientImpl.getServiceProperties());
+
+ // Clear all components.
+ m_changeStep = null;
+ m.clear();
+ }
+
+ /**
+ * This test does the following:
+ *
+ * - Create S service
+ * - Create some S Aspects
+ * - Create S2 Adapter, which adapts S to S2
+ * - Create Client2, which depends on S2. Client2 listens to S2 property change events.
+ * - Now, invoke Client2.invoke(): all S aspects, and finally original S service must be invoked orderly.
+ * - Modify S original service properties, and check if all aspects, S2 Adapter, and Client2 have been orderly called in their "change" callback.
+ */
+ public void testAdapterWithAspectsAndPropagation() {
+ System.out.println("----------- Running testAdapterWithAspectsAndPropagation ...");
+
+ DependencyManager m = getDM();
+ m_invokeStep = new Ensure();
+
+ // Create our original "S" service.
+ Dictionary props = new Hashtable();
+ props.put("foo", "bar");
+ Component s = m.createComponent()
+ .setImplementation(new SImpl())
+ .setInterface(S.class.getName(), props);
+
+ // Create some "S" aspects
+ Component[] aspects = new Component[ASPECTS];
+ for (int rank = 1; rank <= ASPECTS; rank ++) {
+ aspects[rank-1] = m.createAspectService(S.class, null, rank, "add", "change", "remove", "swap")
+ .setImplementation(new A("A" + rank, rank));
+ props = new Hashtable();
+ props.put("a" + rank, "v" + rank);
+ aspects[rank-1].setServiceProperties(props);
+ }
+
+ // Create S2 adapter (which adapts S1 to S2 interface)
+ Component adapter = m.createAdapterService(S.class, null, "add", "change", "remove", "swap")
+ .setInterface(S2.class.getName(), null)
+ .setImplementation(new S2Impl());
+
+ // Create Client2, which depends on "S2" service.
+ Client2 client2Impl;
+ Component client2 = m.createComponent()
+ .setImplementation((client2Impl = new Client2()))
+ .add(m.createServiceDependency()
+ .setService(S2.class)
+ .setRequired(true)
+ .setCallbacks("add", "change", null));
+
+ // Register client2
+ m.add(client2);
+
+ // Register S2 adapter
+ m.add(adapter);
+
+ // Randomly register aspects, original service
+ boolean originalServiceAdded = false;
+ for (int i = 0; i < ASPECTS; i ++) {
+ int index = getRandomAspect();
+ m.add(aspects[index]);
+ if (! originalServiceAdded && _rnd.nextBoolean()) {
+ m.add(s);
+ originalServiceAdded = true;
+ }
+ }
+ if (! originalServiceAdded) {
+ m.add(s);
+ }
+
+ // Now invoke client2, which orderly calls all S1 aspects, then S1Impl, and finally S2 service
+ System.out.println("-------------------------- Invoking client2.");
+ client2Impl.invoke2();
+ m_invokeStep.waitForStep(ASPECTS+2, 5000);
+
+ // Now, change original service "S" properties: this will orderly trigger "change" callbacks on aspects, S2Impl, and Client2.
+ System.out.println("-------------------------- Modifying original service properties.");
+ m_changeStep = new Ensure();
+ props = new Hashtable();
+ props.put("foo", "barModified");
+ s.setServiceProperties(props);
+
+ // Check if aspects and Client2 have been orderly called in their "changed" callback
+ m_changeStep.waitForStep(ASPECTS+2, 5000);
+
+ // Check if modified "foo" original service property has been propagated to Client2
+ Map check = new HashMap();
+ check.put("foo", "barModified");
+ for (int i = 1; i < (ASPECTS - 1); i ++) {
+ check.put("a" + i, null); // we must not inherit from lower ranks, only from the top-level aspect.
+ }
+ check.put("a" + ASPECTS, "v" + ASPECTS);
+ checkServiceProperties(check, client2Impl.getServiceProperties());
+
+ // Clear all components.
+ m_changeStep = null;
+ m.clear();
+ }
+
+ /**
+ * This test does the following:
+ *
+ * - Create S service
+ * - Create some S Aspects without any callbacks (add/change/remove)
+ * - Create S2 Adapter, which adapts S to S2 (but does not have any add/change/remove callbacks)
+ * - Create Client2, which depends on S2. Client2 listens to S2 property change events.
+ * - Now, invoke Client2.invoke(): all S aspects, and finally original S service must be invoked orderly.
+ * - Modify S original service properties, and check if all aspects, S2 Adapter, and Client2 have been orderly called in their "change" callback.
+ */
+ public void testAdapterWithAspectsAndPropagationNoCallbacks() {
+ System.out.println("----------- Running testAdapterWithAspectsAndPropagationNoCallbacks ...");
+
+ DependencyManager m = getDM();
+ m_invokeStep = new Ensure();
+
+ // Create our original "S" service.
+ Dictionary props = new Hashtable();
+ props.put("foo", "bar");
+ Component s = m.createComponent()
+ .setImplementation(new SImpl())
+ .setInterface(S.class.getName(), props);
+
+ // Create some "S" aspects
+ Component[] aspects = new Component[ASPECTS];
+ for (int rank = 1; rank <= ASPECTS; rank ++) {
+ aspects[rank-1] = m.createAspectService(S.class, null, rank)
+ .setImplementation(new A("A" + rank, rank));
+ props = new Hashtable();
+ props.put("a" + rank, "v" + rank);
+ aspects[rank-1].setServiceProperties(props);
+ }
+
+ // Create S2 adapter (which adapts S1 to S2 interface)
+ Component adapter = m.createAdapterService(S.class, null)
+ .setInterface(S2.class.getName(), null)
+ .setImplementation(new S2Impl());
+
+ // Create Client2, which depends on "S2" service.
+ Client2 client2Impl;
+ Component client2 = m.createComponent()
+ .setImplementation((client2Impl = new Client2()))
+ .add(m.createServiceDependency()
+ .setService(S2.class)
+ .setRequired(true)
+ .setCallbacks("add", "change", null));
+
+ // Register client2
+ m.add(client2);
+
+ // Register S2 adapter
+ m.add(adapter);
+
+ // Randomly register aspects, original service
+ boolean originalServiceAdded = false;
+ for (int i = 0; i < ASPECTS; i ++) {
+ int index = getRandomAspect();
+ m.add(aspects[index]);
+ if (! originalServiceAdded && _rnd.nextBoolean()) {
+ m.add(s);
+ originalServiceAdded = true;
+ }
+ }
+ if (! originalServiceAdded) {
+ m.add(s);
+ }
+
+ // Now invoke client2, which orderly calls all S1 aspects, then S1Impl, and finally S2 service
+ System.out.println("-------------------------- Invoking client2.");
+ client2Impl.invoke2();
+ m_invokeStep.waitForStep(ASPECTS+2, 5000);
+
+ // Now, change original service "S" properties: this will orderly trigger "change" callbacks on aspects, S2Impl, and Client2.
+ System.out.println("-------------------------- Modifying original service properties.");
+ m_changeStep = new Ensure();
+ for (int i = 1; i <= ASPECTS+1; i ++) {
+ m_changeStep.step(i); // skip all aspects and the adapter
+ }
+ props = new Hashtable();
+ props.put("foo", "barModified");
+ s.setServiceProperties(props);
+
+ // Check if Client2 has been called in its "changed" callback
+ m_changeStep.waitForStep(ASPECTS+2, 5000);
+
+ // Check if modified "foo" original service property has been propagated to Client2
+ Map check = new HashMap();
+ check.put("foo", "barModified");
+ for (int i = 1; i < (ASPECTS - 1); i ++) {
+ check.put("a" + i, null); // we must not inherit from lower ranks, only from the top-level aspect.
+ }
+ check.put("a" + ASPECTS, "v" + ASPECTS);
+ checkServiceProperties(check, client2Impl.getServiceProperties());
+
+ // Clear all components.
+ m_changeStep = null;
+ m.clear();
+ }
+
+ private void checkServiceProperties(Map<?, ?> check, Dictionary properties) {
+ for (Object key : check.keySet()) {
+ Object val = check.get(key);
+ if (val == null) {
+ Assert.assertNull(properties.get(key));
+ } else {
+ Assert.assertEquals(val, properties.get(key));
+ }
+ }
+ }
+
+ private int getRandomAspect() {
+ int index = 0;
+ do {
+ index = _rnd.nextInt(ASPECTS);
+ } while (_randoms.contains(new Integer(index)));
+ _randoms.add(new Integer(index));
+ return index;
+ }
+
+ // S Service
+ public static interface S {
+ public void invoke();
+ }
+
+ // S ServiceImpl
+ static class SImpl implements S {
+ public SImpl() {
+ }
+
+ public String toString() {
+ return "S";
+ }
+
+ public void invoke() {
+ m_invokeStep.step(ASPECTS+1);
+ }
+ }
+
+ // S Aspect
+ static class A implements S {
+ private final String m_name;
+ private volatile ServiceRegistration m_registration;
+ private volatile S m_next;
+ private final int m_rank;
+
+ public A(String name, int rank) {
+ m_name = name;
+ m_rank = rank;
+ }
+
+ public String toString() {
+ return m_name;
+ }
+
+ public void invoke() {
+ int rank = ServiceUtil.getRanking(m_registration.getReference());
+ m_invokeStep.step(ASPECTS - rank + 1);
+ m_next.invoke();
+ }
+
+ public void add(ServiceReference ref, S s) {
+ System.out.println("+++ A" + m_rank + ".add:" + s + "/" + ServiceUtil.toString(ref));
+ m_next = s;
+ }
+
+ public void swap(ServiceReference oldSRef, S oldS, ServiceReference newSRef, S newS) {
+ System.out.println("+++ A" + m_rank + ".swap: new=" + newS + ", props=" + ServiceUtil.toString(newSRef));
+ Assert.assertTrue(m_next == oldS);
+ m_next = newS;
+ }
+
+ public void change(ServiceReference props, S s) {
+ System.out.println("+++ A" + m_rank + ".change: s=" + s + ", props=" + ServiceUtil.toString(props));
+ if (m_changeStep != null) {
+ int rank = ServiceUtil.getRanking(m_registration.getReference());
+ m_changeStep.step(rank);
+ }
+ }
+
+ public void remove(ServiceReference props, S s) {
+ System.out.println("+++ A" + m_rank + ".remove: " + s + ", props=" + ServiceUtil.toString(props));
+ }
+ }
+
+ // Aspect aware client, depending of "S" service aspects.
+ static class Client {
+ private volatile S m_s;
+ private volatile ServiceReference m_sRef;
+
+ public Client() {
+ }
+
+ public Dictionary getServiceProperties() {
+ Dictionary props = new Hashtable();
+ for (String key : m_sRef.getPropertyKeys()) {
+ props.put(key, m_sRef.getProperty(key));
+ }
+ return props;
+ }
+
+ public void invoke() {
+ m_s.invoke();
+ }
+
+ public String toString() {
+ return "Client";
+ }
+
+ public void add(ServiceReference ref, S s) {
+ System.out.println("+++ Client.add: " + s + "/" + ServiceUtil.toString(ref));
+ m_s = s;
+ m_sRef = ref;
+ }
+
+ public void swap(ServiceReference oldSRef, S oldS, ServiceReference newSRef, S newS) {
+ System.out.println("+++ Client.swap: m_s = " + m_s + ", old=" + oldS + ", oldProps=" + ServiceUtil.toString(oldSRef) + ", new=" + newS + ", props=" + ServiceUtil.toString(newSRef));
+ Assert.assertTrue(m_s == oldS);
+ m_s = newS;
+ m_sRef = newSRef;
+ }
+
+ public void change(ServiceReference properties, S s) {
+ System.out.println("+++ Client.change: s=" + s + ", props=" + ServiceUtil.toString(properties));
+ if (m_changeStep != null) {
+ m_changeStep.step(ASPECTS+1);
+ }
+ }
+
+ public void remove(ServiceReference props, S s) {
+ System.out.println("+++ Client.remove: " + s + ", props=" + ServiceUtil.toString(props));
+ }
+ }
+
+ // S2 Service
+ public static interface S2 {
+ public void invoke2();
+ }
+
+ // S2 impl, which adapts S1 interface to S2 interface
+ static class S2Impl implements S2 {
+ private volatile S m_s; // we shall see top-level aspect on S service
+
+ public void add(ServiceReference ref, S s) {
+ System.out.println("+++ S2Impl.add: " + s + "/" + ServiceUtil.toString(ref));
+ m_s = s;
+ }
+
+ public void swap(ServiceReference oldSRef, S oldS, ServiceReference newSRef, S newS) {
+ System.out.println("+++ S2Impl.swap: new=" + newS + ", props=" + ServiceUtil.toString(newSRef));
+ m_s = newS;
+ }
+
+ public void change(ServiceReference properties, S s) {
+ System.out.println("+++ S2Impl.change: s=" + s + ", props=" + ServiceUtil.toString(properties));
+ if (m_changeStep != null) {
+ m_changeStep.step(ASPECTS+1);
+ }
+ }
+
+ public void remove(ServiceReference props, S s) {
+ System.out.println("+++ S2Impl.remove: " + s + ", props=" + ServiceUtil.toString(props));
+ }
+
+ public void invoke2() {
+ m_s.invoke();
+ m_invokeStep.step(ASPECTS + 2); // All aspects, and S1Impl have been invoked
+ }
+
+ public String toString() {
+ return "S2";
+ }
+ }
+
+ // Client2 depending on S2.
+ static class Client2 {
+ private volatile S2 m_s2;
+ private volatile ServiceReference m_s2Ref;
+
+ public Dictionary getServiceProperties() {
+ Dictionary props = new Hashtable();
+ for (String key : m_s2Ref.getPropertyKeys()) {
+ props.put(key, m_s2Ref.getProperty(key));
+ }
+ return props;
+ }
+
+ public void invoke2() {
+ m_s2.invoke2();
+ }
+
+ public String toString() {
+ return "Client2";
+ }
+
+ public void add(ServiceReference ref, S2 s2) {
+ System.out.println("+++ Client2.add: " + s2 + "/" + ServiceUtil.toString(ref));
+ m_s2 = s2;
+ m_s2Ref = ref;
+ }
+
+ public void change(ServiceReference props, S2 s2) {
+ System.out.println("+++ Client2.change: s2=" + s2 + ", props=" + ServiceUtil.toString(props));
+ if (m_changeStep != null) {
+ m_changeStep.step(ASPECTS + 2); // S1Impl, all aspects, and S2 adapters have been changed before us.
+ }
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AutoConfigTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AutoConfigTest.java
new file mode 100644
index 0000000..0e2ca8f
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/AutoConfigTest.java
@@ -0,0 +1,251 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import org.junit.Assert;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+import org.osgi.framework.Constants;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+@SuppressWarnings({"unchecked", "rawtypes"})
+public class AutoConfigTest extends TestBase {
+ private final Ensure m_ensure = new Ensure();
+
+ public void testField() throws Exception {
+ final DependencyManager dm = getDM();
+ // Create a consumer, depending on some providers (autoconfig field).
+ ConsumeWithProviderField consumer = new ConsumeWithProviderField();
+ Component c = createConsumer(dm, consumer);
+ // Create two providers
+ Component p1 = createProvider(dm, 10, new Provider() {
+ public String toString() { return "provider1"; }
+ public void run() { m_ensure.step(); }
+ });
+ Component p2 = createProvider(dm, 20, new Provider() {
+ public String toString() { return "provider2"; }
+ public void run() { m_ensure.step(); }
+ });
+
+ // add the two providers
+ dm.add(p2);
+ dm.add(p1);
+ // add the consumer, which should have been injected with provider2 (highest rank)
+ dm.add(c);
+ m_ensure.waitForStep(1, 5000);
+ // remove the provider2, the consumer should now be injected with provider1
+ dm.remove(p2);
+ Assert.assertNotNull(consumer.getProvider());
+ Assert.assertEquals("provider1", consumer.getProvider().toString());
+ // remove the provider1, the consumer should have been stopped
+ dm.remove(p1);
+ m_ensure.waitForStep(2, 5000);
+ dm.clear();
+ }
+
+ public void testIterableField() throws Exception {
+ final DependencyManager dm = getDM();
+ ConsumerWithIterableField consumer = new ConsumerWithIterableField();
+ Component c = createConsumer(dm, consumer);
+ Component p1 = createProvider(dm, 10, new Provider() {
+ public void run() { m_ensure.step(); }
+ public String toString() { return "provider1"; }
+ });
+ Component p2 = createProvider(dm, 20, new Provider() {
+ public void run() { m_ensure.step();}
+ public String toString() { return "provider2"; }
+ });
+
+ dm.add(p2);
+ dm.add(p1);
+ dm.add(c);
+ // the consumer should have been injected with all providers.
+ m_ensure.waitForStep(3, 5000);
+
+ // check if all providers are there
+ Assert.assertNotNull(consumer.getProvider("provider1"));
+ Assert.assertNotNull(consumer.getProvider("provider2"));
+
+ // remove provider1
+ dm.remove(p1);
+
+ // check if provider1 has been removed and if provider2 is still there
+ Assert.assertNull(consumer.getProvider("provider1"));
+ Assert.assertNotNull(consumer.getProvider("provider2"));
+
+ // remove provider2, the consumer should be stopped
+ dm.remove(p2);
+ m_ensure.waitForStep(4, 5000);
+ dm.clear();
+ }
+
+ public void testMapField() throws Exception {
+ final DependencyManager dm = getDM();
+ ConsumerWithMapField consumer = new ConsumerWithMapField();
+ Component c = createConsumer(dm, consumer);
+ Component p1 = createProvider(dm, 10, new Provider() {
+ public void run() { m_ensure.step(); }
+ public String toString() { return "provider1"; }
+ });
+ Component p2 = createProvider(dm, 20, new Provider() {
+ public void run() { m_ensure.step();}
+ public String toString() { return "provider2"; }
+ });
+
+ dm.add(p2);
+ dm.add(p1);
+ dm.add(c);
+ // the consumer should have been injected with all providers.
+ m_ensure.waitForStep(3, 5000);
+
+ // check if all providers are there
+ Assert.assertNotNull(consumer.getProvider("provider1"));
+ Assert.assertNotNull(consumer.getProvider("provider2"));
+
+ // remove provider1
+ dm.remove(p1);
+
+ // check if provider1 has been removed and if provider2 is still there
+ Assert.assertNull(consumer.getProvider("provider1"));
+ Assert.assertNotNull(consumer.getProvider("provider2"));
+
+ // remove provider2, the consumer should be stopped
+ dm.remove(p2);
+ m_ensure.waitForStep(4, 5000);
+ dm.clear();
+ }
+
+ private Component createProvider(DependencyManager dm, int rank, Provider provider) {
+ Hashtable props = new Hashtable();
+ props.put(Constants.SERVICE_RANKING, new Integer(rank));
+ return dm.createComponent()
+ .setImplementation(provider)
+ .setInterface(Provider.class.getName(), props);
+ }
+
+ private Component createConsumer(DependencyManager dm, Object consumer) {
+ return dm.createComponent()
+ .setImplementation(consumer)
+ .add(dm.createServiceDependency().setService(Provider.class).setRequired(true));
+ }
+
+ public static interface Provider extends Runnable {
+ }
+
+ public class ConsumeWithProviderField {
+ volatile Provider m_provider;
+
+ void start() {
+ Assert.assertNotNull(m_provider);
+ Assert.assertEquals("provider2", m_provider.toString());
+ m_ensure.step(1);
+ }
+
+ public Provider getProvider() {
+ return m_provider;
+ }
+
+ void stop() {
+ m_ensure.step(2);
+ }
+ }
+
+ public class ConsumerWithIterableField {
+ final Iterable<Provider> m_providers = new ConcurrentLinkedQueue<>();
+
+ void start() {
+ Assert.assertNotNull(m_providers);
+ int found = 0;
+ for (Provider provider : m_providers) {
+ provider.run();
+ found ++;
+ }
+ Assert.assertTrue(found == 2);
+ m_ensure.step(3);
+ }
+
+ public Provider getProvider(String name) {
+ System.out.println("getProvider(" + name + ") : proviers=" + m_providers);
+ for (Provider provider : m_providers) {
+ if (provider.toString().equals(name)) {
+ return provider;
+ }
+ }
+ return null;
+ }
+
+ void stop() {
+ m_ensure.step(4);
+ }
+ }
+
+ public class ConsumerWithMapField {
+ final Map<Provider, Dictionary> m_providers = new ConcurrentHashMap<>();
+
+ void start() {
+ Assert.assertNotNull(m_providers);
+ System.out.println("ConsumerMap.start: injected providers=" + m_providers);
+ Assert.assertTrue(m_providers.size() == 2);
+ for (Map.Entry<Provider, Dictionary> e : m_providers.entrySet()) {
+ Provider provider = e.getKey();
+ Dictionary props = e.getValue();
+
+ provider.run();
+ if (provider.toString().equals("provider1")) {
+ Assert.assertEquals(props.get(Constants.SERVICE_RANKING), 10);
+ } else if (provider.toString().equals("provider2")) {
+ Assert.assertEquals(props.get(Constants.SERVICE_RANKING), 20);
+ } else {
+ Assert.fail("Did not find any properties for provider " + provider);
+ }
+ }
+
+ m_ensure.step(3);
+ }
+
+ public Provider getProvider(String name) {
+ System.out.println("getProvider(" + name + ") : providers=" + m_providers);
+ for (Provider provider : m_providers.keySet()) {
+ if (provider.toString().equals(name)) {
+ return provider;
+ }
+ }
+ return null;
+ }
+
+ Map<Provider, Dictionary> getProviders() {
+ return m_providers;
+ }
+
+ void stop() {
+ m_ensure.step(4);
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/BundleAdapterTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/BundleAdapterTest.java
new file mode 100644
index 0000000..9cd074a
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/BundleAdapterTest.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import org.junit.Assert;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.TestBase;
+import org.osgi.framework.Bundle;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class BundleAdapterTest extends TestBase {
+ public void testBundleAdapter() {
+ DependencyManager m = getDM();
+ // create a bundle adapter service (one is created for each bundle)
+ Component adapter = m.createBundleAdapterService(Bundle.INSTALLED | Bundle.RESOLVED | Bundle.ACTIVE, null, false)
+ .setImplementation(BundleAdapter.class)
+ .setInterface(BundleAdapter.class.getName(), null);
+
+ // create a service provider and consumer
+ Consumer c = new Consumer();
+ Component consumer = m.createComponent().setImplementation(c)
+ .add(m.createServiceDependency().setService(BundleAdapter.class).setCallbacks("add", "remove"));
+
+ // add the bundle adapter
+ m.add(adapter);
+ // add the service consumer
+ m.add(consumer);
+ // check if at least one bundle was found
+ c.check();
+ // remove the consumer again
+ m.remove(consumer);
+ // check if all bundles were removed correctly
+ c.doubleCheck();
+ // remove the bundle adapter
+ m.remove(adapter);
+ }
+
+ public static class BundleAdapter {
+ volatile Bundle m_bundle;
+
+ Bundle getBundle() {
+ return m_bundle;
+ }
+ }
+
+ static class Consumer {
+ private volatile int m_count = 0;
+
+ public void add(BundleAdapter ba) {
+ Bundle b = ba.getBundle();
+ System.out.println("Consumer.add(" + b.getSymbolicName() + ")");
+ Assert.assertNotNull("bundle instance must not be null", b);
+ m_count++;
+ }
+
+ public void check() {
+ Assert.assertTrue("we should have found at least one bundle", m_count > 0);
+ }
+
+ public void remove(BundleAdapter ba) {
+ Bundle b = ba.getBundle();
+ System.out.println("Consumer.remove(" + b.getSymbolicName() + ")");
+ m_count--;
+ }
+
+ public void doubleCheck() {
+ Assert.assertEquals("all bundles we found should have been removed again", 0, m_count);
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/BundleDependencyTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/BundleDependencyTest.java
new file mode 100644
index 0000000..f59d9b0
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/BundleDependencyTest.java
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import org.junit.Assert;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+import org.osgi.framework.Bundle;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class BundleDependencyTest extends TestBase {
+ private final static String BSN = "org.apache.felix.metatype";
+
+ public void testBundleDependencies() {
+ DependencyManager m = getDM();
+ // create a service provider and consumer
+ Consumer c = new Consumer();
+ Component consumer = m.createComponent().setImplementation(c).add(m.createBundleDependency().setCallbacks("add", "remove"));
+ // add the service consumer
+ m.add(consumer);
+ // check if at least one bundle was found
+ c.check();
+ // remove the consumer again
+ m.remove(consumer);
+ // check if all bundles were removed correctly
+ c.doubleCheck();
+
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ Component consumerWithFilter = m.createComponent().setImplementation(new FilteredConsumer(e)).add(m.createBundleDependency().setFilter("(Bundle-SymbolicName=" + BSN + ")").setCallbacks("add", "remove"));
+ // add a consumer with a filter
+ m.add(consumerWithFilter);
+ e.step(2);
+ // remove the consumer again
+ m.remove(consumerWithFilter);
+ e.step(4);
+ }
+
+ public void testRequiredBundleDependency() {
+ DependencyManager m = getDM();
+
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ Component consumerWithFilter = m.createComponent()
+ .setImplementation(new FilteredConsumerRequired(e))
+ .add(m.createBundleDependency()
+ .setRequired(true)
+ .setFilter("(Bundle-SymbolicName=" + BSN + ")")
+ .setCallbacks("add", "remove")
+ );
+ // add a consumer with a filter
+ m.add(consumerWithFilter);
+ e.waitForStep(1, 5000);
+ // remove the consumer again
+ m.remove(consumerWithFilter);
+ e.waitForStep(2, 5000);
+ }
+
+ static class Consumer {
+ private volatile int m_count = 0;
+
+ public void add(Bundle b) {
+ System.out.println("Consumer.add(" + b.getSymbolicName() + ")");
+ Assert.assertNotNull("bundle instance must not be null", b);
+ m_count++;
+ }
+
+ public void check() {
+ Assert.assertTrue("we should have found at least one bundle", m_count > 0);
+ }
+
+ public void remove(Bundle b) {
+ System.out.println("Consumer.remove(" + b.getSymbolicName() + ")");
+ m_count--;
+ }
+
+ public void doubleCheck() {
+ Assert.assertEquals("all bundles we found should have been removed again", 0, m_count);
+ }
+ }
+
+ static class FilteredConsumer {
+ private final Ensure m_ensure;
+
+ public FilteredConsumer(Ensure e) {
+ m_ensure = e;
+ }
+
+ public void add(Bundle b) {
+ m_ensure.step(1);
+ }
+
+ public void remove(Bundle b) {
+ m_ensure.step(3);
+ }
+ }
+
+ static class FilteredConsumerRequired {
+ private final Ensure m_ensure;
+
+ public FilteredConsumerRequired(Ensure e) {
+ m_ensure = e;
+ }
+
+ public void add(Bundle b) {
+ System.out.println("Bundle is " + b);
+// Assert.assertNotNull(b);
+ if (b.getSymbolicName().equals(BSN)) {
+ m_ensure.step(1);
+ }
+ }
+
+ public void remove(Bundle b) {
+ Assert.assertNotNull(b);
+ if (b.getSymbolicName().equals(BSN)) {
+ m_ensure.step(2);
+ }
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ComponentTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ComponentTest.java
new file mode 100644
index 0000000..bb4ca74
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ComponentTest.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.junit.Assert;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+@SuppressWarnings({"unchecked", "rawtypes"})
+public class ComponentTest extends TestBase {
+ private final Ensure m_ensure = new Ensure();
+
+ public void testSimple() throws Exception {
+ final DependencyManager dm = getDM();
+ Component consumer = dm.createComponent();
+ consumer
+ .setImplementation(new Consumer())
+ .add(dm.createServiceDependency()
+ .setService(Provider.class, "(name=provider2)")
+ .setRequired(true)
+ .setCallbacks("add", "remove"))
+ .add(dm.createServiceDependency()
+ .setService(Provider.class, "(name=provider1)")
+ .setRequired(true)
+ .setAutoConfig("m_autoConfiguredProvider"));
+
+ Dictionary props = new Hashtable();
+ props.put("name", "provider1");
+ Component provider1 = dm.createComponent()
+ .setImplementation(new Provider() { public String toString() { return "provider1";}})
+ .setInterface(Provider.class.getName(), props);
+ props = new Hashtable();
+ props.put("name", "provider2");
+ Component provider2 = dm.createComponent()
+ .setImplementation(new Provider() { public String toString() { return "provider2";}})
+ .setInterface(Provider.class.getName(), props);
+ dm.add(provider1);
+ dm.add(provider2);
+ dm.add(consumer);
+ m_ensure.waitForStep(2, 5000);
+ dm.remove(provider1);
+ dm.remove(provider2);
+ m_ensure.waitForStep(5, 5000);
+ dm.clear();
+ }
+
+ public static interface Provider {
+ }
+
+ public class Consumer {
+ Provider m_provider;
+ Provider m_autoConfiguredProvider;
+
+ void add(Map props, Provider provider) {
+ Assert.assertNotNull(provider);
+ Assert.assertEquals("provider2", props.get("name"));
+ m_provider = provider;
+ m_ensure.step(1);
+ }
+
+ void start() {
+ Assert.assertNotNull(m_autoConfiguredProvider);
+ Assert.assertEquals("provider1", m_autoConfiguredProvider.toString());
+ m_ensure.step(2);
+ }
+
+ void stop() {
+ m_ensure.step(3);
+ }
+
+ void destroy() {
+ m_ensure.step(4);
+ }
+
+ void remove(Provider provider) {
+ Assert.assertEquals(m_provider, provider);
+ m_ensure.step(5);
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ComponentTracker.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ComponentTracker.java
new file mode 100644
index 0000000..27d0b37
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ComponentTracker.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.ComponentState;
+import org.apache.felix.dm.ComponentStateListener;
+
+/**
+ * Helper class used to wait for a group of components to be started and stopped.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class ComponentTracker implements ComponentStateListener {
+
+ private final CountDownLatch m_startLatch;
+ private final CountDownLatch m_stopLatch;
+
+ public ComponentTracker(int startCount, int stopCount) {
+ m_startLatch = new CountDownLatch(startCount);
+ m_stopLatch = new CountDownLatch(stopCount);
+ }
+
+ @Override
+ public void changed(Component c, ComponentState state) {
+ switch (state) {
+ case TRACKING_OPTIONAL:
+ m_startLatch.countDown();
+ break;
+
+ case INACTIVE:
+ m_stopLatch.countDown();
+ break;
+
+ default:
+ }
+ }
+
+ public boolean awaitStarted(long millis) throws InterruptedException {
+ return m_startLatch.await(millis, TimeUnit.MILLISECONDS);
+ }
+
+ public boolean awaitStopped(long millis) throws InterruptedException {
+ return m_stopLatch.await(millis, TimeUnit.MILLISECONDS);
+ }
+
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/CompositionTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/CompositionTest.java
new file mode 100644
index 0000000..bfdcdd1
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/CompositionTest.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class CompositionTest extends TestBase {
+ public void testComposition() {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ // create a service provider and consumer
+ Component sp = m.createComponent().setImplementation(new ServiceProvider(e)).setInterface(ServiceInterface.class.getName(), null);
+ Component sc = m.createComponent().setImplementation(new ServiceConsumer(e))
+ .setComposition("getComposition")
+ .add(m.createServiceDependency().setService(ServiceInterface.class).setRequired(true).setCallbacks("add", null));
+ m.add(sp);
+ m.add(sc);
+ // ensure we executed all steps inside the component instance
+ e.step(6);
+ m.clear();
+ }
+
+ static interface ServiceInterface {
+ public void invoke();
+ }
+
+ static class ServiceProvider implements ServiceInterface {
+ private final Ensure m_ensure;
+ public ServiceProvider(Ensure e) {
+ m_ensure = e;
+ }
+ public void invoke() {
+ m_ensure.step(4);
+ }
+ }
+
+ static class ServiceConsumer {
+ private final Ensure m_ensure;
+ private ServiceConsumerComposite m_composite;
+ @SuppressWarnings("unused")
+ private ServiceInterface m_service;
+
+ public ServiceConsumer(Ensure e) {
+ m_ensure = e;
+ m_composite = new ServiceConsumerComposite(m_ensure);
+ }
+
+ public Object[] getComposition() {
+ return new Object[] { this, m_composite };
+ }
+
+ void add(ServiceInterface service) {
+ m_ensure.step(1);
+ m_service = service; // This method seems to not being called anymore
+ }
+
+ void start() {
+ m_composite.invoke();
+ m_ensure.step(5);
+ }
+ }
+
+ static class ServiceConsumerComposite {
+ ServiceInterface m_service;
+ private Ensure m_ensure;
+
+ ServiceConsumerComposite(Ensure ensure)
+ {
+ m_ensure = ensure;
+ }
+
+ void add(ServiceInterface service) {
+
+ m_ensure.step(2);
+ m_service = service;
+ }
+
+ void invoke()
+ {
+ m_ensure.step(3);
+ m_service.invoke();
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ConfigurationDependencyTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ConfigurationDependencyTest.java
new file mode 100644
index 0000000..d3a6aba
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ConfigurationDependencyTest.java
@@ -0,0 +1,209 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import java.io.IOException;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Properties;
+
+import org.junit.Assert;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
+
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+@SuppressWarnings({"unchecked", "rawtypes"})
+public class ConfigurationDependencyTest extends TestBase {
+ final static String PID = "ConfigurationDependencyTest.pid";
+
+ public void testComponentWithRequiredConfigurationAndServicePropertyPropagation() {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ // create a service provider and consumer
+ Component s1 = m.createComponent().setImplementation(new ConfigurationConsumer(e)).setInterface(Runnable.class.getName(), null).add(m.createConfigurationDependency().setPid(PID).setPropagate(true));
+ Component s2 = m.createComponent().setImplementation(new ConfigurationCreator(e)).add(m.createServiceDependency().setService(ConfigurationAdmin.class).setRequired(true));
+ Component s3 = m.createComponent().setImplementation(new ConfiguredServiceConsumer(e)).add(m.createServiceDependency().setService(Runnable.class, ("(testkey=testvalue)")).setRequired(true));
+ m.add(s1);
+ m.add(s2);
+ m.add(s3);
+ e.waitForStep(4, 50000000);
+ m.remove(s1);
+ m.remove(s2);
+ m.remove(s3);
+ // ensure we executed all steps inside the component instance
+ e.step(6);
+ }
+
+ public void testComponentWithRequiredConfigurationAndCallbackInstanceAndServicePropertyPropagation() {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ // create a service provider and consumer
+ ConfigurationConsumerCallbackInstance callbackInstance = new ConfigurationConsumerCallbackInstance(e);
+ Component s1 = m.createComponent().setImplementation(new ConfigurationConsumerWithCallbackInstance(e))
+ .setInterface(Runnable.class.getName(), null)
+ .add(m.createConfigurationDependency().setPid(PID).setPropagate(true).setCallback(callbackInstance, "updateConfiguration"));
+ Component s2 = m.createComponent().setImplementation(new ConfigurationCreator(e))
+ .add(m.createServiceDependency().setService(ConfigurationAdmin.class).setRequired(true));
+ Component s3 = m.createComponent().setImplementation(new ConfiguredServiceConsumer(e))
+ .add(m.createServiceDependency().setService(Runnable.class, ("(testkey=testvalue)")).setRequired(true));
+ m.add(s1);
+ m.add(s2);
+ m.add(s3);
+ e.waitForStep(4, 5000);
+ m.remove(s1);
+ m.remove(s2);
+ m.remove(s3);
+ // ensure we executed all steps inside the component instance
+ e.step(6);
+ }
+
+ public void testFELIX2987() {
+ // mimics testComponentWithRequiredConfigurationAndServicePropertyPropagation
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ // create a service provider and consumer
+ Component s1 = m.createComponent().setImplementation(new ConfigurationConsumer2(e)).setInterface(Runnable.class.getName(), null).add(m.createConfigurationDependency().setPid(PID).setPropagate(true));
+ Component s2 = m.createComponent().setImplementation(new ConfigurationCreator(e)).add(m.createServiceDependency().setService(ConfigurationAdmin.class).setRequired(true));
+ Component s3 = m.createComponent().setImplementation(new ConfiguredServiceConsumer(e)).add(m.createServiceDependency().setService(Runnable.class, ("(testkey=testvalue)")).setRequired(true));
+ m.add(s1);
+ m.add(s2);
+ m.add(s3);
+ e.waitForStep(4, 5000);
+ m.remove(s1);
+ m.remove(s2);
+ m.remove(s3);
+ // ensure we executed all steps inside the component instance
+ e.step(6);
+ }
+
+
+ class ConfigurationCreator {
+ private volatile ConfigurationAdmin m_ca;
+ private final Ensure m_ensure;
+ Configuration m_conf;
+
+ public ConfigurationCreator(Ensure e) {
+ m_ensure = e;
+ }
+
+ public void init() {
+ try {
+ warn("ConfigurationCreator.init");
+ Assert.assertNotNull(m_ca);
+ m_ensure.step(1);
+ m_conf = m_ca.getConfiguration(PID, null);
+ Hashtable props = new Properties();
+ props.put("testkey", "testvalue");
+ m_conf.update(props);
+ }
+ catch (IOException e) {
+ Assert.fail("Could not create configuration: " + e.getMessage());
+ }
+ }
+
+ public void destroy() throws IOException {
+ warn("ConfigurationCreator.destroy");
+ m_conf.delete();
+ m_ensure.step();
+ }
+ }
+
+ static class ConfigurationConsumer2 extends ConfigurationConsumer {
+ public ConfigurationConsumer2(Ensure e) {
+ super(e);
+ }
+ }
+
+ static class ConfigurationConsumer implements ManagedService, Runnable {
+ private final Ensure m_ensure;
+
+ public ConfigurationConsumer(Ensure e) {
+ m_ensure = e;
+ }
+
+ public void updated(Dictionary props) throws ConfigurationException {
+ if (props != null) {
+ m_ensure.step(2);
+ if (!"testvalue".equals(props.get("testkey"))) {
+ Assert.fail("Could not find the configured property.");
+ }
+ }
+ }
+
+ public void run() {
+ m_ensure.step(4);
+ }
+ }
+
+ static class ConfigurationConsumerCallbackInstance {
+ private final Ensure m_ensure;
+
+ public ConfigurationConsumerCallbackInstance(Ensure e) {
+ m_ensure = e;
+ }
+
+ public void updateConfiguration(Dictionary props) throws Exception {
+ if (props != null) {
+ m_ensure.step(2);
+ if (!"testvalue".equals(props.get("testkey"))) {
+ Assert.fail("Could not find the configured property.");
+ }
+ }
+ }
+ }
+
+ static class ConfigurationConsumerWithCallbackInstance implements Runnable {
+ private final Ensure m_ensure;
+
+ public ConfigurationConsumerWithCallbackInstance(Ensure e) {
+ m_ensure = e;
+ }
+
+ public void run() {
+ m_ensure.step(4);
+ }
+ }
+
+ static class ConfiguredServiceConsumer {
+ private final Ensure m_ensure;
+ private volatile Runnable m_runnable;
+
+ public ConfiguredServiceConsumer(Ensure e) {
+ m_ensure = e;
+ }
+
+ public void init() {
+ m_ensure.step(3);
+ m_runnable.run();
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/DynamicProxyAspectTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/DynamicProxyAspectTest.java
new file mode 100644
index 0000000..b5e5938
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/DynamicProxyAspectTest.java
@@ -0,0 +1,199 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+import org.junit.Assert;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+@SuppressWarnings({"rawtypes"})
+public class DynamicProxyAspectTest extends TestBase {
+ public void testImplementGenericAspectWithDynamicProxy() {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+
+ // create two service providers, each providing a different service interface
+ Component sp1 = m.createComponent().setImplementation(new ServiceProvider(e)).setInterface(ServiceInterface.class.getName(), null);
+ Component sp2 = m.createComponent().setImplementation(new ServiceProvider2(e)).setInterface(ServiceInterface2.class.getName(), null);
+
+ // create a dynamic proxy based aspect and hook it up to both services
+ Component a1 = m.createAspectService(ServiceInterface.class, null, 10, "m_service")
+ .setFactory(new Factory(e, ServiceInterface.class, "ServiceInterfaceProxy"), "create");
+ Component a2 = m.createAspectService(ServiceInterface2.class, null, 10, "m_service")
+ .setFactory(new Factory(e, ServiceInterface2.class, "ServiceInterfaceProxy2"), "create");
+
+ // create a client that invokes a method on boths services, validate that it goes
+ // through the proxy twice
+ Component sc = m.createComponent()
+ .setImplementation(new ServiceConsumer(e))
+ .add(m.createServiceDependency().setService(ServiceInterface.class).setRequired(true))
+ .add(m.createServiceDependency().setService(ServiceInterface2.class).setRequired(true))
+ ;
+
+ // register both producers, validate that both services are started
+ m.add(sp1);
+ e.waitForStep(1, 2000);
+ m.add(sp2);
+ e.waitForStep(2, 2000);
+
+ // add both aspects, and validate that both instances have been created
+ m.add(a1);
+ m.add(a2);
+ e.waitForStep(4, 4000);
+
+ // add the client, which will automatically invoke both services
+ m.add(sc);
+
+ // wait until both services have been invoked
+ e.waitForStep(6, 4000);
+
+ // make sure the proxy has been called twice
+ Assert.assertEquals("Proxy should have been invoked this many times.", 2, DynamicProxyHandler.getCounter());
+
+ m.remove(sc);
+ m.remove(a2);
+ m.remove(a1);
+ m.remove(sp2);
+ m.remove(sp1);
+ m.remove(a2);
+ m.remove(a1);
+
+ try {
+ Thread.sleep(2000);
+ }
+ catch (InterruptedException e1) {
+ // TODO Auto-generated catch block
+ e1.printStackTrace();
+ }
+ }
+
+ static interface ServiceInterface {
+ public void invoke(Runnable run);
+ }
+
+ static interface ServiceInterface2 {
+ public void invoke(Runnable run);
+ }
+
+ static class ServiceProvider implements ServiceInterface {
+ private final Ensure m_ensure;
+ public ServiceProvider(Ensure e) {
+ m_ensure = e;
+ }
+ public void start() {
+ m_ensure.step(1);
+ }
+ public void invoke(Runnable run) {
+ run.run();
+ }
+ }
+
+ static class ServiceProvider2 implements ServiceInterface2 {
+ private final Ensure m_ensure;
+ public ServiceProvider2(Ensure ensure) {
+ m_ensure = ensure;
+ }
+ public void start() {
+ m_ensure.step(2);
+ }
+ public void invoke(Runnable run) {
+ run.run();
+ }
+ }
+
+ static class ServiceConsumer implements Runnable {
+ private volatile ServiceInterface m_service;
+ private volatile ServiceInterface2 m_service2;
+ private final Ensure m_ensure;
+
+ public ServiceConsumer(Ensure e) {
+ m_ensure = e;
+ }
+
+ public void init() {
+ Thread t = new Thread(this);
+ t.start();
+ }
+
+ public void run() {
+ m_service.invoke(Ensure.createRunnableStep(m_ensure, 5));
+ m_service2.invoke(Ensure.createRunnableStep(m_ensure, 6));
+ }
+ }
+
+ static class DynamicProxyHandler implements InvocationHandler {
+ public volatile Object m_service; // ISSUE, we cannot inject into "Object" at the moment
+ private final String m_label;
+ private static volatile int m_counter = 0;
+
+ public DynamicProxyHandler(String label) {
+ m_label = label;
+ }
+
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ System.out.println("IIIIIIINVOKE--------------------------" + method.getName());
+ if (m_service == null) {
+ Assert.fail("No service was injected into dynamic proxy handler " + m_label);
+ }
+ Method m = m_service.getClass().getMethod(method.getName(), method.getParameterTypes());
+ if (m == null) {
+ Assert.fail("No method " + method.getName() + " was found in instance " + m_service + " in dynamic proxy handler " + m_label);
+ }
+ if (method.getName().equals("invoke")) {
+ // only count methods called 'invoke' because those are actually the ones
+ // both interfaces implement (and the dynamic proxy might be invoked for
+ // other methods, such as toString() as well)
+ m_counter++;
+ }
+ return m.invoke(m_service, args);
+ }
+
+ public static int getCounter() {
+ return m_counter;
+ }
+ }
+
+ static class Factory {
+ private final String m_label;
+ private Class m_class;
+ private final Ensure m_ensure;
+
+ public Factory(Ensure ensure, Class clazz, String label) {
+ m_ensure = ensure;
+ m_class = clazz;
+ m_label = label;
+ }
+
+ public Object create() {
+ m_ensure.step();
+ return Proxy.newProxyInstance(m_class.getClassLoader(), new Class[] { m_class }, new DynamicProxyHandler(m_label));
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX2078_ServiceDependencyTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX2078_ServiceDependencyTest.java
new file mode 100644
index 0000000..030bd0b
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX2078_ServiceDependencyTest.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class FELIX2078_ServiceDependencyTest extends TestBase {
+ public void testRequiredServiceRegistrationAndConsumption() {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ // create a service provider and consumer
+ Component sp = m.createComponent().setImplementation(new ServiceProvider(e)).setInterface(ServiceInterface.class.getName(), null);
+ Component sp2 = m.createComponent().setImplementation(new ServiceProvider(e)).setInterface(ServiceInterface.class.getName(), null);
+ Component sc = m.createComponent().setImplementation(new ServiceConsumer(e)).add(m.createServiceDependency().setService(ServiceInterface.class).setRequired(true).setCallbacks("add", "remove"));
+ m.add(sp);
+ m.add(sp2);
+ System.out.println("adding client");
+ m.add(sc);
+ System.out.println("waiting");
+ // wait until both services have been added to our consumer
+ e.waitForStep(2, 5000);
+ m.remove(sc);
+ m.remove(sp2);
+ m.remove(sp);
+ // ensure we executed all steps inside the component instance
+ }
+
+ public void testOptionalServiceRegistrationAndConsumption() {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ // create a service provider and consumer
+ Component sp = m.createComponent().setImplementation(new ServiceProvider(e)).setInterface(ServiceInterface.class.getName(), null);
+ Component sp2 = m.createComponent().setImplementation(new ServiceProvider(e)).setInterface(ServiceInterface.class.getName(), null);
+ Component sc = m.createComponent().setImplementation(new ServiceConsumer(e)).add(m.createServiceDependency().setService(ServiceInterface.class).setRequired(false).setCallbacks("add", "remove"));
+ m.add(sp);
+ m.add(sp2);
+ m.add(sc);
+ // wait until both services have been added to our consumer
+ e.waitForStep(2, 5000);
+ m.remove(sc);
+ m.remove(sp2);
+ m.remove(sp);
+ // ensure we executed all steps inside the component instance
+ }
+
+ static interface ServiceInterface {
+ public void invoke();
+ }
+
+ static class ServiceProvider implements ServiceInterface {
+ public ServiceProvider(Ensure e) {
+ }
+ public void invoke() {
+ }
+ }
+
+ static class ServiceConsumer {
+ private final Ensure m_ensure;
+
+ public ServiceConsumer(Ensure e) {
+ m_ensure = e;
+ }
+
+ public void add(ServiceInterface i) {
+ System.out.println("add " + i);
+ m_ensure.step();
+ }
+
+ public void remove(ServiceInterface i) {
+
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX2344_ExtraDependencyWithAutoConfigTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX2344_ExtraDependencyWithAutoConfigTest.java
new file mode 100644
index 0000000..b87f7d1
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX2344_ExtraDependencyWithAutoConfigTest.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import org.junit.Assert;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+@SuppressWarnings("unused")
+public class FELIX2344_ExtraDependencyWithAutoConfigTest extends TestBase {
+ /**
+ * Test if an auto config extra dependency is injected in the expected order.
+ */
+ public void testExtraDependencyWithAutoConfig() {
+ DependencyManager m = getDM();
+ // Helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ // Create a service provider
+ Component sp = m.createComponent().setInterface(ProviderInterface.class.getName(), null).setImplementation(ProviderImpl.class);
+ // Create a service consumer with a required/autoconfig dependency over the service provider.
+ Client c1;
+ Component sc1 = m.createComponent().setImplementation((c1 = new Client(e, true, 1)));
+ // Create a second service consumer with an optional/autoconfig dependency over the service provider.
+ Client c2;
+ Component sc2 = m.createComponent().setImplementation(c2 = new Client(e, false, 3));
+
+ // Add service provider and consumer sc1 (required dependency over provider)
+ m.add(sc1);
+ m.add(sp);
+ e.waitForStep(2, 5000);
+
+ // Remove provider and consumer
+ m.remove(sc1);
+ m.remove(sp);
+
+ // Add consumer sc2 (optional dependency over provider)
+ m.add(sc2);
+ e.waitForStep(4, 5000);
+ m.clear();
+ }
+
+ public interface ProviderInterface {
+ public boolean action();
+ }
+
+ public static class ProviderImpl implements ProviderInterface {
+ public boolean action()
+ {
+ return true;
+ }
+ }
+
+ // This client is not using callbacks, but instead, it uses auto config.
+ public static class Client {
+ volatile ProviderInterface m_provider;
+ private Ensure m_ensure;
+ private final boolean m_required;
+ private final int m_startStep;
+
+ public Client(Ensure e, boolean required, int startStep) {
+ m_ensure = e;
+ m_required = required;
+ m_startStep = startStep;
+ }
+
+ public void init(Component s) {
+ DependencyManager dm = s.getDependencyManager();
+ m_ensure.step(m_startStep);
+ s.add(dm.createServiceDependency()
+ .setService(ProviderInterface.class)
+ .setRequired(m_required)
+ .setAutoConfig("m_provider"));
+ }
+
+ public void start() {
+ // if required dependency: we must have been injected with the service provider
+ // else, we have been injected with a null object.
+ Assert.assertNotNull("provider has not been injected", m_provider);
+ Assert.assertEquals(m_required, m_provider.action()); // action returns false if null object
+ m_ensure.step();
+ }
+ }
+}
+
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX2344_ExtraDependencyWithCallbackTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX2344_ExtraDependencyWithCallbackTest.java
new file mode 100644
index 0000000..a28c8ab
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX2344_ExtraDependencyWithCallbackTest.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import org.junit.Assert;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+
+/**
+ * Tests for extra dependencies which are declared from service's init method.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class FELIX2344_ExtraDependencyWithCallbackTest extends TestBase {
+ /**
+ * Checks if an extra optional/required dependency is properly injected into a consumer, using callbacks.
+ */
+ public void testExtraDependencyWithCallback() {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ // create a service consumer and provider
+ Component sp = m.createComponent().setInterface(ProviderInterface.class.getName(), null).setImplementation(ProviderImpl.class);
+ Component sc = m.createComponent().setImplementation(new Client(e, false, 1));
+ Component sc2 = m.createComponent().setImplementation(new Client(e, true, 5));
+ Component sc3 = m.createComponent().setImplementation(new Client(e, true, 9));
+
+ // add the provider first, then add the consumer which initially will have no dependencies
+ // but via the init() method an optional dependency with a callback method will be added
+ m.add(sp);
+ m.add(sc);
+ // remove the consumer again
+ m.remove(sc);
+ e.waitForStep(4, 5000);
+
+ // next up, add a second consumer, identical to the first, but with a required dependency
+ // with a callback method which will be added in the init() method
+ m.add(sc2);
+ // remove the consumer again
+ m.remove(sc2);
+ e.waitForStep(8, 5000);
+
+ // now remove the provider, add a third consumer, identical to the second, and after the
+ // consumer has started, add the provider again
+ m.remove(sp);
+ m.add(sc3);
+ m.add(sp);
+ e.waitForStep(12, 5000);
+ m.clear();
+ }
+
+ public interface ProviderInterface {
+ }
+
+ public static class ProviderImpl implements ProviderInterface {
+ }
+
+ public static class Client {
+ ProviderInterface m_provider;
+ private Ensure m_ensure;
+ private final boolean m_required;
+ private final int m_startStep;
+
+ public Client(Ensure e, boolean required, int startStep) {
+ m_ensure = e;
+ m_required = required;
+ m_startStep = startStep;
+ }
+
+ public void init(Component s) {
+ DependencyManager dm = s.getDependencyManager();
+ m_ensure.step(m_startStep);
+ s.add(dm.createServiceDependency()
+ .setService(ProviderInterface.class)
+ .setRequired(m_required)
+ .setCallbacks("bind", null));
+ }
+
+ // called before start() for required dependency, or after start for optional dependency
+ void bind(ProviderInterface provider) {
+ System.out.println("bind");
+ m_ensure.step(m_required ? m_startStep + 1 : m_startStep + 3);
+ m_provider = provider;
+ }
+
+ public void start() {
+ System.out.println("start");
+ m_ensure.step(m_required ? m_startStep + 2: m_startStep + 1);
+ if (m_required) {
+ Assert.assertNotNull("Dependendency should have been injected", m_provider);
+ }
+ m_ensure.step(m_required ? m_startStep + 3: m_startStep + 2);
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX2348_ResourceAdapterTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX2348_ResourceAdapterTest.java
new file mode 100644
index 0000000..fbf592d
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX2348_ResourceAdapterTest.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.URL;
+
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.ResourceHandler;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+import org.junit.Assert;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class FELIX2348_ResourceAdapterTest extends TestBase {
+ public void testBasicResourceAdapter() throws Exception {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ m.add(m.createResourceAdapterService("(&(path=/path/to/*.txt)(host=localhost))", false, null, "changed")
+ .setImplementation(new ResourceAdapter(e)));
+ URL resourceURL = new URL("file://localhost/path/to/file1.txt");
+ m.add(m.createComponent().setImplementation(new ResourceProvider(context, resourceURL))
+ .add(m.createServiceDependency().setService(ResourceHandler.class).setCallbacks("add", "remove")));
+ e.waitForStep(3, 5000);
+ m.clear();
+ }
+
+ static class ResourceAdapter {
+ protected URL m_resource; // injected by reflection.
+ private Ensure m_ensure;
+
+ ResourceAdapter(Ensure e) {
+ m_ensure = e;
+ }
+
+ public void start() {
+ m_ensure.step(1);
+ Assert.assertNotNull("resource not injected", m_resource);
+ m_ensure.step(2);
+ try {
+ m_resource.openStream();
+ }
+ catch (FileNotFoundException e) {
+ m_ensure.step(3);
+ }
+ catch (IOException e) {
+ Assert.fail("We should not have gotten this exception.");
+ }
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX2369_ExtraDependencyTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX2369_ExtraDependencyTest.java
new file mode 100644
index 0000000..bb1ff01
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX2369_ExtraDependencyTest.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import org.junit.Assert;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.ServiceDependency;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+
+/**
+ * This testcase verify that a Service is not started if one of its extra required dependencies
+ * is unavailable.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class FELIX2369_ExtraDependencyTest extends TestBase
+{
+ public void testExtraDependencies() {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ // create a service consumer and provider
+ Component sp1 = m.createComponent().setInterface(MyService1.class.getName(), null).setImplementation(new MyService1Impl());
+ Component sc = m.createComponent().setImplementation(new MyClient(e, 1));
+
+ // provides the MyService1 service (but not the MyService2, which is required by MyClient).
+ m.add(sp1);
+ // add MyClient (it should not be invoked in its start() method because MyService2 is not there
+ m.add(sc);
+ // remove MyClient (it should not be invoked in its stop() method because it should not be active, since MyService2 is not there.
+ m.remove(sc);
+ e.waitForStep(2, 5000);
+ m.clear();
+ }
+
+ public interface MyService1 {
+ }
+
+ public interface MyService2 {
+ }
+
+ public static class MyService1Impl implements MyService1 {
+ }
+
+ public static class MyService2Impl implements MyService2 {
+ }
+
+ // This client is not using callbacks, but instead, it uses auto config.
+ public static class MyClient {
+ MyService1 m_myService2; // required/unavailable
+ private Ensure m_ensure;
+ private final int m_startStep;
+
+ public MyClient(Ensure e, int startStep) {
+ m_ensure = e;
+ m_startStep = startStep;
+ }
+
+ public void init(Component s) {
+ DependencyManager dm = s.getDependencyManager();
+ m_ensure.step(m_startStep);
+ ServiceDependency d1 =
+ dm.createServiceDependency() // this dependency is available at this point
+ .setService(MyService1.class)
+ .setRequired(false)
+ .setCallbacks("bind", null);
+ ServiceDependency d2 =
+ dm.createServiceDependency() // not available: we should not be started
+ .setService(MyService2.class)
+ .setRequired(true)
+ .setAutoConfig("m_myService2");
+
+ s.add(d1, d2); // atomically add these two dependencies
+ }
+
+ public void start() {
+ Assert.fail("start should not be called since MyService2 is unavailable");
+ }
+
+ void bind(MyService1 s1) { // optional/available
+ System.out.println("bound MyService1");
+ }
+
+ public void stop() {
+ Assert.fail("stop should not be called since we should not be active at this point");
+ }
+
+ public void destroy() {
+ m_ensure.step(m_startStep+1);
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX2696_ConfigurationAndServiceDependencyTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX2696_ConfigurationAndServiceDependencyTest.java
new file mode 100644
index 0000000..6f2ea65
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX2696_ConfigurationAndServiceDependencyTest.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import java.io.IOException;
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.junit.Assert;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
+
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+@SuppressWarnings("rawtypes")
+public class FELIX2696_ConfigurationAndServiceDependencyTest extends TestBase {
+ final static String PID = "FELIX2696_ConfigurationAndServiceDependencyTest.pid";
+
+ public void testComponentWithRequiredConfigurationAndServicePropertyPropagation() {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ // create a service provider and consumer
+ Component s1 = m.createComponent()
+ .setImplementation(new ConfigurationConsumer(e))
+ .add(m.createServiceDependency().setService(ServiceInterface.class).setRequired(true))
+ .add(m.createConfigurationDependency().setPid(PID));
+ Component s2 = m.createComponent()
+ .setImplementation(new ConfigurationCreator(e))
+ .add(m.createServiceDependency().setService(ConfigurationAdmin.class).setRequired(true));
+ Component s3 = m.createComponent()
+ .setInterface(ServiceInterface.class.getName(), null)
+ .setImplementation(DependentServiceProvider.class);
+ m.add(s1);
+ m.add(s2);
+ m.add(s3);
+ e.waitForStep(2, 5000);
+ warn("removing s3");
+ m.remove(s3);
+ warn("readding s3");
+ m.add(s3);
+ // after adding the required dependency again, the issue in FELIX-2696 means that the
+ // updated() method is not invoked for the new instance, and init() is, so our step
+ // count will only go up to 3 (not 4) causing this test to fail
+ e.waitForStep(4, 5000);
+ m.remove(s3);
+ m.remove(s2);
+ m.remove(s1);
+ e.waitForStep(5, 5000);
+ }
+
+ public static class ConfigurationCreator {
+ private volatile ConfigurationAdmin m_ca;
+ private final Ensure m_ensure;
+ Configuration m_conf;
+
+ public ConfigurationCreator(Ensure e) {
+ m_ensure = e;
+ }
+
+ @SuppressWarnings("unchecked")
+ public void init() {
+ try {
+ m_conf = m_ca.getConfiguration(PID, null);
+ Hashtable props = new Hashtable();
+ props.put("testkey", "testvalue");
+ m_conf.update(props);
+ }
+ catch (IOException e) {
+ Assert.fail("Could not create configuration: " + e.getMessage());
+ }
+ }
+
+ public void destroy() throws IOException {
+ m_conf.delete();
+ m_ensure.step(5);
+ }
+ }
+
+ public class ConfigurationConsumer implements ManagedService {
+ private final Ensure m_ensure;
+
+ public ConfigurationConsumer(Ensure e) {
+ m_ensure = e;
+ }
+
+ public void updated(Dictionary props) throws ConfigurationException {
+ warn("Consumer: updated %s", props);
+ if (props != null) {
+ if (!"testvalue".equals(props.get("testkey"))) {
+ Assert.fail("Could not find the configured property.");
+ }
+ m_ensure.step();
+ }
+ }
+
+ public void init() {
+ warn("Consumer: init");
+ m_ensure.step();
+ }
+ }
+
+ public static interface ServiceInterface {
+ }
+
+ public static class DependentServiceProvider implements ServiceInterface {
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX2875_ServiceDependencyWithoutServiceNameTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX2875_ServiceDependencyWithoutServiceNameTest.java
new file mode 100644
index 0000000..3cc2a90
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX2875_ServiceDependencyWithoutServiceNameTest.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class FELIX2875_ServiceDependencyWithoutServiceNameTest extends TestBase {
+ Ensure m_e;
+
+ public void testServiceDependencyWithoutName() {
+ m_e = new Ensure();
+ DependencyManager dm = getDM();
+ Component consumer = dm.createComponent()
+ .setImplementation(new ConsumeServiceDependencyWithoutName())
+ .add(dm.createServiceDependency()
+ .setService("(provider=*)").setRequired(true)
+ .setCallbacks("add", null))
+ .add(dm.createServiceDependency()
+ .setService("(|(provider=provider1)(provider=provider2))").setRequired(true)
+ .setAutoConfig("m_providers"));
+
+ Hashtable<String, String> props = new Hashtable<>();
+ props.put("provider", "provider1");
+ Component provider1 = dm.createComponent()
+ .setImplementation(new Provider1())
+ .setInterface(Provider.class.getName(), props);
+
+ props = new Hashtable<>();
+ props.put("provider", "provider2");
+ Component provider2 = dm.createComponent()
+ .setImplementation(new Provider2())
+ .setInterface(Provider.class.getName(), props);
+
+ dm.add(provider1);
+ dm.add(provider2);
+ dm.add(consumer);
+ m_e.waitForStep(5, 5000);
+ dm.clear();
+ }
+
+ private class ConsumeServiceDependencyWithoutName {
+ volatile Map<Object, Dictionary<String, String>> m_providers; // autoconfig
+
+ @SuppressWarnings("unused")
+ void add(Map<String, Object> props, Object service) {
+ if ("provider1".equals(props.get("provider"))) {
+ m_e.step();
+ } else if ("provider2".equals(props.get("provider"))) {
+ m_e.step();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ void start() {
+ // check if all providers have been injected in our autoconfig field.
+ for (Map.Entry<Object, Dictionary<String, String>> e : m_providers.entrySet()) {
+ if ("provider1".equals(e.getValue().get("provider"))) {
+ m_e.step();
+ } else if ("provider2".equals(e.getValue().get("provider"))) {
+ m_e.step();
+ }
+ }
+ m_e.step(5);
+ }
+ }
+
+ public interface Provider {
+ }
+
+ public class Provider1 implements Provider {
+ }
+
+ public class Provider2 implements Provider {
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX2955_ShellCommandTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX2955_ShellCommandTest.java
new file mode 100644
index 0000000..4ee902a
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX2955_ShellCommandTest.java
@@ -0,0 +1,165 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+import org.junit.Assert;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+import org.apache.felix.service.command.CommandProcessor;
+import org.apache.felix.service.command.CommandSession;
+import org.osgi.framework.Bundle;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class FELIX2955_ShellCommandTest extends TestBase {
+ private long m_myBundleId;
+ private Bundle m_testBundle;
+
+ public void testShellCommands() throws Throwable {
+ try {
+ m_myBundleId = context.getBundle().getBundleId();
+ for (Bundle b : context.getBundles()) {
+ if (b.getSymbolicName().equals("org.apache.felix.dependencymanager.itest.bundle")) {
+ m_testBundle = b;
+ b.stop();
+ break;
+ }
+ }
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+
+ Component shellClient = m.createComponent();
+ Component missing = m.createComponent();
+
+ long shellClientId = shellClient.getComponentDeclaration().getId();
+ long missingId = missing.getComponentDeclaration().getId();
+ shellClient.setImplementation(new ShellClient(e, shellClientId, missingId))
+ .add(m.createServiceDependency()
+ .setService(CommandProcessor.class)
+ .setRequired(true));
+
+ m.add(shellClient);
+ e.waitForStep(3, 5000);
+ // now create a component with a missing dependency
+ missing.setImplementation(new Object() { public String toString() { return "Object"; }})
+ .add(m.createServiceDependency()
+ .setService(Missing.class) // Warning: don't use Object, or Runnable, which are already registered by bndtools ?
+ .setRequired(true));
+
+ m.add(missing);
+ e.step(4);
+ e.waitForStep(5, 5000);
+ m.remove(missing);
+ // now start/stop our test bundle, which publishes a service that uses the dependency manager
+ m_testBundle.start();
+ m_testBundle.stop();
+ e.step(6);
+ e.waitForStep(7, 5000);
+ e.ensure();
+ m.remove(shellClient);
+ m_testBundle.start(); // restart the runtime bundle
+ m.clear();
+ }
+
+ catch (Throwable t) {
+ error("test failed", t);
+ }
+ }
+
+ public class ShellClient {
+ volatile CommandProcessor m_commandProcessor;
+ private final Ensure m_ensure;
+ private final long m_shellClientId;
+ private final long m_missingId;
+
+ public ShellClient(Ensure e, long shellClientId, long missingId) {
+ m_ensure = e;
+ m_shellClientId = shellClientId;
+ m_missingId = missingId;
+ }
+
+ public void start() throws InterruptedException {
+ Thread t = new Thread("Shell Client") {
+ public void run() {
+ String bsn = context.getBundle().getSymbolicName();
+ m_ensure.step(1);
+ execute("dm bid " + m_myBundleId,
+ "[" + m_myBundleId + "] " + bsn + "\n" +
+ " [" + m_shellClientId + "] ShellClient registered\n" +
+ " org.apache.felix.service.command.CommandProcessor service required available\n",
+ "");
+
+ m_ensure.step(2);
+ // see if there's anything that's not available
+ execute("dm notavail bid " + m_myBundleId,
+ "",
+ "");
+ m_ensure.step(3);
+ // check again, now there should be something missing
+ m_ensure.waitForStep(4, 5000);
+ execute("dm notavail bid " + m_myBundleId,
+ "[" + m_myBundleId + "] " + bsn + "\n" +
+ " [" + m_missingId + "] Object unregistered\n" +
+ " " + Missing.class.getName() + " service required unavailable\n",
+ "");
+ m_ensure.step(5);
+ m_ensure.waitForStep(6, 5000);
+ // this next step actually triggers the bug in FELIX-2955
+ execute("dm notavail bid " + m_myBundleId,
+ "",
+ "");
+ m_ensure.step(7);
+ }
+ };
+ t.start();
+ }
+
+ @Override
+ public String toString() {
+ return "ShellClient";
+ }
+
+ public void execute(String command, String expectedOutput, String expectedError) {
+ try {
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ ByteArrayOutputStream error = new ByteArrayOutputStream();
+ CommandSession session = m_commandProcessor.createSession(System.in, new PrintStream(output), new PrintStream(error));
+ session.execute(command);
+
+ String out = output.toString();
+ Assert.assertEquals(expectedOutput, out.toString());
+ Assert.assertEquals(expectedError, error.toString());
+ }
+ catch (Throwable throwable) {
+ m_ensure.throwable(throwable);
+ }
+ }
+ }
+
+ public static class Missing {
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX3008_FilterIndexStartupTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX3008_FilterIndexStartupTest.java
new file mode 100644
index 0000000..f5b3f41
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX3008_FilterIndexStartupTest.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+//import static org.ops4j.pax.exam.CoreOptions.waitForFrameworkStartupFor;
+//import static org.ops4j.pax.exam.container.def.PaxRunnerOptions.vmOption;
+
+import org.junit.Assert;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+import org.osgi.framework.Bundle;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class FELIX3008_FilterIndexStartupTest extends TestBase {
+ public void testNormalStart() throws Exception {
+ System.setProperty("org.apache.felix.dependencymanager.filterindex", "objectClass");
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ // create a provider
+ Provider provider = new Provider();
+ // activate it
+ Component p = m.createComponent()
+ .setInterface(Service.class.getName(), null)
+ .setImplementation(provider);
+
+ Consumer consumer = new Consumer(e);
+ Component c = m.createComponent()
+ .setImplementation(consumer)
+ .add(m.createServiceDependency()
+ .setService(Service.class)
+ .setRequired(true)
+ );
+
+ m.add(p);
+ m.add(c);
+ e.waitForStep(1, 5000);
+ m.remove(p);
+ e.waitForStep(2, 5000);
+ m.remove(c);
+
+ Assert.assertEquals("Dependency manager bundle should be active.", Bundle.ACTIVE, context.getBundle().getState());
+ }
+
+ public static class Consumer {
+ volatile Service m_service;
+ private final Ensure m_ensure;
+
+ public Consumer(Ensure e) {
+ m_ensure = e;
+ }
+
+ public void start() {
+ System.out.println("start");
+ m_ensure.step(1);
+ }
+
+ public void stop() {
+ System.out.println("stop");
+ m_ensure.step(2);
+ }
+ }
+
+ public static interface Service {
+ }
+
+ public static class Provider implements Service {
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX3057_EmptyServiceReferenceArray.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX3057_EmptyServiceReferenceArray.java
new file mode 100644
index 0000000..07ce09b
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX3057_EmptyServiceReferenceArray.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+//import static org.ops4j.pax.exam.CoreOptions.waitForFrameworkStartupFor;
+//import static org.ops4j.pax.exam.container.def.PaxRunnerOptions.vmOption;
+
+import org.junit.Assert;
+
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.TestBase;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class FELIX3057_EmptyServiceReferenceArray extends TestBase {
+ public void testWithoutIndex() throws Exception {
+ executeTest(context);
+ }
+
+ public void testWithIndex() throws Exception {
+ System.setProperty(DependencyManager.SERVICEREGISTRY_CACHE_INDICES, "objectClass");
+ executeTest(context);
+ }
+
+ private void executeTest(BundleContext context) throws InvalidSyntaxException {
+ DependencyManager m = getDM();
+ Assert.assertNull("Looking up a non-existing service should return null.", m.getBundleContext().getServiceReferences(Service.class.getName(), "(objectClass=*)"));
+ Assert.assertNull("Looking up a non-existing service should return null.", m.getBundleContext().getAllServiceReferences(Service.class.getName(), "(objectClass=*)"));
+ Assert.assertNull("Looking up a non-existing service should return null.", m.getBundleContext().getServiceReference(Service.class.getName()));
+ }
+
+ /** Dummy interface for lookup. */
+ public static interface Service {}
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX3337_UpdatedConfigurationDependencyWithPropagationTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX3337_UpdatedConfigurationDependencyWithPropagationTest.java
new file mode 100644
index 0000000..368acb8
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX3337_UpdatedConfigurationDependencyWithPropagationTest.java
@@ -0,0 +1,151 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import java.io.IOException;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Properties;
+
+import org.junit.Assert;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
+
+
+/**
+ * This test validates the following scenario:
+ * - Service S1 depends on a ConfigurationDependency with propagate = true
+ * - Service S2 depends on S1 (and has access to the S1 configuration using the S1 service
+ * properties (because the ConfigurationDependency is propagated)
+ * - then the S1 PID is updated from ConfigAdmin
+ * - S1 is then called in its updated callback
+ * - S2 is called in its "change" callback.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+@SuppressWarnings({"unchecked", "rawtypes"})
+public class FELIX3337_UpdatedConfigurationDependencyWithPropagationTest extends TestBase {
+ /*
+ * This Pojo creates the configuration pid "test".
+ */
+ static class ConfigurationCreator {
+ private volatile ConfigurationAdmin m_ca;
+ org.osgi.service.cm.Configuration m_conf;
+
+ public void init() {
+ try {
+ m_conf = m_ca.getConfiguration("test", null);
+ Hashtable props = new Properties();
+ props.put("testkey", "testvalue");
+ m_conf.update(props);
+ }
+ catch (IOException e) {
+ Assert.fail("Could not create configuration: " + e.getMessage());
+ }
+ }
+
+ public void update() {
+ try {
+ Hashtable props = new Properties();
+ props.put("testkey", "testvalue");
+ props.put("testkey2", "testvalue2");
+ m_conf.update(props);
+ } catch (IOException e) {
+ Assert.fail("Could not update the configured property: " + e.toString());
+ }
+ }
+ }
+
+ static class S1 implements ManagedService {
+ private Ensure m_ensure;
+ private boolean m_initialized;
+
+ public S1(Ensure e) {
+ m_ensure = e;
+ }
+
+ public void updated(Dictionary props) throws ConfigurationException {
+ if (props != null) {
+ if (!m_initialized) {
+ m_ensure.step(1);
+ m_initialized = true;
+ } else {
+ // we are updated
+ m_ensure.step(3);
+ }
+ }
+ }
+ }
+
+ static class S2 {
+ private final Ensure m_ensure;
+
+ public S2(Ensure e) {
+ m_ensure = e;
+ }
+
+ public void add(S1 s1) {
+ m_ensure.step(2);
+ }
+
+ public void change(S1 runnable) {
+ m_ensure.step(4);
+ }
+ }
+
+ public void testComponentWithRequiredUpdatedConfigurationAndServicePropertyPropagation() {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ ConfigurationCreator confCreator = new ConfigurationCreator();
+ Component s1 = m.createComponent()
+ .setImplementation(new S1(e))
+ .setInterface(S1.class.getName(), null)
+ .add(m.createConfigurationDependency()
+ .setPid("test")
+ .setPropagate(true));
+ Component s2 = m.createComponent()
+ .setImplementation(new S2(e))
+ .add(m.createServiceDependency()
+ .setService(S1.class, ("(testkey=testvalue)"))
+ .setRequired(true)
+ .setCallbacks("add", "change", null));
+ Component s3 = m.createComponent()
+ .setImplementation(confCreator)
+ .add(m.createServiceDependency()
+ .setService(ConfigurationAdmin.class)
+ .setRequired(true));
+
+ m.add(s1);
+ m.add(s2);
+ m.add(s3);
+ e.waitForStep(2, 5000);
+ confCreator.update();
+ e.waitForStep(4, 5000);
+ m.remove(s1);
+ m.remove(s2);
+ m.remove(s3);
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX4158_DependencyDeclarationTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX4158_DependencyDeclarationTest.java
new file mode 100644
index 0000000..11edb49
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX4158_DependencyDeclarationTest.java
@@ -0,0 +1,161 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import org.junit.Assert;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.ComponentDeclaration;
+import org.apache.felix.dm.ComponentDependencyDeclaration;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.TestBase;
+import org.osgi.framework.Bundle;
+import org.osgi.service.log.LogService;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class FELIX4158_DependencyDeclarationTest extends TestBase {
+ public void testServiceDependencyDeclaration() {
+ DependencyManager m = getDM();
+ Component c = m.createComponent()
+ .setImplementation(new Object())
+ .add(m.createServiceDependency().setService(LogService.class, "(foo=bar)"));
+
+ ComponentDeclaration cd = c.getComponentDeclaration();
+ ComponentDependencyDeclaration[] cdds = cd.getComponentDependencies();
+ Assert.assertNotNull(cdds);
+ Assert.assertNotNull(cdds.length == 1);
+ Assert.assertEquals(cdds[0].getName(), "org.osgi.service.log.LogService (foo=bar)");
+ Assert.assertEquals(cdds[0].getSimpleName(), "org.osgi.service.log.LogService");
+ Assert.assertNotNull(cdds[0].getFilter());
+ Assert.assertEquals(cdds[0].getFilter(), "(foo=bar)");
+ m.clear();
+ }
+
+ public void testConfigurationDependencyDeclaration() {
+ DependencyManager m = getDM();
+ Component c = m.createComponent()
+ .setImplementation(new Object())
+ .add(m.createConfigurationDependency().setPid("foo"));
+
+ ComponentDeclaration cd = c.getComponentDeclaration();
+ ComponentDependencyDeclaration[] cdds = cd.getComponentDependencies();
+ Assert.assertNotNull(cdds);
+ Assert.assertNotNull(cdds.length == 1);
+ Assert.assertEquals(cdds[0].getName(), "foo");
+ Assert.assertEquals(cdds[0].getSimpleName(), "foo");
+ Assert.assertNull(cdds[0].getFilter());
+ m.clear();
+ }
+
+ public void testResourceDependencyDeclaration() throws MalformedURLException {
+ DependencyManager m = getDM();
+ Component c = m.createComponent()
+ .setImplementation(new Object())
+ .add(m.createResourceDependency()
+ .setResource(new URL("file://localhost/path/to/file1.txt")));
+
+ ComponentDeclaration cd = c.getComponentDeclaration();
+ ComponentDependencyDeclaration[] cdds = cd.getComponentDependencies();
+ Assert.assertNotNull(cdds);
+ Assert.assertNotNull(cdds.length == 1);
+ Assert.assertEquals(cdds[0].getName(), "file://localhost/path/to/file1.txt");
+ Assert.assertNotNull(cdds[0].getSimpleName());
+ Assert.assertEquals(cdds[0].getSimpleName(), "file://localhost/path/to/file1.txt");
+ Assert.assertNull(cdds[0].getFilter());
+ m.clear();
+ }
+
+ public void testResourceDependencyDeclarationWithFilter() {
+ DependencyManager m = getDM();
+ Component c = m.createComponent()
+ .setImplementation(new Object())
+ .add(m.createResourceDependency()
+ .setFilter("(&(path=/path/to/*.txt)(host=localhost))"));
+
+ ComponentDeclaration cd = c.getComponentDeclaration();
+ ComponentDependencyDeclaration[] cdds = cd.getComponentDependencies();
+ Assert.assertNotNull(cdds);
+ Assert.assertNotNull(cdds.length == 1);
+ Assert.assertEquals(cdds[0].getName(), ("(&(path=/path/to/*.txt)(host=localhost))"));
+ Assert.assertNull(cdds[0].getSimpleName());
+ Assert.assertNotNull(cdds[0].getFilter());
+ Assert.assertEquals(cdds[0].getFilter(), "(&(path=/path/to/*.txt)(host=localhost))");
+ m.clear();
+ }
+
+ public void testBundleDependencyDeclaration() throws MalformedURLException {
+ DependencyManager m = getDM();
+ Component c = m.createComponent()
+ .setImplementation(new Object())
+ .add(m.createBundleDependency());
+
+ ComponentDeclaration cd = c.getComponentDeclaration();
+ ComponentDependencyDeclaration[] cdds = cd.getComponentDependencies();
+ Assert.assertNotNull(cdds);
+ Assert.assertNotNull(cdds.length == 1);
+ Assert.assertEquals(cdds[0].getName(), "active installed resolved");
+ Assert.assertNotNull(cdds[0].getSimpleName());
+ Assert.assertEquals(cdds[0].getSimpleName(), "active installed resolved");
+ Assert.assertNull(cdds[0].getFilter());
+ m.clear();
+ }
+
+ public void testBundleDependencyDeclarationWithMask() throws MalformedURLException {
+ DependencyManager m = getDM();
+ Component c = m.createComponent()
+ .setImplementation(new Object())
+ .add(m.createBundleDependency()
+ .setStateMask( Bundle.ACTIVE | Bundle.RESOLVED));
+
+ ComponentDeclaration cd = c.getComponentDeclaration();
+ ComponentDependencyDeclaration[] cdds = cd.getComponentDependencies();
+ Assert.assertNotNull(cdds);
+ Assert.assertNotNull(cdds.length == 1);
+ Assert.assertEquals(cdds[0].getName(), "active resolved");
+ Assert.assertNotNull(cdds[0].getSimpleName());
+ Assert.assertEquals(cdds[0].getSimpleName(), "active resolved");
+ Assert.assertNull(cdds[0].getFilter());
+ m.clear();
+ }
+
+ public void testBundleDependencyDeclarationWithFilter() throws MalformedURLException {
+ DependencyManager m = getDM();
+ Component c = m.createComponent()
+ .setImplementation(new Object())
+ .add(m.createBundleDependency()
+ .setStateMask( Bundle.ACTIVE )
+ .setFilter("(DependencyManager-Component=*)"));
+
+ ComponentDeclaration cd = c.getComponentDeclaration();
+ ComponentDependencyDeclaration[] cdds = cd.getComponentDependencies();
+ Assert.assertNotNull(cdds);
+ Assert.assertNotNull(cdds.length == 1);
+ Assert.assertEquals(cdds[0].getName(), "active (DependencyManager-Component=*)");
+ Assert.assertNotNull(cdds[0].getSimpleName());
+ Assert.assertEquals(cdds[0].getSimpleName(), "active");
+ Assert.assertNotNull(cdds[0].getFilter());
+ Assert.assertEquals(cdds[0].getFilter(), "(DependencyManager-Component=*)");
+ m.clear();
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX4361_ConcurrentComponentListingTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX4361_ConcurrentComponentListingTest.java
new file mode 100644
index 0000000..0752c3f
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX4361_ConcurrentComponentListingTest.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.junit.Assert;
+
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.TestBase;
+
+
+/**
+ * Test for FELIX-4361 The DependencyManager.getComponents method failed in a concurrent situation on iterating the
+ * result of the method.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class FELIX4361_ConcurrentComponentListingTest extends TestBase {
+
+ @SuppressWarnings("rawtypes")
+ public void testConcurrentGetComponentsManipulation() {
+ DependencyManager dm = getDM();
+ dm.add(dm.createComponent().setImplementation(Object.class));
+ Iterator iterator = dm.getComponents().iterator();
+ dm.add(dm.createComponent().setImplementation(Object.class));
+ iterator.next();
+ dm.clear();
+ }
+
+ public void testConcurrentGetComponentsMultipleThreads() {
+ final DependencyManager m = getDM();
+ final AtomicInteger errors = new AtomicInteger(0);
+ final AtomicInteger componentsAdded = new AtomicInteger(0);
+ final int max = 10000;
+ final AtomicBoolean isRunning = new AtomicBoolean(true);
+
+ ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() + 1);
+ Runnable readTask = new Runnable() {
+ @SuppressWarnings({ "rawtypes", "unused" })
+ public void run() {
+ while (isRunning.get()) {
+ try {
+ List components = m.getComponents();
+ for (Object component : components) {
+ // Just iterating the components should check for concurrent modifications
+ }
+ }
+ catch (Exception ex) {
+ errors.addAndGet(1);
+ ex.printStackTrace();
+ }
+ }
+ }
+ };
+
+ Callable<Boolean> modifyTask = new Callable<Boolean>() {
+ public Boolean call() throws Exception {
+ try {
+ m.add(m.createComponent().setImplementation(Object.class));
+ componentsAdded.addAndGet(1);
+ return true;
+ }
+ catch (Exception ex) {
+ return false;
+ }
+ }
+ };
+
+ executorService.submit(readTask);
+ for (int i = 0; i < max; i++) {
+ executorService.submit(modifyTask);
+ }
+ isRunning.set(false);
+ executorService.shutdown();
+
+ try {
+ executorService.awaitTermination(30, TimeUnit.SECONDS);
+ }
+ catch (InterruptedException e) {
+ }
+ Assert.assertEquals(0, errors.get());
+ Assert.assertEquals(max, componentsAdded.get());
+ m.clear();
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX_4168AdapterWithDynamicallyAddedDependencies.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX_4168AdapterWithDynamicallyAddedDependencies.java
new file mode 100644
index 0000000..cffdcf9
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX_4168AdapterWithDynamicallyAddedDependencies.java
@@ -0,0 +1,110 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class FELIX_4168AdapterWithDynamicallyAddedDependencies extends TestBase {
+ public void testAdapterWithExtraDependenciesAndCallbacks() {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+
+ // create a service S2, which will be added to A1 (needs to be available)
+ Component s2 = m.createComponent().setInterface(S2.class.getName(), null).setImplementation(new S2Impl(e));
+ m.add(s2);
+
+ // create a service adapter that adapts to services S1 and has an optional dependency on services S2
+ Component sa = m.createAdapterService(S1.class, null).setImplementation(SA.class);
+ m.add(sa);
+
+ // create a service S1, which triggers the creation of the first adapter instance (A1)
+ Component s1 = m.createComponent().setInterface(S1.class.getName(), null).setImplementation(new S1Impl());
+ m.add(s1);
+
+ // create a second service S1, which triggers the creation of the second adapter instance (A2)
+ Component s1b = m.createComponent().setInterface(S1.class.getName(), null).setImplementation(new S1Impl());
+ m.add(s1b);
+
+ // observe that S2 is also added to A2
+ e.waitForStep(2, 5000);
+
+ // remove S2 again
+ m.remove(s2);
+
+ // make sure both adapters have their "remove" callbacks invoked
+ e.waitForStep(4, 5000);
+ m.clear();
+ }
+
+ static interface S1 {
+ }
+
+ static interface S2 {
+ public void invoke();
+ }
+
+ static class S1Impl implements S1 {
+ }
+
+ static class S2Impl implements S2 {
+
+ private final Ensure m_e;
+
+ public S2Impl(Ensure e) {
+ m_e = e;
+ }
+
+ public void invoke() {
+ m_e.step();
+ }
+ }
+
+ public static class SA {
+ volatile S2 s2;
+ volatile Component component;
+ volatile DependencyManager manager;
+
+ public SA() {
+ System.out.println("Adapter created");
+ }
+
+ public void init() {
+ System.out.println("Adapter init " + s2);
+ component.add(manager.createServiceDependency()
+ .setService(S2.class).setCallbacks("add", "remove").setRequired(true));
+ }
+
+ public void add(S2 s) {
+ System.out.println("adding " + s);
+ s.invoke();
+ }
+
+ public void remove(S2 s) {
+ System.out.println("removing " + s);
+ s.invoke();
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FactoryConfigurationAdapterTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FactoryConfigurationAdapterTest.java
new file mode 100644
index 0000000..315b3b8
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FactoryConfigurationAdapterTest.java
@@ -0,0 +1,224 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import java.io.IOException;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.junit.Assert;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+import org.osgi.service.cm.ConfigurationAdmin;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+@SuppressWarnings({"unchecked", "rawtypes", "serial"})
+public class FactoryConfigurationAdapterTest extends TestBase
+{
+ private static Ensure m_ensure;
+
+ public void testFactoryConfigurationAdapter() {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ m_ensure = new Ensure();
+
+ // Create a Configuration instance, which will create/update/remove a configuration for factoryPid "MyFactoryPid"
+ ConfigurationCreator configurator = new ConfigurationCreator("MyFactoryPid", "key", "value1");
+ Component s1 = m.createComponent()
+ .setImplementation(configurator)
+ .add(m.createServiceDependency()
+ .setService(ConfigurationAdmin.class)
+ .setRequired(true));
+
+ // Create an Adapter that will be instantiated, once the configuration is created.
+ // This Adapter provides an AdapterService, and depends on an AdapterExtraDependency service.
+ Component s2 = m.createFactoryConfigurationAdapterService("MyFactoryPid", "updated", true /* propagate CM settings */)
+ .setInterface(AdapterService.class.getName(), new Hashtable() {{ put("foo", "bar"); }})
+ .setImplementation(Adapter.class);
+
+ s2.add(m.createServiceDependency()
+ .setService(AdapterExtraDependency.class)
+ .setRequired(true)
+ .setAutoConfig(true));
+
+ // Create extra adapter service dependency upon which our adapter depends on.
+ Component s3 = m.createComponent()
+ .setImplementation(new AdapterExtraDependency())
+ .setInterface(AdapterExtraDependency.class.getName(), null);
+
+ // Create an AdapterService Consumer
+ Component s4 = m.createComponent()
+ .setImplementation(AdapterServiceConsumer.class)
+ .add(m.createServiceDependency()
+ .setService(AdapterService.class)
+ .setRequired(true)
+ .setCallbacks("bind", "change", "remove"));
+
+ // Start services
+ m.add(s1);
+ m.add(s2);
+ m.add(s3);
+ m.add(s4);
+
+ // Wait for step 8: the AdapterService consumer has been injected with the AdapterService, and has called the doService method.
+ m_ensure.waitForStep(8, 10000);
+
+ // Modify configuration.
+ configurator.update("key", "value2");
+
+ // Wait for step 13: the AdapterService has been updated, and the AdapterService consumer has seen the change
+ m_ensure.waitForStep(13, 10000);
+
+ // Remove the configuration
+ m.remove(s1); // The stop method will remove the configuration
+ m_ensure.waitForStep(16, 10000);
+ m.clear();
+ }
+
+ public static class ConfigurationCreator {
+ private volatile ConfigurationAdmin m_ca;
+ private String m_key;
+ private String m_value;
+ private org.osgi.service.cm.Configuration m_conf;
+ private String m_factoryPid;
+
+ public ConfigurationCreator(String factoryPid, String key, String value) {
+ m_factoryPid = factoryPid;
+ m_key = key;
+ m_value = value;
+ }
+
+ public void start() {
+ try {
+ m_ensure.step(1);
+ m_conf = m_ca.createFactoryConfiguration(m_factoryPid, null);
+ Hashtable props = new Hashtable();
+ props.put(m_key, m_value);
+ m_conf.update(props);
+ }
+ catch (IOException e) {
+ Assert.fail("Could not create configuration: " + e.getMessage());
+ }
+ }
+
+ public void update(String key, String val) {
+ Hashtable props = new Hashtable();
+ props.put(key, val);
+ try {
+ m_conf.update(props);
+ }
+ catch (IOException e) {
+ Assert.fail("Could not update configuration: " + e.getMessage());
+ }
+ }
+
+ public void stop() {
+ try
+ {
+ m_conf.delete();
+ }
+ catch (IOException e)
+ {
+ Assert.fail("Could not remove configuration: " + e.toString());
+ }
+ }
+ }
+
+ public interface AdapterService {
+ public void doService();
+ }
+
+ public static class AdapterExtraDependency {
+ }
+
+ public static class Adapter implements AdapterService {
+ volatile AdapterExtraDependency m_extraDependency; // extra dependency.
+ private int updateCount;
+
+ void updated(Dictionary settings) {
+ updateCount ++;
+ if (updateCount == 1) {
+ m_ensure.step(2);
+ Assert.assertEquals(true, "value1".equals(settings.get("key")));
+ m_ensure.step(3);
+ } else if (updateCount == 2) {
+ m_ensure.step(9);
+ Assert.assertEquals(true, "value2".equals(settings.get("key")));
+ m_ensure.step(10);
+ } else {
+ Assert.fail("wrong call to updated method: count=" + updateCount);
+ }
+ }
+
+ public void doService() {
+ m_ensure.step(8);
+ }
+
+ public void start() {
+ m_ensure.step(4);
+ Assert.assertNotNull(m_extraDependency);
+ m_ensure.step(5);
+ }
+
+ public void stop() {
+ m_ensure.step(16);
+ }
+ }
+
+ public static class AdapterServiceConsumer {
+ private AdapterService m_adapterService;
+ private Map m_adapterServiceProperties;
+
+ void bind(Map serviceProperties, AdapterService adapterService) {
+ m_ensure.step(6);
+ m_adapterService = adapterService;
+ m_adapterServiceProperties = serviceProperties;
+ }
+
+ void change(Map serviceProperties, AdapterService adapterService) {
+ m_ensure.step(11);
+ Assert.assertEquals(true, "value2".equals(m_adapterServiceProperties.get("key")));
+ m_ensure.step(12);
+ Assert.assertEquals(true, "bar".equals(m_adapterServiceProperties.get("foo")));
+ m_ensure.step(13);
+ }
+
+ public void start() {
+ m_ensure.step(7);
+ Assert.assertNotNull(m_adapterService);
+ Assert.assertEquals(true, "value1".equals(m_adapterServiceProperties.get("key")));
+ Assert.assertEquals(true, "bar".equals(m_adapterServiceProperties.get("foo")));
+ m_adapterService.doService();
+ }
+
+ public void stop() {
+ m_ensure.step(14);
+ }
+
+ void remove(AdapterService adapterService) {
+ m_ensure.step(15);
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FilterIndexResourceAdapterTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FilterIndexResourceAdapterTest.java
new file mode 100644
index 0000000..56437d9
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FilterIndexResourceAdapterTest.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.ResourceHandler;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+import org.junit.Assert;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class FilterIndexResourceAdapterTest extends TestBase {
+ public void testBasicResourceAdapter() throws Exception {
+ System.setProperty("org.apache.felix.dependencymanager.filterindex", "objectClass");
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ // create a resource provider
+ ResourceProvider provider = new ResourceProvider(context, new URL("file://localhost/path/to/file1.txt"));
+ // activate it
+ m.add(m.createComponent().setImplementation(provider).add(m.createServiceDependency().setService(ResourceHandler.class).setCallbacks("add", "remove")));
+ // create a resource adapter for our single resource
+ // note that we can provide an actual implementation instance here because there will be only one
+ // adapter, normally you'd want to specify a Class here
+ m.add(m.createResourceAdapterService("(&(path=/path/to/*.txt)(host=localhost))", false, null, "changed")
+ .setImplementation(new ResourceAdapter(e)));
+ // wait until the single resource is available
+ e.waitForStep(3, 5000);
+ // trigger a 'change' in our resource
+ provider.change();
+ // wait until the changed callback is invoked
+ e.waitForStep(4, 5000);
+ m.clear();
+ }
+
+ static class ResourceAdapter {
+ protected URL m_resource; // injected by reflection.
+ private Ensure m_ensure;
+
+ ResourceAdapter(Ensure e) {
+ m_ensure = e;
+ }
+
+ public void start() {
+ m_ensure.step(1);
+ Assert.assertNotNull("resource not injected", m_resource);
+ m_ensure.step(2);
+ try {
+ @SuppressWarnings("unused")
+ InputStream in = m_resource.openStream();
+ }
+ catch (FileNotFoundException e) {
+ m_ensure.step(3);
+ }
+ catch (IOException e) {
+ Assert.fail("We should not have gotten this exception.");
+ }
+ }
+
+ public void changed() {
+ m_ensure.step(4);
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/InstanceBoundDependencyTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/InstanceBoundDependencyTest.java
new file mode 100644
index 0000000..988588d
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/InstanceBoundDependencyTest.java
@@ -0,0 +1,159 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.junit.Assert;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * This test does some injection tests on components being in INSTANTIATED_AND_WAITING_FOR_REQUIRED state.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+@SuppressWarnings({"unchecked", "rawtypes"})
+public class InstanceBoundDependencyTest extends TestBase {
+ Ensure m_e;
+
+ public void testServiceInjection() {
+ DependencyManager m = getDM();
+ m_e = new Ensure();
+
+ // Create a "C" component: it depends on some S1 services, and on some S2 instance-bound services (declared from C.init() method)
+ C cimpl = new C();
+ Component c = m.createComponent().setImplementation(cimpl);
+ c.add(m.createServiceDependency().setService(S1.class).setRequired(true).setCallbacks("addS1", "changeS1", "removeS1").setAutoConfig(true));
+ m.add(c);
+
+ // Add S1 (s1_1): C.add(S1 s1) is called, then init() is called where a dependency is declared on S2
+ Hashtable s1_1_props = new Hashtable();
+ s1_1_props.put("name", "s1_1");
+ s1_1_props.put(Constants.SERVICE_RANKING, new Integer(10));
+ S1Impl s1_1_impl = new S1Impl();
+ Component s1_1 = m.createComponent().setImplementation(s1_1_impl).setInterface(S1.class.getName(), s1_1_props);
+ m.add(s1_1);
+ m_e.waitForStep(1, 5000); // wait until C.init called
+ ServiceReference ref = cimpl.getS1("s1_1");
+ Assert.assertNotNull(ref);
+ Assert.assertNotNull(cimpl.getS1());
+ Assert.assertEquals(s1_1_impl, cimpl.getS1());
+
+ // At this point, MyComponent is in INSTANTIATED_AND_WAITING_FOR_REQUIRED state.
+ // add now add another higher ranked S1 (s1_2) instance. C.add(s1_2) method should be called (the S1 dependency
+ // is not instance bound), and m_s1 autoconfig field should be updated.
+ Hashtable s1_2_props = new Hashtable();
+ s1_2_props.put(Constants.SERVICE_RANKING, new Integer(20));
+ s1_2_props.put("name", "s1_2");
+ S1Impl s1_2_impl = new S1Impl();
+ Component s1_2 = m.createComponent().setImplementation(s1_2_impl).setInterface(S1.class.getName(), s1_2_props);
+ m.add(s1_2);
+ ref = cimpl.getS1("s1_2");
+ Assert.assertNotNull(ref);
+ Assert.assertNotNull(cimpl.getS1());
+ Assert.assertEquals(s1_2_impl, cimpl.getS1()); // must return s1_2 with ranking = 20
+
+ // Now, change the s1_1 service properties: C.changed(s1_1) should be called, and C.m_s1AutoConfig should be updated
+ s1_1_props.put(Constants.SERVICE_RANKING, new Integer(30));
+ s1_1.setServiceProperties(s1_1_props);
+ ref = cimpl.getS1("s1_1");
+ Assert.assertNotNull(ref);
+ Assert.assertEquals(new Integer(30), ref.getProperty(Constants.SERVICE_RANKING));
+ Assert.assertNotNull(cimpl.getS1());
+ Assert.assertEquals(s1_1_impl, cimpl.getS1());
+
+ // Now, remove the s1_1: C.remove(s1_1) should be called, and C.m_s1AutoConfig should be updated
+ m.remove(s1_1);
+ ref = cimpl.getS1("s1_1");
+ Assert.assertNull(cimpl.getS1("s1_1"));
+ Assert.assertNotNull(cimpl.getS1());
+ Assert.assertEquals(s1_2_impl, cimpl.getS1());
+ m.clear();
+ }
+
+ // C component depends on some S1 required services
+ public interface S1 {
+ }
+
+ public class S1Impl implements S1 {
+ }
+
+ public interface S2 {
+ }
+
+ public class S2Impl implements S2 {
+ }
+
+ // Our "C" component: it depends on S1 (required) and S2 (required/instance bound)
+ class C {
+ final Map<String, ServiceReference> m_s1Map = new HashMap();
+ final Map<String, ServiceReference> m_s2Map = new HashMap();
+ volatile S1 m_s1; // auto configured
+
+ S1 getS1() {
+ return m_s1;
+ }
+
+ void addS1(ServiceReference s1) {
+ m_s1Map.put((String) s1.getProperty("name"), s1);
+ }
+
+ void changeS1(ServiceReference s1) {
+ m_s1Map.put((String) s1.getProperty("name"), s1);
+ }
+
+ void removeS1(ServiceReference s1) {
+ m_s1Map.remove((String) s1.getProperty("name"));
+ }
+
+ void addS2(ServiceReference s2) {
+ m_s2Map.put((String) s2.getProperty("name"), s2);
+ }
+
+ void changeS2(ServiceReference s2) {
+ m_s2Map.put((String) s2.getProperty("name"), s2);
+ }
+
+ void removeS2(ServiceReference s2) {
+ m_s2Map.remove((String) s2.getProperty("name"));
+ }
+
+ ServiceReference getS1(String name) {
+ return m_s1Map.get(name);
+ }
+
+ ServiceReference getS2(String name) {
+ return m_s2Map.get(name);
+ }
+
+ void init(Component c) {
+ DependencyManager m = c.getDependencyManager();
+ c.add(m.createServiceDependency().setService(S2.class).setRequired(true).setCallbacks("addS2", "changeS2", "removeS2"));
+ m_e.step(1);
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ModifiedBundleDependencyTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ModifiedBundleDependencyTest.java
new file mode 100644
index 0000000..76d681c
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ModifiedBundleDependencyTest.java
@@ -0,0 +1,140 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import org.junit.Assert;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+
+/**
+ * Test for FELIX-4334 issue.
+ *
+ * Two components: A, B
+ *
+ * - A provided.
+ * - B has a bundle dependency on the dependency manager shell bundle, which is currently stopped.
+ * - B has an instance bound dependency on A.
+ * - Now unregister A.
+ * - As a result of that, B becomes unavailable and is unbound from A. But B is not destroyed, because A dependency
+ * is "instance bound". So B is still bound to the bundle dependency.
+ * - Now, someone starts the dependency manager shell bundle: B then shall be called in its "changed" callback.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class ModifiedBundleDependencyTest extends TestBase {
+ public static interface A {
+ }
+
+ static class AImpl implements A {
+ }
+
+ public static interface B {
+ }
+
+ static class BImpl implements B {
+ final Ensure m_e;
+
+ BImpl(Ensure e) {
+ m_e = e;
+ }
+
+ public void add(Bundle dmTest) {
+ m_e.step(1);
+ }
+
+ void init(Component c) {
+ m_e.step(2);
+ DependencyManager dm = c.getDependencyManager();
+ c.add(dm.createServiceDependency().setService(A.class).setRequired(true).setCallbacks("add", "remove"));
+ }
+
+ public void add(A a) {
+ m_e.step(3);
+ }
+
+ public void start() {
+ m_e.step(4);
+ }
+
+ public void stop() {
+ m_e.step(5);
+ }
+
+ public void remove(A a) {
+ m_e.step(6);
+ }
+
+ public void change(Bundle dmTest) { // called two times: one for STARTING, one for STARTED
+ m_e.step();
+ }
+
+ public void destroy() {
+ m_e.step(9);
+ }
+
+ public void remove(Bundle dmTest) {
+ m_e.step(10);
+ }
+ }
+
+ public void testAdapterWithChangedInstanceBoundDependency() {
+ DependencyManager m = getDM();
+ Ensure e = new Ensure();
+
+ Component a = m.createComponent()
+ .setImplementation(new AImpl())
+ .setInterface(A.class.getName(), null);
+
+ Component b = m.createComponent()
+ .setInterface(B.class.getName(), null)
+ .setImplementation(new BImpl(e))
+ .add(m.createBundleDependency()
+ .setFilter("(Bundle-SymbolicName=org.apache.felix.metatype)")
+ .setStateMask(Bundle.INSTALLED|Bundle.ACTIVE|Bundle.RESOLVED|Bundle.STARTING)
+ .setRequired(true)
+ .setCallbacks("add", "change", "remove"));
+
+ Bundle dmtest = getBundle("org.apache.felix.metatype");
+ try {
+ dmtest.stop();
+ } catch (BundleException e1) {
+ Assert.fail("could not find metatype bundle");
+ }
+
+ m.add(a);
+ m.add(b);
+
+ e.waitForStep(4, 5000);
+ m.remove(a); // B will loose A and will enter into "waiting for required (instantiated)" state.
+ System.out.println("Starting metatype bundle ...");
+ try {
+ dmtest.start();
+ } catch (BundleException e1) {
+ Assert.fail("could not start metatype bundle");
+ }
+ e.waitForStep(7, 5000);
+ m.remove(b);
+ e.waitForStep(10, 5000);
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/MultipleExtraDependenciesTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/MultipleExtraDependenciesTest.java
new file mode 100644
index 0000000..32f7855
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/MultipleExtraDependenciesTest.java
@@ -0,0 +1,222 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import java.util.Hashtable;
+
+import org.junit.Assert;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.Dependency;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+@SuppressWarnings({"unchecked", "rawtypes"})
+public class MultipleExtraDependenciesTest extends TestBase {
+ /**
+ * Check that list of extra dependencies (defined from init method) are handled properly.
+ * The extra dependencies are added using a List object (Component.add(List)).
+ * A component c1 will define two extra dependencies over *available* c4/c5 services.
+ */
+ public void testWithTwoAvailableExtraDependency() {
+ DependencyManager m = getDM();
+ // Helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ Component c1 = m.createComponent()
+ .setInterface(Service1.class.getName(), null)
+ .setImplementation(new MyComponent1(e))
+ .add(m.createServiceDependency()
+ .setService(Service2.class)
+ .setRequired(true)
+ .setAutoConfig("m_service2"));
+
+ Component c2 = m.createComponent()
+ .setImplementation(new MyComponent2(e))
+ .add(m.createServiceDependency()
+ .setService(Service1.class)
+ .setRequired(false)
+ .setAutoConfig(false)
+ .setCallbacks("added", null, null));
+
+ Component c3 = m.createComponent()
+ .setInterface(Service2.class.getName(), null)
+ .setImplementation(Service2Impl.class);
+
+ Hashtable h = new Hashtable();
+ h.put("type", "xx");
+ Component c4 = m.createComponent()
+ .setInterface(Service3.class.getName(), h)
+ .setImplementation(Service3Impl1.class);
+
+ h = new Hashtable();
+ h.put("type", "yy");
+ Component c5 = m.createComponent()
+ .setInterface(Service3.class.getName(), h)
+ .setImplementation(Service3Impl2.class);
+
+
+ System.out.println("\n+++ Adding c2 / MyComponent2");
+ m.add(c2);
+ System.out.println("\n+++ Adding c3 / Service2");
+ m.add(c3);
+ System.out.println("\n+++ Adding c4 / Service3(xx)");
+ m.add(c4);
+ System.out.println("\n+++ Adding c5 / Service3(yy)");
+ m.add(c5);
+ System.out.println("\n+++ Adding c1 / MyComponent1");
+ // c1 have declared two extra dependency on Service3 (xx/yy).
+ // both extra dependencies are available, so the c1 component should be started immediately.
+ m.add(c1);
+ e.waitForStep(3, 3000);
+ m.clear();
+ }
+
+ /**
+ * Check that list of extra dependencies (defined from init method) are handled properly.
+ * The extra dependencies are added using a List object (Component.add(List)).
+ * A component c1 will define two extra dependencies over c4/c5. At the point c1.init()
+ * is adding the two extra dependencies from its init method, c4 is available, but not c5.
+ * So, c1 is not yet activated.
+ * Then c5 is added, and it triggers the c1 activation ...
+ */
+ public void testWithOneAvailableExtraDependency() {
+ DependencyManager m = getDM();
+ // Helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ Component c1 = m.createComponent()
+ .setInterface(Service1.class.getName(), null)
+ .setImplementation(new MyComponent1(e))
+ .add(m.createServiceDependency()
+ .setService(Service2.class)
+ .setRequired(true)
+ .setAutoConfig("m_service2"));
+
+ Component c2 = m.createComponent()
+ .setImplementation(new MyComponent2(e))
+ .add(m.createServiceDependency()
+ .setService(Service1.class)
+ .setRequired(false)
+ .setAutoConfig(false)
+ .setCallbacks("added", null, null));
+
+ Component c3 = m.createComponent()
+ .setInterface(Service2.class.getName(), null)
+ .setImplementation(Service2Impl.class);
+
+ Hashtable h = new Hashtable();
+ h.put("type", "xx");
+ Component c4 = m.createComponent()
+ .setInterface(Service3.class.getName(), h)
+ .setImplementation(Service3Impl1.class);
+
+ h = new Hashtable();
+ h.put("type", "yy");
+ Component c5 = m.createComponent()
+ .setInterface(Service3.class.getName(), h)
+ .setImplementation(Service3Impl2.class);
+
+
+ System.out.println("\n+++ Adding c2 / MyComponent2");
+ m.add(c2);
+ System.out.println("\n+++ Adding c3 / Service2");
+ m.add(c3);
+ System.out.println("\n+++ Adding c4 / Service3(xx)");
+ m.add(c4);
+ System.out.println("\n+++ Adding c1 / MyComponent1");
+ m.add(c1);
+
+ // c1 have declared two extra dependency on Service3 (xx/yy).
+ // So, because we have not yet added c5 (yy), c1 should not be started currently.
+ // But, now, we'll add c5 (Service3/yy) and c1 should then be started ...
+ System.out.println("\n+++ Adding c5 / Service3(yy)");
+ m.add(c5);
+ e.waitForStep(3, 3000);
+ m.clear();
+ }
+
+
+ public interface Service1 {}
+ public interface Service2 {}
+ public interface Service3 {}
+
+ public static class Service2Impl implements Service2 {}
+ public static class Service3Impl1 implements Service3 {}
+ public static class Service3Impl2 implements Service3 {}
+
+ public static class MyComponent1 implements Service1 {
+ Service2 m_service2;
+ Service3 m_service3_xx;
+ Service3 m_service3_yy;
+ Ensure m_ensure;
+
+ public MyComponent1(Ensure e) {
+ m_ensure = e;
+ }
+
+ void init(Component c) {
+ m_ensure.step(1);
+ DependencyManager dm = c.getDependencyManager();
+ // Service3/xx currently available
+ Dependency d1 =
+ dm.createServiceDependency()
+ .setService(Service3.class, "(type=xx)")
+ .setRequired(true)
+ .setAutoConfig("m_service3_xx");
+
+ // Service3/yy not yet available
+ Dependency d2 =
+ dm.createServiceDependency()
+ .setService(Service3.class, "(type=yy)")
+ .setRequired(true)
+ .setAutoConfig("m_service3_yy");
+ c.add(d1, d2);
+ }
+
+ void start() {
+ System.out.println("MyComponent1.start");
+ Assert.assertNotNull(m_service2);
+ Assert.assertNotNull(m_service3_xx);
+ Assert.assertNotNull(m_service3_yy);
+ m_ensure.step(2);
+ }
+ }
+
+ public static class MyComponent2 {
+ Ensure m_ensure;
+
+ public MyComponent2(Ensure e) {
+ m_ensure = e;
+ }
+
+ void added(Service1 s1) {
+ System.out.println("MyComponent2.bind(" + s1 + ")");
+ Assert.assertNotNull(s1);
+ m_ensure.step(3);
+ }
+
+ void start() {
+ System.out.println("MyComponent2.start");
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/MultipleExtraDependencyTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/MultipleExtraDependencyTest.java
new file mode 100644
index 0000000..9816b0f
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/MultipleExtraDependencyTest.java
@@ -0,0 +1,226 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import java.util.Hashtable;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+
+/**
+ * Test which validates multi-dependencies combination.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+@SuppressWarnings({"unchecked", "rawtypes", "serial"})
+public class MultipleExtraDependencyTest extends TestBase {
+ public void testMultipleExtraDependencies()
+ {
+ DependencyManager m = getDM();
+ Ensure e = new Ensure();
+
+ Component sp2 = m.createComponent()
+ .setImplementation(ServiceProvider2.class).setInterface(ServiceProvider2.class.getName(), null)
+ .add(m.createServiceDependency()
+ .setService(Runnable.class, "(foo=bar)")
+ .setRequired(false)
+ .setAutoConfig("m_runnable"))
+ .add(m.createServiceDependency()
+ .setService(Sequencer.class)
+ .setRequired(true)
+ .setCallbacks("bind", null))
+ .setCallbacks(null, "start", "stop", null)
+ .setComposition("getComposition");
+
+ Component sp = m.createComponent()
+ .setImplementation(ServiceProvider.class)
+ .setInterface(ServiceInterface.class.getName(),
+ new Hashtable() {{ put("foo", "bar"); }})
+ .add(m.createServiceDependency()
+ .setService(Sequencer.class)
+ .setRequired(true)
+ .setAutoConfig("m_sequencer"))
+ .add(m.createServiceDependency()
+ .setService(ServiceProvider2.class)
+ .setRequired(true)
+ .setCallbacks("bind", "unbind"))
+ .setCallbacks(null, "start", "stop", null);
+
+ Component sc = m.createComponent()
+ .setImplementation(ServiceConsumer.class)
+ .add(m.createServiceDependency()
+ .setService(Sequencer.class)
+ .setRequired(true)
+ .setAutoConfig("m_sequencer"))
+ .add(m.createServiceDependency()
+ .setService(ServiceInterface.class, "(foo=bar)")
+ .setRequired(true)
+ .setAutoConfig("m_service"))
+ .setCallbacks(null, "start", "stop", null);
+
+ Component sequencer =
+ m.createComponent().setImplementation(new SequencerImpl(e))
+ .setInterface(Sequencer.class.getName(), null);
+ m.add(sp2);
+ m.add(sp);
+ m.add(sc);
+ m.add(sequencer);
+
+ // Check if ServiceProvider component have been initialized orderly
+ e.waitForStep(7, 5000);
+
+ // Stop the test.annotation bundle
+ m.remove(sequencer);
+ m.remove(sp);
+ m.remove(sp2);
+ m.remove(sc);
+
+ // And check if ServiceProvider2 has been deactivated orderly
+ e.waitForStep(11, 5000);
+ }
+
+ public interface Sequencer
+ {
+ void step();
+ void step(int step);
+ void waitForStep(int step, int timeout);
+ }
+
+ public static class SequencerImpl implements Sequencer {
+ Ensure m_ensure;
+
+ public SequencerImpl(Ensure e)
+ {
+ m_ensure = e;
+ }
+
+ public void step()
+ {
+ m_ensure.step();
+ }
+
+ public void step(int step)
+ {
+ m_ensure.step(step);
+ }
+
+ public void waitForStep(int step, int timeout)
+ {
+ m_ensure.waitForStep(step, timeout);
+ }
+ }
+
+ public interface ServiceInterface
+ {
+ public void doService();
+ }
+
+ public static class ServiceConsumer
+ {
+ volatile Sequencer m_sequencer;
+ volatile ServiceInterface m_service;
+
+ void start()
+ {
+ m_sequencer.step(6);
+ m_service.doService();
+ }
+
+ void stop()
+ {
+ m_sequencer.step(8);
+ }
+ }
+
+ public static class ServiceProvider implements ServiceInterface
+ {
+ Sequencer m_sequencer;
+ ServiceProvider2 m_serviceProvider2;
+
+ void bind(ServiceProvider2 provider2)
+ {
+ m_serviceProvider2 = provider2;
+ }
+
+ void start()
+ {
+ m_serviceProvider2.step(4);
+ m_sequencer.step(5);
+ }
+
+ void stop()
+ {
+ m_sequencer.step(9);
+ }
+
+ void unbind(ServiceProvider2 provider2)
+ {
+ m_sequencer.step(10);
+ }
+
+ public void doService()
+ {
+ m_sequencer.step(7);
+ }
+ }
+
+ public static class ServiceProvider2
+ {
+ Composite m_composite = new Composite();
+ Sequencer m_sequencer;
+ Runnable m_runnable;
+
+ void bind(Sequencer seq)
+ {
+ m_sequencer = seq;
+ m_sequencer.step(1);
+ }
+
+ void start()
+ {
+ m_sequencer.step(3);
+ m_runnable.run(); // NullObject
+ }
+
+ public void step(int step) // called by ServiceProvider.start() method
+ {
+ m_sequencer.step(step);
+ }
+
+ void stop()
+ {
+ m_sequencer.step(11);
+ }
+
+ Object[] getComposition()
+ {
+ return new Object[] { this, m_composite };
+ }
+ }
+
+ public static class Composite
+ {
+ void bind(Sequencer seq)
+ {
+ seq.step(2);
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/MultipleExtraDependencyTest2.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/MultipleExtraDependencyTest2.java
new file mode 100644
index 0000000..9889f3a
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/MultipleExtraDependencyTest2.java
@@ -0,0 +1,257 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import java.util.Hashtable;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.Dependency;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.ServiceDependency;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+
+/**
+ * Tests for extra dependencies which are declared from service's init method.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+@SuppressWarnings({"unchecked", "rawtypes", "serial"})
+public class MultipleExtraDependencyTest2 extends TestBase {
+ public void testMultipleExtraDependencies()
+ {
+ DependencyManager m = getDM();
+ Ensure e = new Ensure();
+
+ Component sp2 = m.createComponent()
+ .setImplementation(ServiceProvider2.class).setInterface(ServiceProvider2.class.getName(), null)
+ .setCallbacks("init", "start", "stop", null)
+ .setComposition("getComposition");
+
+ Component sp = m.createComponent()
+ .setImplementation(ServiceProvider.class)
+ .setInterface(ServiceInterface.class.getName(), new Hashtable() {{ put("foo", "bar"); }})
+ .setCallbacks("init", "start", "stop", null);
+
+ Component sc = m.createComponent()
+ .setImplementation(ServiceConsumer.class)
+ .setCallbacks("init", "start", "stop", null);
+
+ // Provide the Sequencer service to the MultipleAnnotationsTest class.
+ Component sequencer =
+ m.createComponent().setImplementation(new SequencerImpl(e))
+ .setInterface(Sequencer.class.getName(), null);
+ m.add(sp2);
+ m.add(sp);
+ m.add(sc);
+ m.add(sequencer);
+
+ // Check if the test.annotation components have been initialized orderly
+ e.waitForStep(7, 10000);
+
+ // Stop the test.annotation bundle
+ m.remove(sequencer);
+ m.remove(sp);
+ m.remove(sp2);
+ m.remove(sc);
+
+// m.remove(sp2);
+// m.remove(sc);
+// m.remove(sp);
+// m.remove(sequencer);
+
+
+
+ // And check if the test.annotation bundle has been deactivated orderly
+ e.waitForStep(11, 10000);
+ m.clear();
+ }
+
+ public interface Sequencer
+ {
+ void step();
+ void step(int step);
+ void waitForStep(int step, int timeout);
+ }
+
+ public static class SequencerImpl implements Sequencer {
+ final Ensure m_ensure;
+
+ public SequencerImpl(Ensure e)
+ {
+ m_ensure = e;
+ }
+
+ public void step()
+ {
+ m_ensure.step();
+ }
+
+ public void step(int step)
+ {
+ m_ensure.step(step);
+ }
+
+ public void waitForStep(int step, int timeout)
+ {
+ m_ensure.waitForStep(step, timeout);
+ }
+ }
+
+ public interface ServiceInterface
+ {
+ public void doService();
+ }
+
+ public static class ServiceConsumer {
+ volatile Sequencer m_sequencer;
+ volatile ServiceInterface m_service;
+ volatile Dependency m_d1, m_d2;
+
+ public void init(Component s) {
+ DependencyManager m = s.getDependencyManager();
+ m_d1 = m.createServiceDependency()
+ .setService(Sequencer.class)
+ .setRequired(true)
+ .setAutoConfig("m_sequencer");
+ m_d2 = m.createServiceDependency()
+ .setService(ServiceInterface.class, "(foo=bar)")
+ .setRequired(true)
+ .setAutoConfig("m_service");
+ s.add(m_d1, m_d2);
+ }
+
+ void start() {
+ m_sequencer.step(6);
+ m_service.doService();
+ }
+
+ void stop() {
+ m_sequencer.step(8);
+ }
+ }
+
+ public static class ServiceProvider implements ServiceInterface
+ {
+ volatile Sequencer m_sequencer;
+ volatile ServiceProvider2 m_serviceProvider2;
+ volatile ServiceDependency m_d1, m_d2;
+
+ public void init(Component c)
+ {
+ DependencyManager m = c.getDependencyManager();
+ m_d1 = m.createServiceDependency()
+ .setService(Sequencer.class)
+ .setRequired(true)
+ .setAutoConfig("m_sequencer");
+ m_d2 = m.createServiceDependency()
+ .setService(ServiceProvider2.class)
+ .setRequired(true)
+ .setCallbacks("bind", "unbind");
+ c.add(m_d1, m_d2);
+ }
+
+ void bind(ServiceProvider2 provider2)
+ {
+ m_serviceProvider2 = provider2;
+ }
+
+ void start()
+ {
+ m_serviceProvider2.step(4);
+ m_sequencer.step(5);
+ }
+
+ void stop()
+ {
+ m_sequencer.step(9);
+ }
+
+ void unbind(ServiceProvider2 provider2)
+ {
+ m_sequencer.step(10);
+ }
+
+ public void doService()
+ {
+ m_sequencer.step(7);
+ }
+ }
+
+ public static class ServiceProvider2
+ {
+ final Composite m_composite = new Composite();
+ volatile Sequencer m_sequencer;
+ volatile Runnable m_runnable;
+ volatile ServiceDependency m_d1, m_d2;
+
+ public void init(Component c)
+ {
+ System.out.println("ServiceProvider2.init");
+ DependencyManager m = c.getDependencyManager();
+
+ m_d1 = m.createServiceDependency()
+ .setService(Runnable.class, "(foo=bar)")
+ .setRequired(false)
+ .setAutoConfig("m_runnable");
+ m_d2 = m.createServiceDependency()
+ .setService(Sequencer.class)
+ .setRequired(true)
+ .setCallbacks("bind", null);
+ c.add(m_d1, m_d2);
+ }
+
+ void bind(Sequencer seq)
+ {
+ System.out.println("ServiceProvider2.bind(" + seq + ")");
+ m_sequencer = seq;
+ m_sequencer.step(1);
+ }
+
+ void start()
+ {
+ System.out.println("ServiceProvider2.start: m_runnable=" + m_runnable + ", m_sequencer = " + m_sequencer);
+ m_sequencer.step(3);
+ m_runnable.run(); // NullObject
+ }
+
+ public void step(int step) // called by ServiceProvider.start() method
+ {
+ m_sequencer.step(step);
+ }
+
+ void stop()
+ {
+ m_sequencer.step(11);
+ }
+
+ Object[] getComposition()
+ {
+ return new Object[] { this, m_composite };
+ }
+ }
+
+ public static class Composite
+ {
+ void bind(Sequencer seq)
+ {
+ seq.step(2);
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/MultipleServiceDependencyTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/MultipleServiceDependencyTest.java
new file mode 100644
index 0000000..36874ee
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/MultipleServiceDependencyTest.java
@@ -0,0 +1,150 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import java.util.Hashtable;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+import org.osgi.framework.Constants;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+@SuppressWarnings({"unchecked", "rawtypes", "serial"})
+public class MultipleServiceDependencyTest extends TestBase {
+ public void testMultipleServiceRegistrationAndConsumption() {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ // create a service provider and consumer
+ Component provider = m.createComponent().setImplementation(new ServiceProvider(e)).setInterface(ServiceInterface.class.getName(), null);
+ Component providerWithHighRank = m.createComponent().setImplementation(new ServiceProvider2(e)).setInterface(ServiceInterface.class.getName(), new Hashtable() {{ put(Constants.SERVICE_RANKING, Integer.valueOf(5)); }});
+ Component consumer = m.createComponent().setImplementation(new ServiceConsumer(e)).add(m.createServiceDependency().setService(ServiceInterface.class).setRequired(true));
+ m.add(provider);
+ m.add(providerWithHighRank);
+ m.add(consumer);
+ e.waitForStep(3, 5000);
+ m.remove(providerWithHighRank);
+ e.step(4);
+ e.waitForStep(5, 5000);
+ m.remove(provider);
+ m.remove(consumer);
+ e.waitForStep(6, 5000);
+ }
+
+ public void testReplacementAutoConfig() {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ // create a service provider and consumer
+ Component provider = m.createComponent().setImplementation(new ServiceProvider(e)).setInterface(ServiceInterface.class.getName(), null);
+ Component provider2 = m.createComponent().setImplementation(new ServiceProvider2(e)).setInterface(ServiceInterface.class.getName(), null);
+ Component consumer = m.createComponent().setImplementation(new ServiceConsumer(e)).add(m.createServiceDependency().setService(ServiceInterface.class).setRequired(true));
+ m.add(provider2);
+ m.add(consumer);
+ e.waitForStep(3, 5000);
+ m.add(provider);
+ m.remove(provider2);
+ e.step(4);
+ e.waitForStep(5, 5000);
+ m.remove(provider);
+ m.remove(consumer);
+ e.waitForStep(6, 5000);
+ }
+
+ public void testReplacementCallbacks() {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ // create a service provider and consumer
+ Component provider = m.createComponent().setImplementation(new ServiceProvider(e)).setInterface(ServiceInterface.class.getName(), null);
+ Component provider2 = m.createComponent().setImplementation(new ServiceProvider2(e)).setInterface(ServiceInterface.class.getName(), null);
+ Component consumer = m.createComponent().setImplementation(new ServiceConsumer(e))
+ .add(m.createServiceDependency()
+ .setService(ServiceInterface.class)
+ .setRequired(true)
+ .setCallbacks("add", "remove"));
+ m.add(provider2);
+ m.add(consumer);
+ e.waitForStep(3, 15000);
+ m.add(provider);
+ m.remove(provider2);
+ e.step(4);
+ e.waitForStep(5, 15000);
+ m.remove(provider);
+ m.remove(consumer);
+ e.waitForStep(6, 15000);
+ }
+
+ static interface ServiceInterface {
+ public void invoke();
+ }
+
+ static class ServiceProvider implements ServiceInterface {
+ private final Ensure m_ensure;
+ public ServiceProvider(Ensure e) {
+ m_ensure = e;
+ }
+ public void invoke() {
+ m_ensure.step(5);
+ }
+ }
+
+ static class ServiceProvider2 implements ServiceInterface {
+ private final Ensure m_ensure;
+ public ServiceProvider2(Ensure e) {
+ m_ensure = e;
+ }
+ public void invoke() {
+ m_ensure.step(2);
+ }
+ }
+
+ static class ServiceConsumer implements Runnable {
+ private volatile ServiceInterface m_service;
+ private final Ensure m_ensure;
+
+ @SuppressWarnings("unused")
+ private void add(ServiceInterface service) { m_service = service; }
+
+ @SuppressWarnings("unused")
+ private void remove(ServiceInterface service) { if (m_service == service) { m_service = null; }}
+ public ServiceConsumer(Ensure e) { m_ensure = e; }
+
+ public void start() {
+ Thread t = new Thread(this);
+ t.start();
+ }
+
+ public void run() {
+ m_ensure.step(1);
+ m_service.invoke();
+ m_ensure.step(3);
+ m_ensure.waitForStep(4, 15000);
+ m_service.invoke();
+ }
+
+ public void stop() {
+ m_ensure.step(6);
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/RemovedDependencyTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/RemovedDependencyTest.java
new file mode 100644
index 0000000..5d1af03
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/RemovedDependencyTest.java
@@ -0,0 +1,170 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Properties;
+
+import org.junit.Assert;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.Dependency;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * One consumer, Three providers. The Consumer has two required dependency on provider1, provider2, and one
+ * instance-bound required dependency on provider3.
+ * When the three providers are there, the consumer is started.
+ *
+ * This test asserts the following correct behaviors:
+ * - when we remove the dependency on provider2, then the consumer is not stopped.
+ * - when we remove the (instance-bound) dependency on provider3, then the consumer os not stopped.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+@SuppressWarnings({"unchecked", "rawtypes", "unused"})
+public class RemovedDependencyTest extends TestBase {
+ public void testRemoveDependencyAndConsumerMustRemainStarted() {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ // Create two providers
+ Hashtable props = new Hashtable();
+ props.put("name", "provider1");
+ Component sp = m.createComponent().setImplementation(new ServiceProvider(e)).setInterface(ServiceInterface.class.getName(), props);
+ props = new Properties();
+ props.put("name", "provider2");
+ Component sp2 = m.createComponent().setImplementation(new ServiceProvider(e)).setInterface(ServiceInterface.class.getName(), props);
+ props = new Properties();
+ props.put("name", "provider3");
+ Component sp3 = m.createComponent().setImplementation(new ServiceProvider(e)).setInterface(ServiceInterface.class.getName(), props);
+
+ // Create the consumer, and start it
+ Dependency d1 = m.createServiceDependency().setService(ServiceInterface.class, "(name=provider1)").setRequired(true).setCallbacks("add", "remove");
+ Dependency d2 = m.createServiceDependency().setService(ServiceInterface.class, "(name=provider2)").setRequired(true).setCallbacks("add", "remove");
+ Dependency d3 = m.createServiceDependency().setService(ServiceInterface.class, "(name=provider3)").setRequired(true).setCallbacks("add", "remove");
+
+ ServiceConsumer consumer = new ServiceConsumer(e, d3);
+ Component sc = m.createComponent().setImplementation(consumer).add(d1, d2);
+
+ // Add the first two providers and the consumer
+ m.add(sp);
+ m.add(sp2);
+ m.add(sp3);
+ m.add(sc);
+
+ // Check if consumer has been bound to the three providers
+ e.waitForStep(3, 5000);
+ Assert.assertEquals(3, consumer.getProvidersCount());
+ Assert.assertNotNull(consumer.getProvider("provider1"));
+ Assert.assertNotNull(consumer.getProvider("provider2"));
+ Assert.assertNotNull(consumer.getProvider("provider3"));
+
+ // Now remove the provider2, and check if the consumer is still alive
+ sc.remove(d2);
+ Assert.assertFalse(consumer.isStopped());
+ Assert.assertEquals(2, consumer.getProvidersCount());
+ Assert.assertNotNull(consumer.getProvider("provider1"));
+ Assert.assertNull(consumer.getProvider("provider2"));
+ Assert.assertNotNull(consumer.getProvider("provider3"));
+
+ // Now remove the provider3 (the consumer has an instance bound dependency on it), and check if the consumer is still alive
+ sc.remove(d3);
+ Assert.assertFalse(consumer.isStopped());
+ Assert.assertEquals(1, consumer.getProvidersCount());
+ Assert.assertNotNull(consumer.getProvider("provider1"));
+ Assert.assertNull(consumer.getProvider("provider2"));
+ Assert.assertNull(consumer.getProvider("provider3"));
+
+ m.clear();
+ }
+
+ static interface ServiceInterface {
+ public void invoke();
+ }
+
+ class ServiceProvider implements ServiceInterface {
+ final Ensure m_ensure;
+
+ public ServiceProvider(Ensure e) {
+ m_ensure = e;
+ }
+ public void invoke() {
+ m_ensure.step();
+ }
+ }
+
+ class ServiceConsumer {
+ private final Ensure m_ensure;
+ private final List<ServiceReference> m_providers = new ArrayList<>();
+ private BundleContext m_bc;
+ private boolean m_stopped;
+ private final Dependency m_dependency3;
+
+ public ServiceConsumer(Ensure e, Dependency dependency3) {
+ m_ensure = e;
+ m_dependency3 = dependency3;
+ }
+
+ public void add(ServiceReference ref) {
+ debug("ServiceConsumer.add(%s)", ref);
+ m_providers.add(ref);
+ ServiceInterface s = (ServiceInterface) m_bc.getService(ref);
+ s.invoke();
+ }
+
+ public void remove(ServiceReference ref) {
+ debug("ServiceConsumer.remove(%s)", ref);
+ m_providers.remove(ref);
+ debug("ServiceConsumer: current providers list=%s", m_providers);
+ }
+
+ public void init(Component c) {
+ c.add(m_dependency3);
+ }
+
+ public int getProvidersCount() {
+ return m_providers.size();
+ }
+
+ public ServiceInterface getProvider(String name) {
+ for (ServiceReference ref : m_providers) {
+ Object n = ref.getProperty("name");
+ if (n.equals(name)) {
+ return (ServiceInterface) m_bc.getService(ref);
+ }
+ }
+ return null;
+ }
+
+ public void stop() {
+ m_stopped = true;
+ }
+
+ public boolean isStopped() {
+ return m_stopped;
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ResourceAdapterDependencyAddAndRemoveTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ResourceAdapterDependencyAddAndRemoveTest.java
new file mode 100644
index 0000000..6d844d1
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ResourceAdapterDependencyAddAndRemoveTest.java
@@ -0,0 +1,167 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import java.net.URL;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.ComponentState;
+import org.apache.felix.dm.ComponentStateListener;
+import org.apache.felix.dm.Dependency;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.ResourceHandler;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class ResourceAdapterDependencyAddAndRemoveTest extends TestBase {
+ public void testBasicResourceAdapter() throws Exception {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+
+ // create and add a service provider
+ m.add(m.createComponent()
+ .setInterface(ServiceInterface.class.getName(), null)
+ .setImplementation(new ServiceProvider(e)));
+
+ // create and add a resource provider
+ ResourceProvider provider = new ResourceProvider(context, new URL("file://localhost/path/to/file1.txt"));
+ m.add(m.createComponent()
+ .setImplementation(provider)
+ .add(m.createServiceDependency()
+ .setService(ResourceHandler.class)
+ .setCallbacks("add", "remove"))
+ );
+
+ // create a resource adapter for our single resource
+ // note that we can provide an actual implementation instance here because there will be only one
+ // adapter, normally you'd want to specify a Class here
+ // also, create a callback instance which will be used for both callbacks on resource changes and
+ // life cycle callbacks on the adapters themselves
+
+ Dependency d = m.createServiceDependency()
+ .setService(ServiceInterface.class)
+ .setRequired(true);
+ CallbackInstance callbackInstance = new CallbackInstance(e, d);
+ Component component = m.createResourceAdapterService("(&(path=/path/to/*.txt)(host=localhost))", false, callbackInstance, "changed")
+ .setImplementation(new ResourceAdapter(e))
+ .setCallbacks(callbackInstance, "init", "start", "stop", "destroy");
+
+ // add the resource adapter
+ m.add(component);
+
+ // wait until the single resource is available (the adapter has been started)
+ e.waitForStep(1, 5000);
+ // trigger a 'change' in our resource
+ provider.change();
+ // wait until the changed callback is invoked
+ e.waitForStep(2, 5000);
+ // and has completed (ensuring no "extra" steps are invoked in the mean time)
+ e.waitForStep(3, 5000);
+
+ // remove the resource adapter again
+ // add a component state listener, in order to track resource adapter destruction
+ component.add(new ComponentStateListenerImpl(e));
+ m.remove(component);
+
+ // wait for the stopped callback in the state listener
+ e.waitForStep(4, 5000);
+ m.clear();
+ }
+
+ static class ResourceAdapter {
+ protected URL m_resource; // injected by reflection.
+
+ ResourceAdapter(Ensure e) {
+ }
+ }
+
+ static interface ServiceInterface {
+ public void invoke();
+ }
+
+ static class ServiceProvider implements ServiceInterface {
+ public ServiceProvider(Ensure e) {
+ }
+ public void invoke() {
+ }
+ }
+
+ class CallbackInstance {
+ private final Dependency m_dependency;
+ private final Ensure m_ensure;
+
+
+ public CallbackInstance(Ensure e, Dependency d) {
+ m_ensure = e;
+ m_dependency = d;
+ }
+
+ void init(Component c) {
+ debug("CallbackInstance.init");
+ c.add(m_dependency);
+ }
+
+ void start() {
+ debug("CallbackInstance.start");
+ m_ensure.step(1);
+ }
+
+ void stop() {
+ debug("CallbackInstance.stop");
+ }
+
+ void destroy() {
+ debug("CallbackInstance.destroy");
+ }
+
+ void changed(Component component) {
+ m_ensure.step(2);
+ Dependency oldDependency = m_dependency;
+ // and add a new dependency
+ component.add(component.getDependencyManager().createServiceDependency().setService(ServiceInterface.class).setRequired(true));
+ // remove the old dependency
+ component.remove(oldDependency);
+ debug("CallbackInstance.changed the dependencies");
+ m_ensure.step(3);
+ }
+ }
+
+ class ComponentStateListenerImpl implements ComponentStateListener {
+
+ private final Ensure m_ensure;
+
+ public ComponentStateListenerImpl(Ensure e) {
+ this.m_ensure = e;
+ }
+
+ public void changed(Component c, ComponentState state) {
+ debug("ComponentStateListenerImpl.changed: state=%s", state);
+ switch (state) {
+ case INACTIVE:
+ System.out.println("stopped");
+ m_ensure.step(4);
+ default:
+ }
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ResourceAdapterDependencyAddAndRemoveTest2.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ResourceAdapterDependencyAddAndRemoveTest2.java
new file mode 100644
index 0000000..09d7dd1
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ResourceAdapterDependencyAddAndRemoveTest2.java
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import java.net.URL;
+import java.util.Hashtable;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.ComponentState;
+import org.apache.felix.dm.ComponentStateListener;
+import org.apache.felix.dm.Dependency;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.ResourceHandler;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class ResourceAdapterDependencyAddAndRemoveTest2 extends TestBase {
+ public void testBasicResourceAdapter() throws Exception {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ // create a resource provider
+ ResourceProvider provider = new ResourceProvider(context, new URL("file://localhost/path/to/file1.txt"));
+ // activate it
+ Hashtable<String, String> props = new Hashtable<String, String>();
+ props.put("id", "1");
+ m.add(m.createComponent()
+ .setInterface(ServiceInterface.class.getName(), props)
+ .setImplementation(new ServiceProvider(e))
+ );
+
+ props = new Hashtable<String, String>();
+ props.put("id", "2");
+ m.add(m.createComponent()
+ .setInterface(ServiceInterface.class.getName(), props)
+ .setImplementation(new ServiceProvider(e))
+ );
+
+ m.add(m.createComponent()
+ .setImplementation(provider)
+ .add(m.createServiceDependency()
+ .setService(ResourceHandler.class)
+ .setCallbacks("add", "remove")
+ )
+ );
+
+ // create a resource adapter for our single resource
+ // note that we can provide an actual implementation instance here because there will be only one
+ // adapter, normally you'd want to specify a Class here
+ Dependency d = m.createServiceDependency().setService(ServiceInterface.class, "(id=1)").setRequired(true);
+ ResourceAdapter service = new ResourceAdapter(e, d);
+
+ CallbackInstance callbackInstance = new CallbackInstance(e, d);
+ Component component = m.createResourceAdapterService("(&(path=/path/to/*.txt)(host=localhost))", false, callbackInstance, "changed")
+ .setImplementation(service)
+ .setCallbacks(callbackInstance, "init", "start", "stop", "destroy");
+ component.add(new ComponentStateListenerImpl(e));
+ m.add(component);
+ // wait until the single resource is available
+ e.waitForStep(1, 5000);
+ // trigger a 'change' in our resource
+ provider.change();
+ // wait until the changed callback is invoked
+ e.waitForStep(2, 5000);
+
+ System.out.println("Done!");
+ m.clear();
+ }
+
+ static class ResourceAdapter {
+ protected URL m_resource; // injected by reflection.
+ final Dependency m_dependency;
+
+ ResourceAdapter(Ensure e, Dependency d) {
+ m_dependency = d;
+ }
+ }
+
+ static interface ServiceInterface {
+ public void invoke();
+ }
+
+ static class ServiceProvider implements ServiceInterface {
+ public ServiceProvider(Ensure e) {
+ }
+ public void invoke() {
+ }
+ }
+
+ static class CallbackInstance {
+ private final Ensure m_ensure;
+ private final Dependency m_dependency;
+
+ public CallbackInstance(Ensure e, Dependency d) {
+ m_ensure = e;
+ m_dependency = d;
+ }
+
+ void init(Component c) {
+ c.add(m_dependency);
+ System.out.println("init");
+ m_ensure.step(1);
+ }
+
+ void start() {
+ System.out.println("start");
+ }
+
+ void stop() {
+ System.out.println("stop");
+ }
+
+ void destroy() {
+ System.out.println("destroy");
+ }
+
+ void changed(Component component) {
+ m_ensure.step(2);
+ System.out.println("Changing the dependencies");
+ Dependency oldDependency = m_dependency;
+
+ // and add a new dependency
+ component.add(component.getDependencyManager().createServiceDependency().setService(ServiceInterface.class, "(id=2)").setRequired(true));
+ // remove the old dependency
+ component.remove(oldDependency);
+ System.out.println("Changed the dependencies");
+ }
+ }
+
+ static class ComponentStateListenerImpl implements ComponentStateListener {
+ public ComponentStateListenerImpl(Ensure e) {
+ }
+
+ @Override
+ public void changed(Component c, ComponentState state) {
+ switch (state) {
+ case INACTIVE:
+ System.out.println("INACTIVE");
+ break;
+ case INSTANTIATED_AND_WAITING_FOR_REQUIRED:
+ System.out.println("INSTANTIATED_AND_WAITING_FOR_REQUIRED");
+ break;
+ case WAITING_FOR_REQUIRED:
+ System.out.println("WAITING_FOR_REQUIRED");
+ break;
+ case TRACKING_OPTIONAL:
+ System.out.println("TRACKING_OPTIONAL");
+ break;
+
+ }
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ResourceAdapterTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ResourceAdapterTest.java
new file mode 100644
index 0000000..ad25a54
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ResourceAdapterTest.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.URL;
+
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.ResourceHandler;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+import org.junit.Assert;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class ResourceAdapterTest extends TestBase {
+ public void testBasicResourceAdapter() throws Exception {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ // create a resource provider
+ ResourceProvider provider = new ResourceProvider(context, new URL("file://localhost/path/to/file1.txt"));
+ // activate it
+ m.add(m.createComponent().setImplementation(provider).add(m.createServiceDependency().setService(ResourceHandler.class).setCallbacks("add", "remove")));
+ // create a resource adapter for our single resource
+ // note that we can provide an actual implementation instance here because there will be only one
+ // adapter, normally you'd want to specify a Class here
+ m.add(m.createResourceAdapterService("(&(path=/path/to/*.txt)(host=localhost))", false, null, "changed")
+ .setImplementation(new ResourceAdapter(e)));
+ // wait until the single resource is available
+ e.waitForStep(3, 5000);
+ // trigger a 'change' in our resource
+ provider.change();
+ // wait until the changed callback is invoked
+ e.waitForStep(4, 5000);
+ m.clear();
+ }
+
+ static class ResourceAdapter {
+ protected URL m_resource; // injected by reflection.
+ private Ensure m_ensure;
+
+ ResourceAdapter(Ensure e) {
+ m_ensure = e;
+ }
+
+ public void start() {
+ m_ensure.step(1);
+ Assert.assertNotNull("resource not injected", m_resource);
+ m_ensure.step(2);
+ try {
+ m_resource.openStream();
+ }
+ catch (FileNotFoundException e) {
+ m_ensure.step(3);
+ }
+ catch (IOException e) {
+ Assert.fail("We should not have gotten this exception.");
+ }
+ }
+
+ public void changed() {
+ m_ensure.step(4);
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ResourceDependencyTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ResourceDependencyTest.java
new file mode 100644
index 0000000..9595a9f
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ResourceDependencyTest.java
@@ -0,0 +1,151 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.ResourceHandler;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+import org.junit.Assert;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class ResourceDependencyTest extends TestBase {
+ public void testResourceDependency() throws MalformedURLException {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ // create a service provider and consumer
+ ResourceConsumer c = new ResourceConsumer(e);
+ Component consumer = m.createComponent()
+ .setImplementation(c)
+ .add(m.createResourceDependency()
+ .setFilter("(&(path=/path/to/*.txt)(host=localhost))")
+ .setCallbacks("add", "change", "remove"));
+ Component dynamicProxyConsumer = m.createComponent()
+ .setFactory(new ResourceConsumerFactory(e), "create")
+ .add(m.createResourceDependency()
+ .setFilter("(path=*.doc)")
+ .setCallbacks("add", null));
+ ResourceProvider provider = new ResourceProvider(context,
+ new URL("file://localhost/path/to/file1.txt"),
+ new URL("file://localhost/path/to/file2.txt"),
+ new URL("file://localhost/path/to/file3.doc"));
+ Component resourceProvider = m.createComponent()
+ .setImplementation(provider)
+ .add(m.createServiceDependency()
+ .setService(ResourceHandler.class)
+ .setCallbacks("add", "remove"));
+
+ // first add the consumer
+ m.add(consumer);
+ // then the resource provider, which will provide 3 resources,
+ // 2 of which match the consumers filter conditions
+ m.add(resourceProvider);
+ // make sure our consumer invoked openStream() on both resources,
+ // increasing the step counter to 2
+ e.step(3);
+
+ // now add another consumer, that matches only one resource, and uses
+ // a dynamic proxy as its implementation
+ m.add(dynamicProxyConsumer);
+ // ensure the resource was injected properly
+ e.waitForStep(4, 5000);
+
+ // now change a resource and see if it gets propagated to the consumer
+ provider.change(0);
+
+ // wait for change callback
+ e.waitForStep(5, 5000);
+ e.step(6);
+
+ // cleanup
+ m.remove(dynamicProxyConsumer);
+ m.remove(resourceProvider);
+ m.remove(consumer);
+
+ // validate that all consumed resources are "unconsumed" again
+ c.ensure();
+ m.clear();
+ }
+
+ class ResourceConsumer {
+ private volatile int m_counter;
+ private Ensure m_ensure;
+
+ public ResourceConsumer(Ensure ensure) {
+ m_ensure = ensure;
+ }
+
+ public void add(URL resource) {
+ debug("ResourceConsumer.add(%s)", resource);
+ m_counter++;
+ m_ensure.step();
+ }
+ public void change(URL resource) {
+ m_ensure.step();
+ }
+ public void remove(URL resource) {
+ debug("ResourceConsumer.remove(%s)", resource);
+ m_counter--;
+ }
+ public void ensure() {
+ Assert.assertTrue("all resources should have been added and removed at this point, but " + m_counter + " are remaining", m_counter == 0);
+ }
+ }
+
+
+ class ResourceConsumerFactory {
+ private final Ensure m_ensure;
+ public ResourceConsumerFactory(Ensure ensure) {
+ m_ensure = ensure;
+ }
+ public Object create() {
+ ResourceConsumer resourceConsumer = new ResourceConsumer(m_ensure);
+ // create a dynamic proxy for the ResourceProvider
+ return Proxy.newProxyInstance(resourceConsumer.getClass().getClassLoader(), resourceConsumer.getClass().getInterfaces(), new DynamicProxyHandler(resourceConsumer, m_ensure));
+ }
+ }
+
+ static class DynamicProxyHandler implements InvocationHandler {
+ Ensure m_ensure;
+ ResourceConsumer resourceConsumer = null;
+
+ public DynamicProxyHandler(ResourceConsumer resourceConsumer, Ensure ensure) {
+ this.resourceConsumer = resourceConsumer;
+ m_ensure = ensure;
+ }
+
+ public void add(URL resource) {
+ m_ensure.step(4);
+ }
+
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ return method.invoke(resourceConsumer, args);
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ResourceProvider.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ResourceProvider.java
new file mode 100644
index 0000000..7e0e46b
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ResourceProvider.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Assert;
+import org.apache.felix.dm.ResourceHandler;
+import org.apache.felix.dm.ResourceUtil;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Filter;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+class ResourceProvider {
+ final URL[] m_resources;
+ final BundleContext m_context;
+ final Map<ResourceHandler, Filter> m_handlers = new HashMap<>();
+
+ ResourceProvider(BundleContext ctx, URL ... resources) {
+ m_context = ctx;
+ m_resources = resources;
+ }
+
+ public void change() {
+ for (int i = 0; i < m_resources.length; i++) {
+ change(i);
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ public void change(int resourceIndex) {
+ Map<ResourceHandler, Filter> handlers = new HashMap<>();
+ synchronized (m_handlers) {
+ handlers.putAll(m_handlers);
+ }
+ for (Map.Entry<ResourceHandler, Filter> e : handlers.entrySet()) {
+ ResourceHandler handler = e.getKey();
+ Filter filter = e.getValue();
+ if (filter == null || filter.match(ResourceUtil.createProperties(m_resources[resourceIndex]))) {
+ handler.changed(m_resources[resourceIndex]);
+ }
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ public void add(ServiceReference ref, ResourceHandler handler) {
+ String filterString = (String) ref.getProperty("filter");
+ Filter filter = null;
+ if (filterString != null) {
+ try {
+ filter = m_context.createFilter(filterString);
+ }
+ catch (InvalidSyntaxException e) {
+ Assert.fail("Could not create filter for resource handler: " + e);
+ return;
+ }
+ }
+ for (int i = 0; i < m_resources.length; i++) {
+ if (filter == null || filter.match(ResourceUtil.createProperties(m_resources[i]))) {
+ synchronized (m_handlers) {
+ m_handlers.put(handler, filter);
+ }
+ handler.added(m_resources[i]);
+ }
+ }
+ }
+
+ public void remove(ServiceReference ref, ResourceHandler handler) {
+ Filter filter;
+ synchronized (m_handlers) {
+ filter = (Filter) m_handlers.remove(handler);
+ }
+ if (filter != null) {
+ removeResources(handler, filter);
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ private void removeResources(ResourceHandler handler, Filter filter) {
+ for (int i = 0; i < m_resources.length; i++) {
+ if (filter == null || filter.match(ResourceUtil.createProperties(m_resources[i]))) {
+ handler.removed(m_resources[i]);
+ }
+ }
+ }
+
+ public void destroy() {
+ Map<ResourceHandler, Filter> handlers = new HashMap<>();
+ synchronized (m_handlers) {
+ handlers.putAll(m_handlers);
+ }
+
+ for (Map.Entry<ResourceHandler, Filter> e : handlers.entrySet()) {
+ ResourceHandler handler = e.getKey();
+ Filter filter = e.getValue();
+ removeResources(handler, filter);
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ServiceDependencyCallbackSignaturesTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ServiceDependencyCallbackSignaturesTest.java
new file mode 100644
index 0000000..e39f830
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ServiceDependencyCallbackSignaturesTest.java
@@ -0,0 +1,294 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.junit.Assert;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+@SuppressWarnings("unused")
+public class ServiceDependencyCallbackSignaturesTest extends TestBase {
+ volatile Ensure m_ensure;
+
+ /**
+ * Tests if all possible dependency callbacks signatures supported by ServiceDependency.
+ */
+ public void testDependencyCallbackSignatures() {
+ DependencyManager m = getDM();
+ m_ensure = new Ensure();
+ Hashtable<String, String> props = new Hashtable<>();
+ props.put("foo", "bar");
+ Component provider = m.createComponent()
+ .setImplementation(new ProviderImpl()).setInterface(Provider.class.getName(), props);
+
+ declareConsumer(m, new Object() {
+ void bind(Component c, ServiceReference ref, Provider provider) {
+ Assert.assertNotNull(c);
+ Assert.assertNotNull(ref);
+ Assert.assertNotNull(provider);
+ Assert.assertEquals("bar", ref.getProperty("foo"));
+ Assert.assertEquals(context.getService(ref), provider);
+ m_ensure.step();
+ }
+ void change(Component c, ServiceReference ref, Provider provider) {
+ Assert.assertNotNull(c);
+ Assert.assertNotNull(ref);
+ Assert.assertNotNull(provider);
+ Assert.assertEquals("zoo", ref.getProperty("foo"));
+ Assert.assertEquals(context.getService(ref), provider);
+ m_ensure.step();
+ }
+ });
+ declareConsumer(m, new Object() {
+ void bind(Component c, ServiceReference ref, Object provider) {
+ Assert.assertNotNull(c);
+ Assert.assertNotNull(ref);
+ Assert.assertEquals("bar", ref.getProperty("foo"));
+ Assert.assertNotNull(provider);
+ Assert.assertEquals(context.getService(ref), provider);
+ m_ensure.step();
+ }
+ void change(Component c, ServiceReference ref, Object provider) {
+ Assert.assertNotNull(c);
+ Assert.assertNotNull(ref);
+ Assert.assertEquals("zoo", ref.getProperty("foo"));
+ Assert.assertNotNull(provider);
+ Assert.assertEquals(context.getService(ref), provider);
+ m_ensure.step();
+ }
+ });
+ declareConsumer(m, new Object() {
+ void bind(Component c, ServiceReference ref) {
+ Assert.assertNotNull(c);
+ Assert.assertNotNull(ref);
+ Assert.assertEquals("bar", ref.getProperty("foo"));
+ Assert.assertNotNull(context.getService(ref));
+ Assert.assertEquals(context.getService(ref).getClass(), ProviderImpl.class);
+ m_ensure.step();
+ }
+ void change(Component c, ServiceReference ref) {
+ Assert.assertNotNull(c);
+ Assert.assertNotNull(ref);
+ Assert.assertEquals("zoo", ref.getProperty("foo"));
+ Assert.assertNotNull(context.getService(ref));
+ Assert.assertEquals(context.getService(ref).getClass(), ProviderImpl.class);
+ m_ensure.step();
+ }
+ });
+ declareConsumer(m, new Object() {
+ void bind(Component c, Provider provider) {
+ Assert.assertNotNull(c);
+ Assert.assertNotNull(provider);
+ m_ensure.step();
+ }
+ void change(Component c, Provider provider) {
+ Assert.assertNotNull(c);
+ Assert.assertNotNull(provider);
+ m_ensure.step();
+ }
+ });
+ declareConsumer(m, new Object() {
+ void bind(Component c, Object provider) {
+ Assert.assertNotNull(c);
+ Assert.assertNotNull(provider);
+ Assert.assertEquals(provider.getClass(), ProviderImpl.class);
+ m_ensure.step();
+ }
+ void change(Component c, Object provider) {
+ Assert.assertNotNull(c);
+ Assert.assertNotNull(provider);
+ Assert.assertEquals(provider.getClass(), ProviderImpl.class);
+ m_ensure.step();
+ }
+ });
+ declareConsumer(m, new Object() {
+ void bind(Component c) {
+ Assert.assertNotNull(c);
+ m_ensure.step();
+ }
+ void change(Component c) {
+ Assert.assertNotNull(c);
+ m_ensure.step();
+ }
+ });
+ declareConsumer(m, new Object() {
+ void bind(Component c, Map<String, String> props, Provider provider) {
+ Assert.assertNotNull(c);
+ Assert.assertNotNull(props);
+ Assert.assertNotNull(provider);
+ Assert.assertEquals("bar", props.get("foo"));
+ Assert.assertEquals(provider.getClass(), ProviderImpl.class);
+ m_ensure.step();
+ }
+ void change(Component c, Map<String, String> props, Provider provider) {
+ Assert.assertNotNull(c);
+ Assert.assertNotNull(props);
+ Assert.assertNotNull(provider);
+ Assert.assertEquals("zoo", props.get("foo"));
+ Assert.assertEquals(provider.getClass(), ProviderImpl.class);
+ m_ensure.step();
+ }
+ });
+ declareConsumer(m, new Object() {
+ void bind(ServiceReference ref, Provider provider) {
+ Assert.assertNotNull(ref);
+ Assert.assertNotNull(provider);
+ Assert.assertEquals("bar", ref.getProperty("foo"));
+ m_ensure.step();
+ }
+ void change(ServiceReference ref, Provider provider) {
+ Assert.assertNotNull(ref);
+ Assert.assertNotNull(provider);
+ Assert.assertEquals("zoo", ref.getProperty("foo"));
+ m_ensure.step();
+ }
+ });
+ declareConsumer(m, new Object() {
+ void bind(ServiceReference ref, Object provider) {
+ Assert.assertNotNull(ref);
+ Assert.assertNotNull(provider);
+ Assert.assertEquals("bar", ref.getProperty("foo"));
+ Assert.assertEquals(provider.getClass(), ProviderImpl.class);
+ m_ensure.step();
+ }
+ void change(ServiceReference ref, Object provider) {
+ Assert.assertNotNull(ref);
+ Assert.assertNotNull(provider);
+ Assert.assertEquals("zoo", ref.getProperty("foo"));
+ Assert.assertEquals(provider.getClass(), ProviderImpl.class);
+ m_ensure.step();
+ }
+ });
+ declareConsumer(m, new Object() {
+ void bind(ServiceReference ref) {
+ Assert.assertNotNull(ref);
+ Assert.assertEquals("bar", ref.getProperty("foo"));
+ m_ensure.step();
+ }
+ void change(ServiceReference ref) {
+ Assert.assertNotNull(ref);
+ Assert.assertEquals("zoo", ref.getProperty("foo"));
+ m_ensure.step();
+ }
+ });
+ declareConsumer(m, new Object() {
+ void bind(Provider provider) {
+ Assert.assertNotNull(provider);
+ m_ensure.step();
+ }
+ void change(Provider provider) {
+ Assert.assertNotNull(provider);
+ m_ensure.step();
+ }
+ });
+ declareConsumer(m, new Object() {
+ void bind(Provider provider, Map<?, ?> props) {
+ Assert.assertNotNull(provider);
+ Assert.assertEquals("bar", props.get("foo"));
+ m_ensure.step();
+ }
+ void change(Provider provider, Map<?, ?> props) {
+ Assert.assertNotNull(provider);
+ Assert.assertEquals("zoo", props.get("foo"));
+ m_ensure.step();
+ }
+ });
+ declareConsumer(m, new Object() {
+ void bind(Map<?, ?> props, Provider provider) {
+ Assert.assertNotNull(provider);
+ Assert.assertEquals("bar", props.get("foo"));
+ m_ensure.step();
+ }
+ void change(Map<?, ?> props, Provider provider) {
+ Assert.assertNotNull(provider);
+ Assert.assertEquals("zoo", props.get("foo"));
+ m_ensure.step();
+ }
+ });
+ declareConsumer(m, new Object() {
+ void bind(Provider provider, Dictionary<?, ?> props) {
+ Assert.assertNotNull(provider);
+ Assert.assertEquals("bar", props.get("foo"));
+ m_ensure.step();
+ }
+ void change(Provider provider, Dictionary<?, ?> props) {
+ Assert.assertNotNull(provider);
+ Assert.assertEquals("zoo", props.get("foo"));
+ m_ensure.step();
+ }
+ });
+ declareConsumer(m, new Object() {
+ void bind(Dictionary<?, ?> props, Provider provider) {
+ Assert.assertNotNull(provider);
+ Assert.assertEquals("bar", props.get("foo"));
+ m_ensure.step();
+ }
+ void change(Dictionary<?, ?> props, Provider provider) {
+ Assert.assertNotNull(provider);
+ Assert.assertEquals("zoo", props.get("foo"));
+ m_ensure.step();
+ }
+ });
+ declareConsumer(m, new Object() {
+ void bind(Object provider) {
+ Assert.assertNotNull(provider);
+ Assert.assertEquals(provider.getClass(), ProviderImpl.class);
+ m_ensure.step();
+ }
+ void change(Object provider) {
+ bind(provider);
+ }
+ });
+
+ m.add(provider);
+ m_ensure.waitForStep(16, 5000);
+
+ props = new Hashtable<>();
+ props.put("foo", "zoo");
+ provider.setServiceProperties(props);
+ m_ensure.waitForStep(32, 5000);
+
+ m.remove(provider);
+ m_ensure.waitForStep(48, 5000);
+ }
+
+ private void declareConsumer(DependencyManager m, Object consumerImpl) {
+ Component consumer = m.createComponent().setImplementation(consumerImpl)
+ .add(m.createServiceDependency().setService(Provider.class).setCallbacks("bind", "change", "change").setRequired(true));
+ m.add(consumer);
+ }
+
+ public static interface Provider {
+ }
+
+ public static class ProviderImpl implements Provider {
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ServiceDependencyInjectionTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ServiceDependencyInjectionTest.java
new file mode 100644
index 0000000..47581ff
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ServiceDependencyInjectionTest.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import org.junit.Assert;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class ServiceDependencyInjectionTest extends TestBase {
+ public void testServiceInjection() {
+ DependencyManager m = getDM();
+ Ensure e = new Ensure();
+ // create a service provider and consumer
+ ServiceProvider provider = new ServiceProvider(e);
+ Component sp = m.createComponent().setImplementation(provider).setInterface(ServiceInterface2.class.getName(), null);
+ Component sc = m.createComponent().setImplementation(new ServiceConsumer())
+ .add(m.createServiceDependency().setService(ServiceInterface2.class).setRequired(true));
+ Component sc2 = m.createComponent() // all dependencies are optional
+ .setImplementation(new ServiceConsumerNamedInjection(false, false))
+ .add(m.createServiceDependency().setService(ServiceInterface2.class).setRequired(false).setAutoConfig("m_service"))
+ .add(m.createServiceDependency().setService(ServiceInterface2.class).setRequired(false).setAutoConfig("m_service2"))
+ .add(m.createServiceDependency().setService(ServiceInterface2.class).setRequired(false).setAutoConfig("m_service3"))
+ ;
+ Component sc3 = m.createComponent() // second dependency is required, first and third are optional
+ .setImplementation(new ServiceConsumerNamedInjection(false, false))
+ .add(m.createServiceDependency().setService(ServiceInterface2.class).setRequired(false).setAutoConfig("m_service"))
+ .add(m.createServiceDependency().setService(ServiceInterface2.class).setRequired(true).setAutoConfig("m_service2"))
+ .add(m.createServiceDependency().setService(ServiceInterface2.class).setRequired(false).setAutoConfig("m_service3"))
+ ;
+ Component sc4 = m.createComponent()
+ .setImplementation(new ServiceConsumerNamedInjection(true, false));
+ Component sc5 = m.createComponent()
+ .setImplementation(new ServiceConsumerNamedInjection(true, true));
+ m.add(sp);
+ m.add(sc);
+ m.remove(sc);
+ m.add(sc2);
+ m.remove(sc2);
+ m.add(sc3);
+ m.remove(sc4);
+ m.add(sc4);
+ m.remove(sc4);
+ m.add(sc5);
+ m.remove(sc5);
+ m.remove(sp);
+ e.waitForStep(11, 5000);
+ m.clear();
+ }
+
+ static interface ServiceInterface {
+ public void invoke();
+ }
+
+ static interface ServiceInterface2 extends ServiceInterface {
+ public void invoke2();
+ }
+
+ static class ServiceProvider implements ServiceInterface2 {
+ private final Ensure m_ensure;
+ private Ensure.Steps m_invokeSteps = new Ensure.Steps(4, 5, 7, 8, 10, 11, 13, 14);
+ private Ensure.Steps m_invoke2Steps = new Ensure.Steps(1, 2, 3, 6, 9, 12);
+
+ public ServiceProvider(Ensure e) {
+ m_ensure = e;
+ }
+
+ public void invoke() {
+ System.out.println("invoke");
+ m_ensure.steps(m_invokeSteps);
+ }
+
+ public void invoke2() {
+ System.out.println("invoke2");
+ m_ensure.steps(m_invoke2Steps);
+ }
+ }
+
+ static class ServiceConsumer {
+ private volatile ServiceInterface2 m_service;
+ private volatile ServiceInterface2 m_service2;
+
+ public void init() {
+ // invoke the second method of the interface via both injected members, to ensure
+ // neither of them is a null object (or null)
+ m_service.invoke2();
+ m_service2.invoke2();
+ Assert.assertEquals("Both members should have been injected with the same service.", m_service, m_service2);
+ }
+ }
+
+ class ServiceConsumerNamedInjection {
+ private volatile ServiceInterface2 m_service;
+ private volatile ServiceInterface m_service2;
+ private volatile Object m_service3;
+ private final boolean m_secondDependencyRequired;
+ private final boolean m_instanceBound;
+
+ ServiceConsumerNamedInjection(boolean instanceBound, boolean withSecondRequired) {
+ m_secondDependencyRequired = withSecondRequired;
+ m_instanceBound = instanceBound;
+ }
+
+ public void init(Component c) {
+ if (m_instanceBound) {
+ DependencyManager m = c.getDependencyManager();
+ c.add(m.createServiceDependency().setService(ServiceInterface2.class).setRequired(false).setAutoConfig("m_service"),
+ m.createServiceDependency().setService(ServiceInterface2.class).setRequired(m_secondDependencyRequired).setAutoConfig("m_service2"),
+ m.createServiceDependency().setService(ServiceInterface2.class).setRequired(false).setAutoConfig("m_service3"));
+ } else {
+ check();
+ }
+ }
+
+ public void start() {
+ if (m_instanceBound) {
+ check();
+ }
+ }
+
+ public void check() {
+ warn("ServiceConsumerNamedInjectionInstanceBound: m_service=%s, m_service2=%s, m_service3=%s", m_service, m_service2, m_service3);
+ // invoke the second method
+ m_service.invoke2();
+ // invoke the first method (twice)
+ m_service2.invoke();
+ ((ServiceInterface) m_service3).invoke();
+ Assert.assertNotNull("Should have been injected", m_service);
+ Assert.assertNotNull("Should have been injected", m_service2);
+ Assert.assertNotNull("Should have been injected", m_service3);
+ Assert.assertEquals("Members should have been injected with the same service.", m_service, m_service2);
+ Assert.assertEquals("Members should have been injected with the same service.", m_service, m_service3);
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ServiceDependencyPropagateTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ServiceDependencyPropagateTest.java
new file mode 100644
index 0000000..2efb898
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ServiceDependencyPropagateTest.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * Validates ServiceDependency service properties propagation.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+@SuppressWarnings({"unchecked", "rawtypes", "serial"})
+public class ServiceDependencyPropagateTest extends TestBase {
+ /**
+ * Checks that a ServiceDependency propagates the dependency service properties to the provided service properties.
+ */
+ public void testServiceDependencyPropagate() {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ Component c1 = m.createComponent()
+ .setImplementation(new C1(e))
+ .add(m.createServiceDependency().setService(C2.class).setRequired(true).setCallbacks("bind", null));
+
+ Component c2 = m.createComponent()
+ .setInterface(C2.class.getName(), new Hashtable() {{ put("foo", "bar"); }})
+ .setImplementation(new C2())
+ .add(m.createServiceDependency().setService(C3.class).setRequired(true).setPropagate(true));
+
+ Component c3 = m.createComponent()
+ .setInterface(C3.class.getName(), new Hashtable() {{ put("foo2", "bar2"); put("foo", "overriden");}})
+ .setImplementation(new C3());
+
+ m.add(c1);
+ m.add(c2);
+ m.add(c3);
+
+ e.waitForStep(3, 10000);
+
+ m.remove(c3);
+ m.remove(c2);
+ m.remove(c1);
+ m.clear();
+ }
+
+ /**
+ * Checks that a ServiceDependency propagates the dependency service properties to the provided service properties,
+ * using a callback method.
+ */
+ public void testServiceDependencyPropagateCallback() {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ Component c1 = m.createComponent()
+ .setImplementation(new C1(e))
+ .add(m.createServiceDependency().setService(C2.class).setRequired(true).setCallbacks("bind", null));
+
+ C2 c2Impl = new C2();
+ Component c2 = m.createComponent()
+ .setInterface(C2.class.getName(), new Hashtable() {{ put("foo", "bar"); }})
+ .setImplementation(c2Impl)
+ .add(m.createServiceDependency().setService(C3.class).setRequired(true).setPropagate(c2Impl, "getServiceProperties"));
+
+ Component c3 = m.createComponent()
+ .setInterface(C3.class.getName(), null)
+ .setImplementation(new C3());
+
+ m.add(c1);
+ m.add(c2);
+ m.add(c3);
+
+ e.waitForStep(3, 10000);
+ m.clear();
+ }
+
+ public static class C1 {
+ private Map m_props;
+ private Ensure m_ensure;
+
+ C1(Ensure ensure) {
+ m_ensure = ensure;
+ }
+
+ void bind(Map props, C2 c2) {
+ m_props = props;
+ }
+
+ void start() {
+ m_ensure.step(1);
+ if ("bar".equals(m_props.get("foo"))) { // "foo=overriden" from C2 should not override our own "foo" property
+ m_ensure.step(2);
+ }
+ if ("bar2".equals(m_props.get("foo2"))) {
+ m_ensure.step(3);
+ }
+ }
+ }
+
+ public static class C2 {
+ C3 m_c3;
+
+ public Dictionary getServiceProperties(ServiceReference ref) {
+ return new Hashtable() {{ put("foo2", "bar2"); put("foo", "overriden"); }};
+ }
+ }
+
+ public static class C3 {
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ServiceDependencyTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ServiceDependencyTest.java
new file mode 100644
index 0000000..ae0b2ee
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ServiceDependencyTest.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class ServiceDependencyTest extends TestBase {
+ public void testServiceRegistrationAndConsumption() {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ // create a service provider and consumer
+ Component sp = m.createComponent().setImplementation(new ServiceProvider(e)).setInterface(ServiceInterface.class.getName(), null);
+ Component sc = m.createComponent().setImplementation(new ServiceConsumer(e)).add(m.createServiceDependency().setService(ServiceInterface.class).setRequired(true));
+ Component sc2 = m.createComponent().setImplementation(new ServiceConsumerCallbacks(e)).add(m.createServiceDependency().setService(ServiceInterface.class).setRequired(false).setCallbacks("add", "remove"));
+ m.add(sp);
+ m.add(sc);
+ m.remove(sc);
+ m.add(sc2);
+ m.remove(sp);
+ m.remove(sc2);
+ // ensure we executed all steps inside the component instance
+ e.step(6);
+ }
+
+ static interface ServiceInterface {
+ public void invoke();
+ }
+
+ static class ServiceProvider implements ServiceInterface {
+ private final Ensure m_ensure;
+ public ServiceProvider(Ensure e) {
+ m_ensure = e;
+ }
+ public void invoke() {
+ m_ensure.step(2);
+ }
+ }
+
+ static class ServiceConsumer {
+ private volatile ServiceInterface m_service;
+ private final Ensure m_ensure;
+
+ public ServiceConsumer(Ensure e) {
+ m_ensure = e;
+ }
+
+ public void init() {
+ m_ensure.step(1);
+ m_service.invoke();
+ }
+
+ public void destroy() {
+ m_ensure.step(3);
+ }
+ }
+
+ static class ServiceConsumerCallbacks {
+ private final Ensure m_ensure;
+
+ public ServiceConsumerCallbacks(Ensure e) {
+ m_ensure = e;
+ }
+
+ public void add(ServiceInterface service) {
+ m_ensure.step(4);
+ }
+ public void remove(ServiceInterface service) {
+ m_ensure.step(5);
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ServiceDependencyThroughCallbackInstanceTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ServiceDependencyThroughCallbackInstanceTest.java
new file mode 100644
index 0000000..e376e29
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ServiceDependencyThroughCallbackInstanceTest.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import org.junit.Assert;
+
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.TestBase;
+import org.osgi.framework.BundleContext;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class ServiceDependencyThroughCallbackInstanceTest extends TestBase {
+ public void testServiceWithCallbacksAndOneDependency() {
+ invokeTest(context, 1);
+ }
+
+ public void testServiceWithCallbacksAndThreeDependencies() {
+ invokeTest(context, 3);
+ }
+
+ private void invokeTest(BundleContext context, int numberOfServices) {
+ DependencyManager m = getDM();
+ // create a number of services
+ for (int i = 0; i < numberOfServices; i++) {
+ final int num = i;
+ m.add(m.createComponent()
+ .setInterface(Service.class.getName(), null)
+ .setImplementation(new Service() {
+ public String toString() {
+ return "A" + num;
+ }
+ }
+ )
+ );
+ }
+
+ // create a service with dependency which will be invoked on a callback instance
+ CallbackInstance instance = new CallbackInstance();
+ m.add(m.createComponent()
+ .setImplementation(new SimpleService() {})
+ .add(m.createServiceDependency()
+ .setService(Service.class)
+ .setCallbacks(instance, "added", "removed")
+ .setRequired(true)
+ )
+ );
+
+ Assert.assertEquals(numberOfServices, instance.getCount());
+ m.clear();
+ }
+
+ public static interface Service {
+ }
+
+ public static interface SimpleService {
+ }
+
+ public static class CallbackInstance {
+ int m_count = 0;
+
+ void added(Service service) {
+ System.out.println("added " + service);
+ m_count++;
+ }
+
+ void removed(Service service) {
+ }
+
+ int getCount() {
+ return m_count;
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ServiceRaceParallelTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ServiceRaceParallelTest.java
new file mode 100644
index 0000000..ba97fbe
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ServiceRaceParallelTest.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class ServiceRaceParallelTest extends ServiceRaceTest {
+ public ServiceRaceParallelTest() {
+ setParallel(); // Configure DM to use a threadpool
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ServiceRaceTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ServiceRaceTest.java
new file mode 100644
index 0000000..bcc6b17
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ServiceRaceTest.java
@@ -0,0 +1,306 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Assert;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.ConfigurationDependency;
+import org.apache.felix.dm.ServiceDependency;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.cm.ConfigurationException;
+
+
+/**
+ * This test class simulates a client having many dependencies being registered/unregistered concurrently.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+@SuppressWarnings({"unchecked", "rawtypes"})
+public class ServiceRaceTest extends TestBase {
+ volatile ConfigurationAdmin m_cm;
+ final static int STEP_WAIT = 5000;
+ final static int DEPENDENCIES = 10;
+ final static int LOOPS = 3000;
+ final Ensure m_done = new Ensure(true);
+
+ // Executor used to bind/unbind service dependencies.
+ ExecutorService m_threadpool;
+
+ // Timestamp used to log the time consumed to execute 100 tests.
+ long m_timeStamp;
+
+ public interface Dep {
+ }
+
+ public class DepImpl implements Dep {
+ }
+
+ /**
+ * Creates many service dependencies, and activate/deactivate them concurrently.
+ */
+ public void testCreateParallelComponentRegistgrationUnregistration() {
+ m_dm.add(m_dm.createComponent()
+ .setImplementation(this)
+ .setCallbacks(null, "start", null, null)
+ .add(m_dm.createServiceDependency().setService(ConfigurationAdmin.class).setRequired(true)));
+ m_done.waitForStep(1, 60000);
+ m_dm.clear();
+ Assert.assertFalse(super.errorsLogged());
+ }
+
+ void start() {
+ new Thread(new Runnable() {
+ public void run() {
+ doStart();
+ }}).start();
+ }
+
+ void doStart() {
+ info("Starting createParallelComponentRegistgrationUnregistration test");
+ initThreadPool(); // only if setParallel() has not been called (only if a parallel DM is not used).
+
+ try {
+ m_timeStamp = System.currentTimeMillis();
+ for (int loop = 0; loop < LOOPS; loop++) {
+ doTest(loop);
+ }
+ }
+ catch (Throwable t) {
+ error("got unexpected exception", t);
+ }
+ finally {
+ shutdownThreadPool();
+ m_done.step(1);
+ }
+ }
+
+ private void initThreadPool() {
+ if (! m_parallel) {
+ // We are not using a parallel DM, so we create a custom threadpool in order to add components concurrently.
+ int cores = Math.max(16, Runtime.getRuntime().availableProcessors());
+ info("using " + cores + " cores.");
+ m_threadpool = Executors.newFixedThreadPool(Math.max(cores, DEPENDENCIES + 3 /* start/stop/configure */));
+ }
+ }
+
+ void shutdownThreadPool() {
+ if (! m_parallel && m_threadpool != null) {
+ m_threadpool.shutdown();
+ try {
+ m_threadpool.awaitTermination(60, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+
+ void doTest(int loop) throws Throwable {
+ debug("loop#%d -------------------------", loop);
+
+ final Ensure step = new Ensure(false);
+
+ // Create one client component, which depends on many service dependencies
+ final Component client = m_dm.createComponent();
+ final Client clientImpl = new Client(step);
+ client.setImplementation(clientImpl);
+
+ // Create client service dependencies
+ final ServiceDependency[] dependencies = new ServiceDependency[DEPENDENCIES];
+ for (int i = 0; i < DEPENDENCIES; i++) {
+ final String filter = "(id=loop" + loop + "." + i + ")";
+ dependencies[i] = m_dm.createServiceDependency().setService(Dep.class, filter)
+ .setRequired(true)
+ .setCallbacks("add", "remove");
+ client.add(dependencies[i]);
+ }
+ String pid = "pid." + loop;
+ final ConfigurationDependency confDependency = m_dm.createConfigurationDependency().setPid(pid);
+ client.add(confDependency);
+
+ // Create Configuration (concurrently).
+ final Configuration conf = m_cm.getConfiguration(pid, null);
+ final Hashtable props = new Hashtable();
+ props.put("foo", "bar");
+ schedule(new Runnable() {
+ public void run() {
+ try {
+ conf.update(props);
+ }
+ catch (IOException e) {
+ error("update failed", e);
+ }
+ }
+ });
+
+ // Activate the client service dependencies concurrently.
+ List<Component> deps = new ArrayList();
+ for (int i = 0; i < DEPENDENCIES; i++) {
+ Hashtable h = new Hashtable();
+ h.put("id", "loop" + loop + "." + i);
+ final Component s = m_dm.createComponent()
+ .setInterface(Dep.class.getName(), h)
+ .setImplementation(new DepImpl());
+ deps.add(s);
+ schedule(new Runnable() {
+ public void run() {
+ m_dm.add(s);
+ }
+ });
+ }
+
+ // Start the client (concurrently)
+ schedule(new Runnable() {
+ public void run() {
+ m_dm.add(client);
+ }
+ });
+
+ // Ensure that client has been started.
+ int expectedStep = 1 /* conf */ + DEPENDENCIES + 1 /* start */;
+ step.waitForStep(expectedStep, STEP_WAIT);
+ Assert.assertEquals(DEPENDENCIES, clientImpl.getDependencies());
+ Assert.assertNotNull(clientImpl.getConfiguration());
+
+ // Stop all dependencies concurrently.
+ for (Component dep : deps) {
+ final Component dependency = dep;
+ schedule(new Runnable() {
+ public void run() {
+ m_dm.remove(dependency);
+ }
+ });
+ }
+
+ // Stop client concurrently.
+ schedule(new Runnable() {
+ public void run() {
+ m_dm.remove(client);
+ }
+ });
+
+ // Remove configuration (asynchronously)
+ schedule(new Runnable() {
+ public void run() {
+ try {
+ conf.delete();
+ }
+ catch (IOException e) {
+ warn("error while unconfiguring", e);
+ }
+ }
+ });
+
+ // Ensure that client has been stopped, then destroyed, then unbound from all dependencies
+ expectedStep += 2; // stop/destroy
+ expectedStep += DEPENDENCIES; // removed all dependencies
+ expectedStep += 1; // removed configuration
+ step.waitForStep(expectedStep, STEP_WAIT);
+ step.ensure();
+ Assert.assertEquals(0, clientImpl.getDependencies());
+ Assert.assertNull(clientImpl.getConfiguration());
+
+ if (super.errorsLogged()) {
+ throw new IllegalStateException("Race test interrupted (some error occured, see previous logs)");
+ }
+
+ debug("finished one test loop");
+ if ((loop + 1) % 100 == 0) {
+ long duration = System.currentTimeMillis() - m_timeStamp;
+ warn("Performed 100 tests (total=%d) in %d ms.", (loop + 1), duration);
+ m_timeStamp = System.currentTimeMillis();
+ }
+ }
+
+ private void schedule(Runnable task) {
+ if (! m_parallel) {
+ // not using parallel DM, so use our custom threadpool.
+ m_threadpool.execute(task);
+ } else {
+ task.run();
+ }
+ }
+
+ public class Client {
+ final Ensure m_step;
+ volatile int m_dependencies;
+ volatile Dictionary m_conf;
+
+ public Client(Ensure step) {
+ m_step = step;
+ }
+
+ public void updated(Dictionary conf) throws ConfigurationException {
+ m_conf = conf;
+ try {
+ if (conf != null) {
+ Assert.assertEquals("bar", conf.get("foo"));
+ m_step.step(1);
+ } else {
+ m_step.step();
+ }
+ } catch (Throwable t) {
+ m_step.throwable(t);
+ }
+ }
+
+ void add(Dep d) {
+ Assert.assertNotNull(d);
+ m_step.step();
+ m_dependencies ++;
+ }
+
+ void remove(Dep d) {
+ Assert.assertNotNull(d);
+ m_step.step();
+ m_dependencies --;
+ }
+
+ void start() {
+ m_step.step((DEPENDENCIES + 1) /* deps + conf */ + 1 /* start */);
+ }
+
+ void stop() {
+ m_step.step((DEPENDENCIES + 1) /* deps + conf */ + 1 /* start */ + 1 /* stop */);
+ }
+
+ void destroy() {
+ m_step.step((DEPENDENCIES + 1) /* deps + conf */ + 1 /* start */ + 1 /* stop */ + 1 /* destroy */);
+ }
+
+ int getDependencies() {
+ return m_dependencies;
+ }
+
+ Dictionary getConfiguration() {
+ return m_conf;
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ServiceTrackerTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ServiceTrackerTest.java
new file mode 100644
index 0000000..d9076b4
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ServiceTrackerTest.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import java.util.Hashtable;
+
+import org.junit.Assert;
+
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.ServiceUtil;
+import org.apache.felix.dm.itest.util.TestBase;
+import org.apache.felix.dm.tracker.ServiceTracker;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+@SuppressWarnings({"unchecked", "rawtypes", "serial"})
+public class ServiceTrackerTest extends TestBase {
+ public void testPlainServiceTracker() {
+ ServiceTracker st = new ServiceTracker(context, ServiceInterface.class.getName(), null);
+ st.open();
+ ServiceRegistration sr = context.registerService(ServiceInterface.class.getName(), new ServiceProvider(), null);
+ Assert.assertEquals("There should be one service that matches the tracker", 1, st.getServices().length);
+ sr.unregister();
+ Assert.assertNull("There should be no service that matches the tracker", st.getServices());
+ st.close();
+ }
+
+ public void testAspectServiceTracker() {
+ ServiceTracker st = new ServiceTracker(context, ServiceInterface.class.getName(), null);
+ st.open();
+
+ ServiceRegistration sr = context.registerService(ServiceInterface.class.getName(), new ServiceProvider(), null);
+ Assert.assertEquals("There should be one service that matches the tracker", 1, st.getServices().length);
+
+ final long sid = ServiceUtil.getServiceId(sr.getReference());
+ ServiceRegistration asr = context.registerService(ServiceInterface.class.getName(), new ServiceProvider(),
+ new Hashtable() {{ put(DependencyManager.ASPECT, sid); put(Constants.SERVICE_RANKING, 10); }});
+ Assert.assertEquals("There should be one service that matches the tracker", 1, st.getServices().length);
+ Assert.assertEquals("Service ranking should be 10", Integer.valueOf(10), (Integer) st.getServiceReference().getProperty(Constants.SERVICE_RANKING));
+
+ ServiceRegistration asr2 = context.registerService(ServiceInterface.class.getName(), new ServiceProvider(),
+ new Hashtable() {{ put(DependencyManager.ASPECT, sid); put(Constants.SERVICE_RANKING, 20); }});
+ Assert.assertEquals("There should be one service that matches the tracker", 1, st.getServices().length);
+ Assert.assertEquals("Service ranking should be 20", Integer.valueOf(20), (Integer) st.getServiceReference().getProperty(Constants.SERVICE_RANKING));
+
+ asr.unregister();
+ Assert.assertEquals("There should be one service that matches the tracker", 1, st.getServices().length);
+ Assert.assertEquals("Service ranking should be 20", Integer.valueOf(20), (Integer) st.getServiceReference().getProperty(Constants.SERVICE_RANKING));
+
+ asr2.unregister();
+ Assert.assertEquals("There should be one service that matches the tracker", 1, st.getServices().length);
+ Assert.assertNull("Service should not have a ranking", st.getServiceReference().getProperty(Constants.SERVICE_RANKING));
+
+ sr.unregister();
+ Assert.assertNull("There should be no service that matches the tracker", st.getServices());
+
+ st.close();
+ }
+
+ public void testExistingAspectServiceTracker() {
+ ServiceTracker st = new ServiceTracker(context, ServiceInterface.class.getName(), null);
+ ServiceRegistration sr = context.registerService(ServiceInterface.class.getName(), new ServiceProvider(), null);
+ final long sid = ServiceUtil.getServiceId(sr.getReference());
+ ServiceRegistration asr = context.registerService(ServiceInterface.class.getName(), new ServiceProvider(),
+ new Hashtable() {{ put(DependencyManager.ASPECT, sid); put(Constants.SERVICE_RANKING, 10); }});
+ ServiceRegistration asr2 = context.registerService(ServiceInterface.class.getName(), new ServiceProvider(),
+ new Hashtable() {{ put(DependencyManager.ASPECT, sid); put(Constants.SERVICE_RANKING, 20); }});
+
+ st.open();
+ Assert.assertEquals("There should be one service that matches the tracker", 1, st.getServices().length);
+ Assert.assertEquals("Service ranking should be 20", Integer.valueOf(20), (Integer) st.getServiceReference().getProperty(Constants.SERVICE_RANKING));
+
+ asr2.unregister();
+ Assert.assertEquals("There should be one service that matches the tracker", 1, st.getServices().length);
+ Assert.assertEquals("Service ranking should be 10", Integer.valueOf(10), (Integer) st.getServiceReference().getProperty(Constants.SERVICE_RANKING));
+
+ asr.unregister();
+ Assert.assertEquals("There should be one service that matches the tracker", 1, st.getServices().length);
+ Assert.assertNull("Service should not have a ranking", st.getServiceReference().getProperty(Constants.SERVICE_RANKING));
+
+ sr.unregister();
+ Assert.assertNull("There should be no service that matches the tracker", st.getServices());
+
+ st.close();
+ }
+
+ static interface ServiceInterface {
+ public void invoke();
+ }
+
+ static class ServiceProvider implements ServiceInterface {
+ public void invoke() {
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ServiceUpdateTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ServiceUpdateTest.java
new file mode 100644
index 0000000..6bc1522
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ServiceUpdateTest.java
@@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import java.net.URL;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Properties;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.ResourceHandler;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class ServiceUpdateTest extends TestBase {
+ public void testServiceUpdate() throws Exception {
+ final DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ // create a resource provider
+ ResourceProvider provider = new ResourceProvider(context, new URL("file://localhost/path/to/file1.txt"));
+ // activate it
+ m.add(m.createComponent()
+ .setImplementation(new ServiceProvider(e))
+ .add(m.createServiceDependency()
+ .setService(ServiceInterface.class)
+ .setRequired(true)
+ .setCallbacks("add", "change", "remove")
+ )
+ );
+
+ m.add(m.createComponent()
+ .setImplementation(provider)
+ .add(m.createServiceDependency()
+ .setService(ResourceHandler.class)
+ .setCallbacks("add", "remove")
+ )
+ );
+
+ // create a resource adapter for our single resource
+ // note that we can provide an actual implementation instance here because there will be only one
+ // adapter, normally you'd want to specify a Class here
+ CallbackInstance callbackInstance = new CallbackInstance(e);
+ Hashtable<String, String> serviceProps = new Hashtable<String, String>();
+ serviceProps.put("number", "1");
+ Component component = m.createResourceAdapterService("(&(path=/path/to/*.txt)(host=localhost))", false, callbackInstance, "changed")
+ .setImplementation(new ResourceAdapter(e))
+ .setInterface(ServiceInterface.class.getName(), serviceProps)
+ .setCallbacks(callbackInstance, "init", "start", "stop", "destroy");
+ m.add(component);
+ // wait until the single resource is available
+ e.waitForStep(1, 5000);
+ // wait until the component gets the dependency injected
+ e.waitForStep(2, 5000);
+ // trigger a 'change' in our resource
+ provider.change();
+ // wait until the changed callback is invoked
+ e.waitForStep(3, 5000);
+ // wait until the changed event arrived at the component
+ e.waitForStep(4, 5000);
+ System.out.println("Done!");
+ m.clear();
+ }
+
+ static class ResourceAdapter implements ServiceInterface {
+ protected URL m_resource; // injected by reflection.
+
+ ResourceAdapter(Ensure e) {
+ }
+
+ public void invoke() {
+ // TODO Auto-generated method stub
+
+ }
+ }
+
+ static interface ServiceInterface {
+ public void invoke();
+ }
+
+ static class ServiceProvider {
+ private final Ensure m_ensure;
+ public ServiceProvider(Ensure e) {
+ m_ensure = e;
+ }
+ void add(ServiceInterface i) {
+ m_ensure.step(2);
+ }
+ void change(ServiceInterface i) {
+ System.out.println("Change...");
+ m_ensure.step(4);
+ }
+ void remove(ServiceInterface i) {
+ System.out.println("Remove...");
+ }
+ }
+
+ static class CallbackInstance {
+ private final Ensure m_ensure;
+ public CallbackInstance(Ensure e) {
+ m_ensure = e;
+ }
+
+ void init() {
+ System.out.println("init");
+ }
+ void start() {
+ System.out.println("start");
+ m_ensure.step(1);
+ }
+ void stop() {
+ System.out.println("stop");
+ }
+ void destroy() {
+ System.out.println("destroy");
+ }
+ void changed(Component component) {
+ System.out.println("resource changed");
+ m_ensure.step(3);
+
+ Properties newProps = new Properties();
+ // update the component's service properties
+ Dictionary<String, String> dict = component.getServiceProperties();
+ Enumeration<String> e = dict.keys();
+ while (e.hasMoreElements()) {
+ String key = e.nextElement();
+ String value = dict.get(key);
+ newProps.setProperty(key, value);
+ }
+ newProps.setProperty("new-property", "2");
+ component.getServiceRegistration().setProperties(newProps);
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/TemporalServiceDependencyTest.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/TemporalServiceDependencyTest.java
new file mode 100644
index 0000000..97b1565
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/TemporalServiceDependencyTest.java
@@ -0,0 +1,206 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.api;
+
+import java.util.Hashtable;
+
+import org.junit.Assert;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.ServiceDependency;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+@SuppressWarnings({"unchecked", "rawtypes", "serial"})
+public class TemporalServiceDependencyTest extends TestBase {
+ public void testServiceConsumptionAndIntermittentAvailability() {
+ final DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ // create a service provider and consumer
+ TemporalServiceProvider provider = new TemporalServiceProvider(e);
+ Component sp = m.createComponent().setImplementation(provider).setInterface(TemporalServiceInterface.class.getName(), null);
+ TemporalServiceProvider2 provider2 = new TemporalServiceProvider2(e);
+ Component sp2 = m.createComponent().setImplementation(provider2).setInterface(TemporalServiceInterface.class.getName(), null);
+ TemporalServiceConsumer consumer = new TemporalServiceConsumer(e);
+ Component sc = m.createComponent().setImplementation(consumer)
+ .add(m.createTemporalServiceDependency(10000).setService(TemporalServiceInterface.class).setRequired(true));
+ // add the service consumer
+ m.add(sc);
+ // now add the first provider
+ m.add(sp);
+ e.waitForStep(2, 5000);
+ // and remove it again (this should not affect the consumer yet)
+ m.remove(sp);
+ // now add the second provider
+ m.add(sp2);
+ e.step(3);
+ e.waitForStep(4, 5000);
+ // and remove it again
+ m.remove(sp2);
+ // finally remove the consumer
+ m.remove(sc);
+ // ensure we executed all steps inside the component instance
+ e.step(6);
+ m.clear();
+ }
+
+ public void testServiceConsumptionWithCallbackAndIntermittentAvailability() {
+ final DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ // create a service provider and consumer
+ TemporalServiceProvider provider = new TemporalServiceProvider(e);
+ Component sp = m.createComponent().setImplementation(provider).setInterface(TemporalServiceInterface.class.getName(), null);
+ TemporalServiceProvider2 provider2 = new TemporalServiceProvider2(e);
+ Component sp2 = m.createComponent().setImplementation(provider2).setInterface(TemporalServiceInterface.class.getName(), null);
+ TemporalServiceConsumerWithCallback consumer = new TemporalServiceConsumerWithCallback(e);
+ ServiceDependency temporalDep = m.createTemporalServiceDependency(10000).setService(TemporalServiceInterface.class).setRequired(true).setCallbacks("add", "remove");
+ Component sc = m.createComponent().setImplementation(consumer).add(temporalDep);
+
+ // add the service consumer
+ m.add(sc);
+ // now add the first provider
+ m.add(sp);
+ e.waitForStep(2, 5000);
+ // and remove it again (this should not affect the consumer yet)
+ m.remove(sp);
+ // now add the second provider
+ m.add(sp2);
+ e.step(3);
+ e.waitForStep(4, 5000);
+ // and remove it again
+ m.remove(sp2);
+ // finally remove the consumer
+ m.remove(sc);
+ // Wait for the consumer.remove callback
+ e.waitForStep(6, 5000);
+ // ensure we executed all steps inside the component instance
+ e.step(7);
+ m.clear();
+ }
+
+ public void testFelix4602_PropagateServiceInvocationException() {
+ final DependencyManager m = getDM();
+ final Ensure ensure = new Ensure();
+ Runnable provider = new Runnable() {
+ public void run() {
+ throw new UncheckedException();
+ }
+ };
+ Hashtable props = new Hashtable();
+ props.put("target", getClass().getSimpleName());
+ Component providerComp = m.createComponent()
+ .setInterface(Runnable.class.getName(), props)
+ .setImplementation(provider);
+
+ Object consumer = new Object() {
+ volatile Runnable m_provider;
+ @SuppressWarnings("unused")
+ void start() {
+ try {
+ ensure.step(1);
+ m_provider.run();
+ } catch (UncheckedException e) {
+ ensure.step(2);
+ }
+ }
+ };
+ Component consumerComp = m.createComponent()
+ .setImplementation(consumer)
+ .add(m.createTemporalServiceDependency(5000)
+ .setService(Runnable.class, "(target=" + getClass().getSimpleName() + ")")
+ .setRequired(true));
+ m.add(consumerComp);
+ m.add(providerComp);
+ ensure.waitForStep(2, 5000);
+ m.clear();
+ }
+
+ static class UncheckedException extends RuntimeException {
+ }
+
+ static interface TemporalServiceInterface {
+ public void invoke();
+ }
+
+ static class TemporalServiceProvider implements TemporalServiceInterface {
+ private final Ensure m_ensure;
+ public TemporalServiceProvider(Ensure e) {
+ m_ensure = e;
+ }
+ public void invoke() {
+ m_ensure.step(2);
+ }
+ }
+
+ static class TemporalServiceProvider2 implements TemporalServiceInterface {
+ protected final Ensure m_ensure;
+ public TemporalServiceProvider2(Ensure e) {
+ m_ensure = e;
+ }
+ public void invoke() {
+ m_ensure.step(4);
+ }
+ }
+
+ static class TemporalServiceConsumer implements Runnable {
+ protected volatile TemporalServiceInterface m_service;
+ protected final Ensure m_ensure;
+
+ public TemporalServiceConsumer(Ensure e) {
+ m_ensure = e;
+ }
+
+ public void init() {
+ m_ensure.step(1);
+ Thread t = new Thread(this);
+ t.start();
+ }
+
+ public void run() {
+ m_service.invoke();
+ m_ensure.waitForStep(3, 15000);
+ m_service.invoke();
+ }
+
+ public void destroy() {
+ m_ensure.step(5);
+ }
+ }
+
+ static class TemporalServiceConsumerWithCallback extends TemporalServiceConsumer {
+ public TemporalServiceConsumerWithCallback(Ensure e) {
+ super(e);
+ }
+
+ public void add(TemporalServiceInterface service) {
+ m_service = service;
+ }
+
+ public void remove(TemporalServiceInterface service) {
+ Assert.assertTrue(m_service == service);
+ m_ensure.step(6);
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/bundle/Activator.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/bundle/Activator.java
new file mode 100644
index 0000000..a550e30
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/bundle/Activator.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.bundle;
+
+import org.apache.felix.dm.DependencyActivatorBase;
+import org.apache.felix.dm.DependencyManager;
+import org.osgi.framework.BundleContext;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class Activator extends DependencyActivatorBase {
+
+ @Override
+ public void init(BundleContext ctx, DependencyManager m)
+ throws Exception {
+ m.add(createComponent()
+ .setImplementation(TestComponent.class)
+ .setInterface(TestService.class.getName(), null));
+ }
+
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/bundle/TestComponent.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/bundle/TestComponent.java
new file mode 100644
index 0000000..2ff626f
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/bundle/TestComponent.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.bundle;
+
+/**
+ * Test Component used by the FELIX2955_ShellCommandTest test.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class TestComponent implements TestService {
+
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/bundle/TestService.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/bundle/TestService.java
new file mode 100644
index 0000000..6393075
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/bundle/TestService.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.bundle;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public interface TestService {
+
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/util/Ensure.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/util/Ensure.java
new file mode 100644
index 0000000..9c34741
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/util/Ensure.java
@@ -0,0 +1,174 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.util;
+
+import java.io.PrintStream;
+
+import org.junit.Assert;
+
+/**
+ * Helper class to make sure that steps in a test happen in the correct order. Instantiate
+ * this class and subsequently invoke <code>step(nr)</code> with steps starting at 1. You
+ * can also have threads wait until you arrive at a certain step.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class Ensure {
+ private final boolean DEBUG;
+ private static long INSTANCE = 0;
+ private static final int RESOLUTION = 100;
+ private static PrintStream STREAM = System.out;
+ int step = 0;
+ private Throwable m_throwable;
+
+ public Ensure() {
+ this(true);
+ }
+
+ public Ensure(boolean debug) {
+ DEBUG = debug;
+ if (DEBUG) {
+ INSTANCE++;
+ }
+ }
+
+ public void setStream(PrintStream output) {
+ STREAM = output;
+ }
+
+ /**
+ * Mark this point as step <code>nr</code>.
+ *
+ * @param nr the step we are in
+ */
+ public synchronized void step(int nr) {
+ step++;
+ Assert.assertEquals(nr, step);
+ if (DEBUG) {
+ String info = getLineInfo(3);
+ STREAM.println("[Ensure " + INSTANCE + "] step " + step + " [" + currentThread() + "] " + info);
+ }
+ notifyAll();
+ }
+
+ private String getLineInfo(int depth) {
+ StackTraceElement[] trace = Thread.currentThread().getStackTrace();
+ String info = trace[depth].getClassName() + "." + trace[depth].getMethodName() + ":" + trace[depth].getLineNumber();
+ return info;
+ }
+
+ /**
+ * Mark this point as the next step.
+ */
+ public synchronized void step() {
+ step++;
+ if (DEBUG) {
+ String info = getLineInfo(3);
+ STREAM.println("[Ensure " + INSTANCE + "] next step " + step + " [" + currentThread() + "] " + info);
+ }
+ notifyAll();
+ }
+
+ /**
+ * Wait until we arrive at least at step <code>nr</code> in the process, or fail if that
+ * takes more than <code>timeout</code> milliseconds. If you invoke wait on a thread,
+ * you are effectively assuming some other thread will invoke the <code>step(nr)</code>
+ * method.
+ *
+ * @param nr the step to wait for
+ * @param timeout the number of milliseconds to wait
+ */
+ public synchronized void waitForStep(int nr, int timeout) {
+ final int initialTimeout = timeout;
+ if (DEBUG) {
+ String info = getLineInfo(3);
+ STREAM.println("[Ensure " + INSTANCE + "] waiting for step " + nr + " [" + currentThread() + "] " + info);
+ }
+ while (step < nr && timeout > 0) {
+ try {
+ wait(RESOLUTION);
+ timeout -= RESOLUTION;
+ }
+ catch (InterruptedException e) {}
+ }
+ if (step < nr) {
+ throw new IllegalStateException("Timed out waiting for " + initialTimeout + " ms for step " + nr + ", we are still at step " + step);
+ }
+ if (DEBUG) {
+ String info = getLineInfo(3);
+ STREAM.println("[Ensure " + INSTANCE + "] arrived at step " + nr + " [" + currentThread() + "] " + info);
+ }
+ }
+
+ private String currentThread() {
+ Thread thread = Thread.currentThread();
+ return thread.getId() + " " + thread.getName();
+ }
+
+ public static Runnable createRunnableStep(final Ensure ensure, final int nr) {
+ return new Runnable() { public void run() { ensure.step(nr); }};
+ }
+
+ public synchronized void steps(Steps steps) {
+ steps.next(this);
+ }
+
+ /**
+ * Helper class for naming a list of step numbers. If used with the steps(Steps) method
+ * you can define at which steps in time this point should be passed. That means you can
+ * check methods that will get invoked multiple times during a test.
+ */
+ public static class Steps {
+ private final int[] m_steps;
+ private int m_stepIndex;
+
+ /**
+ * Create a list of steps and initialize the step counter to zero.
+ */
+ public Steps(int... steps) {
+ m_steps = steps;
+ m_stepIndex = 0;
+ }
+
+ /**
+ * Ensure we're at the right step. Will throw an index out of bounds exception if we enter this step more often than defined.
+ */
+ public void next(Ensure ensure) {
+ ensure.step(m_steps[m_stepIndex++]);
+ }
+ }
+
+ /**
+ * Saves a thrown exception that occurred in a different thread. You can only save one exception
+ * at a time this way.
+ */
+ public synchronized void throwable(Throwable throwable) {
+ m_throwable = throwable;
+ }
+
+ /**
+ * Throws a <code>Throwable</code> if one occurred in a different thread and that thread saved it
+ * using the <code>throwable()</code> method.
+ */
+ public synchronized void ensure() throws Throwable {
+ if (m_throwable != null) {
+ throw m_throwable;
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/util/ServiceUtil.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/util/ServiceUtil.java
new file mode 100644
index 0000000..628189c
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/util/ServiceUtil.java
@@ -0,0 +1,176 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.util;
+
+import java.util.List;
+
+import org.apache.felix.dm.DependencyManager;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * OSGi service utilities (copied from dependency manager implementation).
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class ServiceUtil {
+ /**
+ * 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 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;
+ }
+ return new Integer(0);
+ }
+
+ /**
+ * 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 getServiceId(ServiceReference ref) {
+ 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) {
+ Long aid = (Long) ref.getProperty(DependencyManager.ASPECT);
+ if (aid != null) {
+ return aid;
+ }
+ Long sid = (Long) ref.getProperty(Constants.SERVICE_ID);
+ if (sid != null) {
+ return sid;
+ }
+ throw new IllegalArgumentException("Invalid service reference, no service ID found");
+ }
+
+ /**
+ * Determines if the service is an aspect as defined by the dependency manager.
+ * Aspects are defined by a property and this method will check for its presence.
+ *
+ * @param ref the service reference
+ * @return <code>true</code> if it's an aspect, <code>false</code> otherwise
+ */
+ public static boolean isAspect(ServiceReference ref) {
+ Long aid = (Long) ref.getProperty(DependencyManager.ASPECT);
+ return (aid != null);
+ }
+
+ /**
+ * Converts a service reference to a string, listing both the bundle it was
+ * registered from and all properties.
+ *
+ * @param ref the service reference
+ * @return a string representation of the service
+ */
+ public static String toString(ServiceReference ref) {
+ if (ref == null) {
+ return "ServiceReference[null]";
+ }
+ else {
+ StringBuffer buf = new StringBuffer();
+ Bundle bundle = ref.getBundle();
+ if (bundle != null) {
+ buf.append("ServiceReference[");
+ buf.append(bundle.getBundleId());
+ buf.append("]{");
+ }
+ else {
+ buf.append("ServiceReference[unregistered]{");
+ }
+ buf.append(propertiesToString(ref, null));
+ buf.append("}");
+ return buf.toString();
+ }
+ }
+
+ /**
+ * Converts the properties of a service reference to a string.
+ *
+ * @param ref the service reference
+ * @param exclude a list of properties to exclude, or <code>null</code> to show everything
+ * @return a string representation of the service properties
+ */
+ public static String propertiesToString(ServiceReference ref, List<String> exclude) {
+ StringBuffer buf = new StringBuffer();
+ String[] keys = ref.getPropertyKeys();
+ for (int i = 0; i < keys.length; i++) {
+ if (i > 0) {
+ buf.append(',');
+ }
+ buf.append(keys[i]);
+ buf.append('=');
+ Object val = ref.getProperty(keys[i]);
+ if (exclude == null || !exclude.contains(val)) {
+ if (val instanceof String[]) {
+ String[] valArray = (String[]) val;
+ StringBuffer valBuf = new StringBuffer();
+ valBuf.append('{');
+ for (int j = 0; j < valArray.length; j++) {
+ if (valBuf.length() > 1) {
+ valBuf.append(',');
+ }
+ valBuf.append(valArray[j].toString());
+ }
+ valBuf.append('}');
+ buf.append(valBuf);
+ }
+ else {
+ buf.append(val.toString());
+ }
+ }
+ }
+ return buf.toString();
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/util/TestBase.java b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/util/TestBase.java
new file mode 100644
index 0000000..4cedfb6
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/util/TestBase.java
@@ -0,0 +1,354 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.itest.util;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Hashtable;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Assert;
+import junit.framework.TestCase;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.ComponentExecutorFactory;
+import org.apache.felix.dm.DependencyManager;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.FrameworkEvent;
+import org.osgi.framework.FrameworkListener;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.log.LogService;
+
+/**
+ * Base class for all integration tests.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public abstract class TestBase extends TestCase implements LogService, FrameworkListener {
+ // Default OSGI log service level.
+ protected final static int LOG_LEVEL = LogService.LOG_WARNING;
+
+ // optional thread pool used by parallel dependency managers
+ private volatile ExecutorService m_threadPool;
+
+ // flag used to check if the threadpool must be used for a given test.
+ protected volatile boolean m_parallel;
+
+ // Flag used to check if some errors have been logged during the execution of a given test.
+ private volatile boolean m_errorsLogged;
+
+ // We implement OSGI log service.
+ protected ServiceRegistration logService;
+
+ // Our bundle context
+ protected BundleContext context;
+
+ // Our dependency manager used to create test components.
+ protected volatile DependencyManager m_dm;
+
+ // The Registration for the DM threadpool.
+ private ServiceRegistration m_componentExecutorFactoryReg;
+
+ public TestBase() {
+ }
+
+ protected void setParallel() {
+ m_parallel = true;
+ }
+
+ public void setUp() throws Exception {
+ warn("Setting up test " + getClass().getName());
+ context = FrameworkUtil.getBundle(this.getClass()).getBundleContext();
+ Hashtable<String, Object> props = new Hashtable<>();
+ props.put(Constants.SERVICE_RANKING, new Integer(Integer.MAX_VALUE));
+ logService = context.registerService(LogService.class.getName(), this, props);
+ context.addFrameworkListener(this);
+ m_dm = new DependencyManager(context);
+ if (m_parallel) {
+ warn("Using threadpool ...");
+ m_threadPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
+ m_componentExecutorFactoryReg = context.registerService(ComponentExecutorFactory.class.getName(),
+ new ComponentExecutorFactory() {
+ @Override
+ public Executor getExecutorFor(Component component) {
+ return m_threadPool;
+ }
+ },
+ null);
+ }
+ }
+
+ public void tearDown() throws Exception {
+ warn("Tearing down test " + getClass().getName());
+ logService.unregister();
+ context.removeFrameworkListener(this);
+ clearComponents();
+ if (m_parallel && m_componentExecutorFactoryReg != null) {
+ m_componentExecutorFactoryReg.unregister();
+ m_threadPool.shutdown();
+ try {
+ m_threadPool.awaitTermination(60, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ }
+ }
+ Assert.assertFalse(errorsLogged());
+ }
+
+ protected DependencyManager getDM() {
+ return m_dm;
+ }
+
+ protected void clearComponents() throws InterruptedException {
+ m_dm.clear();
+ warn("All component cleared.");
+ }
+
+ /**
+ * Creates and provides an Ensure object with a name service property into the OSGi service registry.
+ */
+ protected ServiceRegistration register(Ensure e, String name) {
+ Hashtable<String, String> props = new Hashtable<String, String>();
+ props.put("name", name);
+ return context.registerService(Ensure.class.getName(), e, props);
+ }
+
+ /**
+ * Helper method used to stop a given bundle.
+ *
+ * @param symbolicName
+ * the symbolic name of the bundle to be stopped.
+ */
+ protected void stopBundle(String symbolicName) {
+ // Stop the test.annotation bundle
+ boolean found = false;
+ for (Bundle b : context.getBundles()) {
+ if (b.getSymbolicName().equals(symbolicName)) {
+ try {
+ found = true;
+ b.stop();
+ } catch (BundleException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ if (!found) {
+ throw new IllegalStateException("bundle " + symbolicName + " not found");
+ }
+ }
+
+ /**
+ * Helper method used to start a given bundle.
+ *
+ * @param symbolicName
+ * the symbolic name of the bundle to be started.
+ */
+ protected void startBundle(String symbolicName) {
+ // Stop the test.annotation bundle
+ boolean found = false;
+ for (Bundle b : context.getBundles()) {
+ if (b.getSymbolicName().equals(symbolicName)) {
+ try {
+ found = true;
+ b.start();
+ } catch (BundleException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ if (!found) {
+ throw new IllegalStateException("bundle " + symbolicName + " not found");
+ }
+ }
+
+ /**
+ * Helper method used to get a given bundle.
+ *
+ * @param symbolicName
+ * the symbolic name of the bundle to get.
+ */
+ protected Bundle getBundle(String symbolicName) {
+ for (Bundle b : context.getBundles()) {
+ if (b.getSymbolicName().equals(symbolicName)) {
+ return b;
+ }
+ }
+ throw new IllegalStateException("bundle " + symbolicName + " not found");
+ }
+
+ /**
+ * Suspend the current thread for a while.
+ *
+ * @param n
+ * the number of milliseconds to wait for.
+ */
+ protected void sleep(int ms) {
+ try {
+ Thread.sleep(ms);
+ } catch (InterruptedException e) {
+ }
+ }
+
+ public void log(int level, String message) {
+ checkError(level, null);
+ if (LOG_LEVEL >= level) {
+ System.out.println(getLevel(level) + " - " + Thread.currentThread().getName() + " : " + message);
+ }
+ }
+
+ public void log(int level, String message, Throwable exception) {
+ checkError(level, exception);
+ if (LOG_LEVEL >= level) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(getLevel(level) + " - " + Thread.currentThread().getName() + " : ");
+ sb.append(message);
+ parse(sb, exception);
+ System.out.println(sb.toString());
+ }
+ }
+
+ public void log(ServiceReference sr, int level, String message) {
+ checkError(level, null);
+ if (LOG_LEVEL >= level) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(getLevel(level) + " - " + Thread.currentThread().getName() + " : ");
+ sb.append(message);
+ System.out.println(sb.toString());
+ }
+ }
+
+ public void log(ServiceReference sr, int level, String message, Throwable exception) {
+ checkError(level, exception);
+ if (LOG_LEVEL >= level) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(getLevel(level) + " - " + Thread.currentThread().getName() + " : ");
+ sb.append(message);
+ parse(sb, exception);
+ System.out.println(sb.toString());
+ }
+ }
+
+ protected boolean errorsLogged() {
+ return m_errorsLogged;
+ }
+
+ private void parse(StringBuilder sb, Throwable t) {
+ if (t != null) {
+ sb.append(" - ");
+ StringWriter buffer = new StringWriter();
+ PrintWriter pw = new PrintWriter(buffer);
+ t.printStackTrace(pw);
+ sb.append(buffer.toString());
+ m_errorsLogged = true;
+ }
+ }
+
+ private String getLevel(int level) {
+ switch (level) {
+ case LogService.LOG_DEBUG :
+ return "DEBUG";
+ case LogService.LOG_ERROR :
+ return "ERROR";
+ case LogService.LOG_INFO :
+ return "INFO";
+ case LogService.LOG_WARNING :
+ return "WARN";
+ default :
+ return "";
+ }
+ }
+
+ private void checkError(int level, Throwable exception) {
+ if (level <= LOG_ERROR) {
+ m_errorsLogged = true;
+ }
+ if (exception != null) {
+ m_errorsLogged = true;
+ }
+ }
+
+ public void frameworkEvent(FrameworkEvent event) {
+ int eventType = event.getType();
+ String msg = getFrameworkEventMessage(eventType);
+ int level = (eventType == FrameworkEvent.ERROR) ? LOG_ERROR : LOG_WARNING;
+ if (msg != null) {
+ log(level, msg, event.getThrowable());
+ } else {
+ log(level, "Unknown fwk event: " + event);
+ }
+ }
+
+ private String getFrameworkEventMessage(int event) {
+ switch (event) {
+ case FrameworkEvent.ERROR :
+ return "FrameworkEvent: ERROR";
+ case FrameworkEvent.INFO :
+ return "FrameworkEvent INFO";
+ case FrameworkEvent.PACKAGES_REFRESHED :
+ return "FrameworkEvent: PACKAGE REFRESHED";
+ case FrameworkEvent.STARTED :
+ return "FrameworkEvent: STARTED";
+ case FrameworkEvent.STARTLEVEL_CHANGED :
+ return "FrameworkEvent: STARTLEVEL CHANGED";
+ case FrameworkEvent.WARNING :
+ return "FrameworkEvent: WARNING";
+ default :
+ return null;
+ }
+ }
+
+ protected void warn(String msg, Object ... params) {
+ if (LOG_LEVEL >= LogService.LOG_WARNING) {
+ log(LogService.LOG_WARNING, params.length > 0 ? String.format(msg, params) : msg);
+ }
+ }
+
+ @SuppressWarnings("unused")
+ protected void info(String msg, Object ... params) {
+ if (LOG_LEVEL >= LogService.LOG_INFO) {
+ log(LogService.LOG_INFO, params.length > 0 ? String.format(msg, params) : msg);
+ }
+ }
+
+ @SuppressWarnings("unused")
+ protected void debug(String msg, Object ... params) {
+ if (LOG_LEVEL >= LogService.LOG_DEBUG) {
+ log(LogService.LOG_DEBUG, params.length > 0 ? String.format(msg, params) : msg);
+ }
+ }
+
+ protected void error(String msg, Object ... params) {
+ log(LogService.LOG_ERROR, params.length > 0 ? String.format(msg, params) : msg);
+ }
+
+ protected void error(String msg, Throwable err, Object ... params) {
+ log(LogService.LOG_ERROR, params.length > 0 ? String.format(msg, params) : msg, err);
+ }
+
+ protected void error(Throwable err) {
+ log(LogService.LOG_ERROR, "error", err);
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/util/packageinfo b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/util/packageinfo
new file mode 100644
index 0000000..e252556
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/util/packageinfo
@@ -0,0 +1 @@
+version 1.0.0
\ No newline at end of file