FELIX-4116 Ability to listen for component service dependencies providings configuration properties
Apply patch.
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1493172 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/ipojo/runtime/core-it/src/it/ipojo-core-configuration-test/src/test/java/org/apache/felix/ipojo/runtime/core/TestListeners.java b/ipojo/runtime/core-it/src/it/ipojo-core-configuration-test/src/test/java/org/apache/felix/ipojo/runtime/core/TestListeners.java
new file mode 100644
index 0000000..56ba027
--- /dev/null
+++ b/ipojo/runtime/core-it/src/it/ipojo-core-configuration-test/src/test/java/org/apache/felix/ipojo/runtime/core/TestListeners.java
@@ -0,0 +1,202 @@
+/*
+ * 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.ipojo.runtime.core;
+
+import org.apache.felix.ipojo.ComponentInstance;
+import org.apache.felix.ipojo.handlers.configuration.ConfigurationHandlerDescription;
+import org.apache.felix.ipojo.handlers.configuration.ConfigurationListener;
+import org.apache.felix.ipojo.runtime.core.services.FooService;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.osgi.framework.ServiceReference;
+
+import java.util.*;
+
+import static org.junit.Assert.*;
+
+/**
+ * Test that the ConfigurationHandler calls the ConfigurationListeners when watched instance is reconfigured.
+ */
+public class TestListeners extends Common {
+
+ /**
+ * The number of reconfigurations, incremented by the {@code CountingListener}s.
+ */
+ int updates = 0;
+
+ /**
+ * The {@code instance} arguments received by the {@code AppendingListener}s.
+ */
+ List<ComponentInstance> instances = new ArrayList<ComponentInstance>();
+
+ /**
+ * The {@code configuration} arguments received by the {@code AppendingListener}s.
+ */
+ List<Map<String, Object>> configs = new ArrayList<Map<String, Object>>();
+
+ /**
+ * The tested component instance.
+ */
+ ComponentInstance fooProvider;
+
+ /**
+ * The configuration handler description of the tested component instance.
+ */
+ ConfigurationHandlerDescription fooConfig;
+
+ /**
+ * The service provided by the tested component instance.
+ */
+ FooService foo;
+
+ @Before
+ public void setUp() {
+ Properties p = new Properties();
+ p.put("instance.name", "FooProvider-42");
+ p.put("int", 4);
+ p.put("boolean", false);
+ p.put("string", "bar");
+ p.put("strAProp", new String[]{"bar", "foo"});
+ p.put("intAProp", new int[]{1, 2, 3});
+ fooProvider = ipojoHelper.createComponentInstance("CONFIG-FooProviderType-ConfUpdated", p);
+ fooConfig = (ConfigurationHandlerDescription) fooProvider.getInstanceDescription()
+ .getHandlerDescription("org.apache.felix.ipojo:properties");
+
+ // Get the service
+ ServiceReference ref = ipojoHelper.getServiceReferenceByName(FooService.class.getName(), "FooProvider-42");
+ foo = (FooService) osgiHelper.getServiceObject(ref);
+ }
+
+ @After
+ public void tearDown() {
+ fooConfig = null;
+ fooProvider.dispose();
+ fooProvider = null;
+ }
+
+ /**
+ * A ConfigurationListener that just count its calls.
+ */
+ private class CountingListener implements ConfigurationListener {
+ public void configurationChanged(ComponentInstance instance, Map<String, Object> configuration) {
+ updates++;
+ }
+ }
+
+ /**
+ * A ConfigurationListener that just fails.
+ * <p>
+ * Used to ensure that a failing listener does not prevent the following listeners to be called.
+ * </p>
+ */
+ private class ThrowingListener implements ConfigurationListener {
+ public void configurationChanged(ComponentInstance instance, Map<String, Object> configuration) {
+ throw new RuntimeException("I'm bad");
+ }
+ }
+
+ /**
+ * A ConfigurationListener that just appends its arguments.
+ */
+ private class AppendingListener implements ConfigurationListener {
+ public void configurationChanged(ComponentInstance instance, Map<String, Object> configuration) {
+ instances.add(instance);
+ configs.add(configuration);
+ }
+ }
+
+ /**
+ * Test that the listeners registered on the tested instance are called when the instance is reconfigured.
+ */
+ @Test
+ public void testConfigurationListener() {
+ // Register listeners
+ ConfigurationListener l1 = new CountingListener();
+ fooConfig.addListener(l1);
+ ConfigurationListener l2 = new ThrowingListener();
+ fooConfig.addListener(l2);
+ ConfigurationListener l3 = new AppendingListener();
+ fooConfig.addListener(l3);
+
+ // Trigger a manual reconfiguration
+ Hashtable<String, Object> conf = new Hashtable<String, Object>();
+ conf.put("int", 40);
+ conf.put("boolean", true);
+ conf.put("string", "saloon");
+ conf.put("strAProp", new String[]{"bar", "bad"});
+ conf.put("intAProp", new int[]{21, 22, 23});
+ fooProvider.reconfigure(conf);
+
+ // Check the listeners has been called + check the arguments.
+ assertEquals(1, updates);
+
+ assertEquals(1, instances.size());
+ assertSame(fooProvider, instances.get(0));
+
+ assertEquals(1, configs.size());
+ Map<String, Object> configMap = configs.get(0);
+ assertEquals(5, configMap.size());
+ assertEquals(40, configMap.get("int"));
+ assertEquals(true, configMap.get("boolean"));
+ assertEquals("saloon", configMap.get("string"));
+ assertArrayEquals(new String[]{"bar", "bad"}, (String[]) configMap.get("strAProp"));
+ assertArrayEquals(new int[]{21, 22, 23}, (int[]) configMap.get("intAProp"));
+
+ // Trigger a POJO internal "reconfiguration".
+ // It should not trigger any reconfiguration event.
+ foo.foo();
+
+ // Check nothing has changed in the listeners
+ assertEquals(1, updates);
+ assertEquals(1, instances.size());
+ assertEquals(1, configs.size());
+
+ // Unregister the listeners
+ fooConfig.removeListener(l1);
+ fooConfig.removeListener(l2);
+ fooConfig.removeListener(l3);
+
+ // Trigger a manual reconfiguration
+ conf.put("int", 40);
+ conf.put("boolean", true);
+ conf.put("string", "saloon");
+ conf.put("strAProp", new String[]{"bar", "bad"});
+ conf.put("intAProp", new int[]{21, 22, 23});
+ fooProvider.reconfigure(conf);
+
+ // Check nothing has changed in the listeners, since they are all removed
+ assertEquals(1, updates);
+ assertEquals(1, instances.size());
+ assertEquals(1, configs.size());
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testNullProvidedServiceListener() {
+ // Should fail!
+ fooConfig.addListener(null);
+ }
+
+ @Test(expected = NoSuchElementException.class)
+ public void testRemoveNonexistentProvidedServiceListener() {
+ // Should fail!
+ fooConfig.removeListener(new ThrowingListener());
+ }
+}
diff --git a/ipojo/runtime/core-it/src/it/ipojo-core-service-providing-test/src/test/java/org/apache/felix/ipojo/runtime/core/TestListeners.java b/ipojo/runtime/core-it/src/it/ipojo-core-service-providing-test/src/test/java/org/apache/felix/ipojo/runtime/core/TestListeners.java
new file mode 100644
index 0000000..1ba8114
--- /dev/null
+++ b/ipojo/runtime/core-it/src/it/ipojo-core-service-providing-test/src/test/java/org/apache/felix/ipojo/runtime/core/TestListeners.java
@@ -0,0 +1,272 @@
+/*
+ * 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.ipojo.runtime.core;
+
+import org.apache.felix.ipojo.ComponentInstance;
+import org.apache.felix.ipojo.handlers.providedservice.ProvidedService;
+import org.apache.felix.ipojo.handlers.providedservice.ProvidedServiceDescription;
+import org.apache.felix.ipojo.handlers.providedservice.ProvidedServiceHandlerDescription;
+import org.apache.felix.ipojo.handlers.providedservice.ProvidedServiceListener;
+import org.apache.felix.ipojo.runtime.core.services.CheckService;
+import org.apache.felix.ipojo.runtime.core.services.FooService;
+import org.junit.Test;
+
+import java.util.*;
+
+import static org.junit.Assert.*;
+
+/**
+ * Test that the ProvidedServiceHandler calls the ProvidedServiceHandlerListeners when watched provided service changes.
+ */
+public class TestListeners extends Common {
+
+ /**
+ * The number of provided service registrations, incremented by the {@code CountingListener}s.
+ */
+ int registrations = 0;
+
+ /**
+ * The number of provided service unregistrations, incremented by the {@code CountingListener}s.
+ */
+ int unregistrations = 0;
+
+ /**
+ * The number of provided service updates, incremented by the {@code CountingListener}s.
+ */
+ int updates = 0;
+
+ /**
+ * The total number of provided service events, incremented by the {@code TotalCountingListener}s.
+ */
+ int total = 0;
+
+ /**
+ * The {@code instance} arguments received by the {@code AppendingListener}s.
+ */
+ List<ComponentInstance> instances = new ArrayList<ComponentInstance>();
+
+ /**
+ * A ProvidedServiceListener that just count service registrations, updates and unregistrations.
+ */
+ private class CountingListener implements ProvidedServiceListener {
+ public void serviceRegistered(ComponentInstance instance, ProvidedService providedService) {
+ registrations++;
+ }
+ public void serviceModified(ComponentInstance instance, ProvidedService providedService) {
+ updates++;
+ }
+ public void serviceUnregistered(ComponentInstance instance, ProvidedService providedService) {
+ unregistrations++;
+ }
+ }
+
+ /**
+ * A ProvidedServiceListener that just count events.
+ */
+ private class TotalCountingListener implements ProvidedServiceListener {
+ public void serviceRegistered(ComponentInstance instance, ProvidedService providedService) {
+ total++;
+ }
+ public void serviceModified(ComponentInstance instance, ProvidedService providedService) {
+ total++;
+ }
+ public void serviceUnregistered(ComponentInstance instance, ProvidedService providedService) {
+ total++;
+ }
+ }
+
+ /**
+ * A ProvidedServiceListener that just fails.
+ * <p>
+ * Used to ensure that a failing listener does not prevent the following listeners to be called.
+ * </p>
+ */
+ private class ThrowingListener implements ProvidedServiceListener {
+ public void serviceRegistered(ComponentInstance instance, ProvidedService providedService) {
+ throw new RuntimeException("I'm bad");
+ }
+ public void serviceModified(ComponentInstance instance, ProvidedService providedService) {
+ throw new RuntimeException("I'm bad");
+ }
+ public void serviceUnregistered(ComponentInstance instance, ProvidedService providedService) {
+ throw new RuntimeException("I'm bad");
+ }
+ }
+
+ /**
+ * A ProvidedServiceListener that just appends its arguments.
+ */
+ private class AppendingListener implements ProvidedServiceListener {
+ public void serviceRegistered(ComponentInstance instance, ProvidedService providedService) {
+ instances.add(instance);
+ }
+ public void serviceModified(ComponentInstance instance, ProvidedService providedService) {
+ instances.add(instance);
+ }
+ public void serviceUnregistered(ComponentInstance instance, ProvidedService providedService) {
+ instances.add(instance);
+ }
+ }
+
+ /**
+ * Test that the listeners registered on the tested instance are called when the instance provided service is
+ * registered, updated or unregistered.
+ */
+ @Test
+ public void testProvidedServiceListener() {
+ ComponentInstance ci = ipojoHelper.createComponentInstance("PS-Controller-1-default");
+ ProvidedServiceHandlerDescription pshd = (ProvidedServiceHandlerDescription) ci.getInstanceDescription()
+ .getHandlerDescription("org.apache.felix.ipojo:provides");
+ ProvidedServiceDescription ps = getPS(FooService.class.getName(), pshd.getProvidedServices());
+
+ // Controller set to true.
+ osgiHelper.waitForService(FooService.class.getName(), null, 5000);
+ osgiHelper.waitForService(CheckService.class.getName(), null, 5000);
+
+ CheckService check = (CheckService) osgiHelper.getServiceObject(CheckService.class.getName(), null);
+ assertNotNull(check);
+
+ // Register listeners :
+ // 1- CountingListener l1
+ // 2- ThrowingListener bad
+ // 3- TotalCountingListener l2
+ // 4- AppendingListener l3
+ ProvidedServiceListener l1 = new CountingListener();
+ ps.addListener(l1);
+ ProvidedServiceListener bad = new ThrowingListener();
+ ps.addListener(bad);
+ ProvidedServiceListener l2 = new TotalCountingListener();
+ ps.addListener(l2);
+ ProvidedServiceListener l3 = new AppendingListener();
+ ps.addListener(l3);
+
+ // Check initial valued are untouched
+ assertEquals(0, registrations);
+ assertEquals(0, unregistrations);
+ assertEquals(0, updates);
+ assertEquals(0, total);
+
+ // Unregister the service and check.
+ assertFalse(check.check());
+ assertEquals(0, registrations);
+ assertEquals(1, unregistrations);
+ assertEquals(0, updates);
+ assertEquals(1, total);
+
+ // Modify the service while it is unregistered. Nothing should move.
+ Hashtable<String, Object> props = new Hashtable<String, Object>();
+ props.put("change1", "1");
+ ps.addProperties(props);
+ assertEquals(0, registrations);
+ assertEquals(1, unregistrations);
+ assertEquals(0, updates);
+ assertEquals(1, total);
+
+ // Register the service and check.
+ assertTrue(check.check());
+ assertEquals(1, registrations);
+ assertEquals(1, unregistrations);
+ assertEquals(0, updates);
+ assertEquals(2, total);
+
+ // Modify the service while it is REGISTERED
+ props.clear();
+ props.put("change2", "2");
+ ps.addProperties(props);
+ assertEquals(1, registrations);
+ assertEquals(1, unregistrations);
+ assertEquals(1, updates);
+ assertEquals(3, total);
+
+ // One more time, just to be sure...
+ assertFalse(check.check()); // Unregister
+ assertEquals(1, registrations);
+ assertEquals(2, unregistrations);
+ assertEquals(1, updates);
+ assertEquals(4, total);
+ assertTrue(check.check()); // Register
+ assertEquals(2, registrations);
+ assertEquals(2, unregistrations);
+ assertEquals(1, updates);
+ assertEquals(5, total);
+
+ // Unregister the listener
+ ps.removeListener(l1);
+ ps.removeListener(bad);
+ ps.removeListener(l2);
+ ps.removeListener(l3);
+
+ // Play with the controller and check that nothing moves
+ assertFalse(check.check()); // Unregister
+ assertEquals(2, registrations);
+ assertEquals(2, unregistrations);
+ assertEquals(1, updates);
+ assertEquals(5, total);
+ assertTrue(check.check()); // Register
+ assertEquals(2, registrations);
+ assertEquals(2, unregistrations);
+ assertEquals(1, updates);
+ assertEquals(5, total);
+ props.clear(); props.put("change3", "3"); ps.addProperties(props); // Modify
+ assertEquals(2, registrations);
+ assertEquals(2, unregistrations);
+ assertEquals(1, updates);
+ assertEquals(5, total);
+
+ // Check that instances contains $total times the ci component instance, and nothing else.
+ assertEquals(5, instances.size());
+ instances.removeAll(Collections.singleton(ci));
+ assertEquals(0, instances.size());
+
+ ci.dispose();
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testNullProvidedServiceListener() {
+ ComponentInstance ci = ipojoHelper.createComponentInstance("PS-Controller-1-default");
+ ProvidedServiceHandlerDescription pshd = (ProvidedServiceHandlerDescription) ci.getInstanceDescription()
+ .getHandlerDescription("org.apache.felix.ipojo:provides");
+ ProvidedServiceDescription ps = getPS(FooService.class.getName(), pshd.getProvidedServices());
+
+ // Should fail!
+ ps.addListener(null);
+ }
+
+ @Test(expected = NoSuchElementException.class)
+ public void testRemoveNonexistentProvidedServiceListener() {
+ ComponentInstance ci = ipojoHelper.createComponentInstance("PS-Controller-1-default");
+ ProvidedServiceHandlerDescription pshd = (ProvidedServiceHandlerDescription) ci.getInstanceDescription()
+ .getHandlerDescription("org.apache.felix.ipojo:provides");
+ ProvidedServiceDescription ps = getPS(FooService.class.getName(), pshd.getProvidedServices());
+
+ // Should fail!
+ ps.removeListener(new ThrowingListener());
+ }
+
+ private ProvidedServiceDescription getPS(String itf, ProvidedServiceDescription[] svc) {
+ for (int i = 0; i < svc.length; i++) {
+ if (svc[i].getServiceSpecifications()[0].equals(itf)) {
+ return svc[i];
+ }
+ }
+
+ fail("Service : " + itf + " not found");
+ return null;
+ }
+}
diff --git a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/dependency/impl/ServiceReferenceManager.java b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/dependency/impl/ServiceReferenceManager.java
index 639bf0a..cf56966 100644
--- a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/dependency/impl/ServiceReferenceManager.java
+++ b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/dependency/impl/ServiceReferenceManager.java
@@ -30,6 +30,11 @@
import java.util.*;
+import static org.apache.felix.ipojo.util.DependencyModel.DependencyEventType;
+import static org.apache.felix.ipojo.util.DependencyModel.DependencyEventType.ARRIVAL;
+import static org.apache.felix.ipojo.util.DependencyModel.DependencyEventType.DEPARTURE;
+import static org.apache.felix.ipojo.util.DependencyModel.DependencyEventType.MODIFIED;
+
/**
* This class is handling the transformations between the base service set and the selected service set.
* It handles the matching services and the selected service set.
@@ -384,6 +389,7 @@
// Callback invoked outside of locks.
// The called method is taking the write lock anyway.
onNewMatchingService(ref);
+ m_dependency.notifyListeners(ARRIVAL, ref, null);
}
}
@@ -485,6 +491,11 @@
// 1) the service was matching and does not match anymore -> it's a departure.
// 2) the service was not matching and matches -> it's an arrival
// 3) the service was matching and still matches -> it's a modification.
+
+ // The dependency event to send
+ DependencyEventType eventType = null;
+ ServiceReference<?> eventRef = null;
+
try {
m_dependency.acquireWriteLockIfNotHeld();
@@ -497,12 +508,16 @@
// case 1
m_matchingReferences.remove(reference);
onDepartureOfAMatchingService(initial, service);
+ eventType = DEPARTURE;
+ eventRef = initial;
} else {
// Do we have a real change
if (!ServiceReferenceUtils.haveSameProperties(initial, accepted)) {
// case 3
m_matchingReferences.put(reference, accepted);
onModificationOfAMatchingService(accepted, service);
+ eventType = MODIFIED;
+ eventRef = accepted;
}
}
} else {
@@ -513,11 +528,17 @@
// case 2
m_matchingReferences.put(reference, transformed);
onNewMatchingService(transformed);
+ eventType = ARRIVAL;
+ eventRef = transformed;
}
}
} finally {
m_dependency.releaseWriteLockIfHeld();
}
+
+ if (eventType != null) {
+ m_dependency.notifyListeners(eventType, eventRef, service);
+ }
}
public void onDepartureOfAMatchingService(TransformedServiceReference reference, Object service) {
@@ -544,9 +565,10 @@
// 1 - the service was in the matching set => real departure
// 2 - the service was not in the matching set => nothing do do.
+ TransformedServiceReference initial = null;
try {
m_dependency.acquireWriteLockIfNotHeld();
- TransformedServiceReference initial = m_matchingReferences.remove(reference);
+ initial = m_matchingReferences.remove(reference);
if (initial != null) {
// Case 1
onDepartureOfAMatchingService(initial, service);
@@ -556,6 +578,11 @@
m_dependency.releaseWriteLockIfHeld();
}
+ // Call the listeners outside the locked region.
+ if (initial != null) {
+ m_dependency.notifyListeners(DEPARTURE, initial, service);
+ }
+
}
/**
diff --git a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/configuration/ConfigurationHandler.java b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/configuration/ConfigurationHandler.java
index 0dead11..267c1a5 100644
--- a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/configuration/ConfigurationHandler.java
+++ b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/configuration/ConfigurationHandler.java
@@ -18,10 +18,7 @@
*/
package org.apache.felix.ipojo.handlers.configuration;
-import org.apache.felix.ipojo.ConfigurationException;
-import org.apache.felix.ipojo.Factory;
-import org.apache.felix.ipojo.HandlerFactory;
-import org.apache.felix.ipojo.PrimitiveHandler;
+import org.apache.felix.ipojo.*;
import org.apache.felix.ipojo.architecture.ComponentTypeDescription;
import org.apache.felix.ipojo.architecture.HandlerDescription;
import org.apache.felix.ipojo.architecture.PropertyDescription;
@@ -97,6 +94,11 @@
private Callback m_updated;
/**
+ * The configuration listeners.
+ */
+ private List<ConfigurationListener> m_listeners = new ArrayList<ConfigurationListener>();
+
+ /**
* Initialize the component type.
*
* @param desc : component type description to populate.
@@ -386,19 +388,27 @@
* @param configuration : the new configuration
* @see org.apache.felix.ipojo.Handler#reconfigure(java.util.Dictionary)
*/
- public synchronized void reconfigure(Dictionary configuration) {
- info(getInstanceManager().getInstanceName() + " is reconfiguring the properties : " + configuration);
- Properties props = reconfigureProperties(configuration);
- propagate(props, m_propagatedFromInstance);
- m_propagatedFromInstance = props;
+ public void reconfigure(Dictionary configuration) {
+ Map<String, Object> map = new LinkedHashMap<String, Object>();
+ synchronized (this) {
+ info(getInstanceManager().getInstanceName() + " is reconfiguring the properties : " + configuration);
+ Properties props = reconfigureProperties(configuration);
+ propagate(props, m_propagatedFromInstance);
+ m_propagatedFromInstance = props;
- if (getInstanceManager().getPojoObjects() != null) {
- try {
- notifyUpdated(null);
- } catch (Throwable e) {
- error("Cannot call the updated method : " + e.getMessage(), e);
+ if (getInstanceManager().getPojoObjects() != null) {
+ try {
+ notifyUpdated(null);
+ } catch (Throwable e) {
+ error("Cannot call the updated method : " + e.getMessage(), e);
+ }
+ }
+ // Make a snapshot of the current configuration
+ for (Property p : m_configurableProperties) {
+ map.put(p.getName(), p.getValue());
}
}
+ notifyListeners(map);
}
/**
@@ -508,10 +518,13 @@
* @see org.apache.felix.ipojo.PrimitiveHandler#onCreation(Object)
*/
public void onCreation(Object instance) {
+ Map<String, Object> map = new LinkedHashMap<String, Object>();
for (Property prop : m_configurableProperties) {
if (prop.hasMethod()) {
prop.invoke(instance);
}
+ // Fill the snapshot copy while calling callbacks
+ map.put(prop.getName(), prop.getValue());
}
try {
@@ -519,6 +532,7 @@
} catch (Throwable e) {
error("Cannot call the updated method : " + e.getMessage(), e);
}
+ notifyListeners(map);
}
/**
@@ -606,27 +620,35 @@
* the reconfiguration failed.
* @see org.osgi.service.cm.ManagedService#updated(java.util.Dictionary)
*/
- public synchronized void updated(Dictionary conf) throws org.osgi.service.cm.ConfigurationException {
- if (conf == null && !m_configurationAlreadyPushed) {
- return; // First call
- } else if (conf != null) { // Configuration push
- Properties props = reconfigureProperties(conf);
- propagate(props, m_propagatedFromCA);
- m_propagatedFromCA = props;
- m_configurationAlreadyPushed = true;
- } else if (m_configurationAlreadyPushed) { // Configuration deletion
- propagate(null, m_propagatedFromCA);
- m_propagatedFromCA = null;
- m_configurationAlreadyPushed = false;
- }
+ public void updated(Dictionary conf) throws org.osgi.service.cm.ConfigurationException {
+ Map<String, Object> map = new LinkedHashMap<String, Object>();
+ synchronized (this) {
+ if (conf == null && !m_configurationAlreadyPushed) {
+ return; // First call
+ } else if (conf != null) { // Configuration push
+ Properties props = reconfigureProperties(conf);
+ propagate(props, m_propagatedFromCA);
+ m_propagatedFromCA = props;
+ m_configurationAlreadyPushed = true;
+ } else if (m_configurationAlreadyPushed) { // Configuration deletion
+ propagate(null, m_propagatedFromCA);
+ m_propagatedFromCA = null;
+ m_configurationAlreadyPushed = false;
+ }
- if (getInstanceManager().getPojoObjects() != null) {
- try {
- notifyUpdated(null);
- } catch (Throwable e) {
- error("Cannot call the updated method : " + e.getMessage(), e);
+ if (getInstanceManager().getPojoObjects() != null) {
+ try {
+ notifyUpdated(null);
+ } catch (Throwable e) {
+ error("Cannot call the updated method : " + e.getMessage(), e);
+ }
+ }
+ // Make a snapshot of the current configuration
+ for (Property p : m_configurableProperties) {
+ map.put(p.getName(), p.getValue());
}
}
+ notifyListeners(map);
}
/**
@@ -639,5 +661,87 @@
return m_description;
}
+ /**
+ * Add the given listener to the configuration handler's list of listeners.
+ *
+ * @param listener the {@code ConfigurationListener} object to be added
+ * @throws NullPointerException if {@code listener} is {@code null}
+ */
+ public void addListener(ConfigurationListener listener) {
+ if (listener == null) {
+ throw new NullPointerException("null listener");
+ }
+ synchronized (m_listeners) {
+ m_listeners.add(listener);
+ }
+ }
+ /**
+ * Remove the given listener from the configuration handler's list of listeners.
+ *
+ * @param listener the {@code ConfigurationListener} object to be removed
+ * @throws NullPointerException if {@code listener} is {@code null}
+ * @throws NoSuchElementException if {@code listener} wasn't present the in configuration handler's list of listeners
+ */
+ public void removeListener(ConfigurationListener listener) {
+ if (listener == null) {
+ throw new NullPointerException("null listener");
+ }
+ synchronized (m_listeners) {
+ // We definitely cannot rely on listener's equals method...
+ // ...so we need to manually search for the listener, using ==.
+ int i = -1;
+ for(int j = m_listeners.size() -1; j>=0 ; j--) {
+ if (m_listeners.get(j) == listener) {
+ // Found!
+ i = j;
+ break;
+ }
+ }
+ if (i != -1) {
+ m_listeners.remove(i);
+ } else {
+ throw new NoSuchElementException("no such listener");
+ }
+ }
+ }
+
+ /**
+ * Notify all listeners that a reconfiguration has occurred.
+ *
+ * @param map the new configuration of the component instance.
+ */
+ private void notifyListeners(Map<String, Object> map) {
+ // Get a snapshot of the listeners
+ List<ConfigurationListener> tmp;
+ synchronized (m_listeners) {
+ tmp = new ArrayList<ConfigurationListener>(m_listeners);
+ }
+ // Protect the map.
+ map = Collections.unmodifiableMap(map);
+ // Do notify, outside any lock
+ for (ConfigurationListener l : tmp) {
+ try {
+ l.configurationChanged(getInstanceManager(), map);
+ } catch (Throwable e) {
+ // Failure inside a listener: put a warning on the logger, and continue
+ warn(String.format(
+ "[%s] A ConfigurationListener has failed: %s",
+ getInstanceManager().getInstanceName(),
+ e.getMessage())
+ , e);
+ }
+ }
+ }
+
+ @Override
+ public void stateChanged(int state) {
+ if (state == ComponentInstance.DISPOSED) {
+ // Clean up the list of listeners
+ synchronized (m_listeners) {
+ m_listeners.clear();
+ }
+ }
+ super.stateChanged(state);
+ }
}
diff --git a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/configuration/ConfigurationHandlerDescription.java b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/configuration/ConfigurationHandlerDescription.java
index d8bdb4b..7455b0f 100644
--- a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/configuration/ConfigurationHandlerDescription.java
+++ b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/configuration/ConfigurationHandlerDescription.java
@@ -20,7 +20,6 @@
import java.util.List;
-import org.apache.felix.ipojo.Handler;
import org.apache.felix.ipojo.architecture.HandlerDescription;
import org.apache.felix.ipojo.architecture.PropertyDescription;
import org.apache.felix.ipojo.metadata.Attribute;
@@ -46,13 +45,19 @@
private String m_pid;
/**
+ * The configuration handler.
+ */
+ private final ConfigurationHandler m_conf;
+
+ /**
* Creates the description object for the configuration handler description.
* @param handler the configuration handler.
* @param props the list of properties.
* @param pid the managed service pid or <code>null</code> if not set.
*/
- public ConfigurationHandlerDescription(Handler handler, List/*<Property>*/ props, String pid) {
+ public ConfigurationHandlerDescription(ConfigurationHandler handler, List/*<Property>*/ props, String pid) {
super(handler);
+ m_conf = handler;
m_properties = new PropertyDescription[props.size()];
for (int i = 0; i < props.size(); i++) {
m_properties[i] = new PropertyDescription((Property) props.get(i));
@@ -108,4 +113,25 @@
return m_pid;
}
+ /**
+ * Add the given listener to the configuration handler's list of listeners.
+ *
+ * @param listener the {@code ConfigurationListener} object to be added
+ * @throws NullPointerException if {@code listener} is {@code null}
+ */
+ public void addListener(ConfigurationListener listener) {
+ m_conf.addListener(listener);
+ }
+
+ /**
+ * Remove the given listener from the configuration handler's list of listeners.
+ *
+ * @param listener the {@code ConfigurationListener} object to be removed
+ * @throws NullPointerException if {@code listener} is {@code null}
+ * @throws java.util.NoSuchElementException if {@code listener} wasn't present the in configuration handler's list of listeners
+ */
+ public void removeListener(ConfigurationListener listener) {
+ m_conf.removeListener(listener);
+ }
+
}
diff --git a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/configuration/ConfigurationListener.java b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/configuration/ConfigurationListener.java
new file mode 100644
index 0000000..aab13e4
--- /dev/null
+++ b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/configuration/ConfigurationListener.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.ipojo.handlers.configuration;
+
+import org.apache.felix.ipojo.ComponentInstance;
+
+import java.util.Map;
+
+/**
+ * Listener interface for configuration updates of iPOJO component instances.
+ */
+public interface ConfigurationListener {
+
+ /**
+ * Called when the component instance is reconfigured.
+ *
+ * @param instance the concerned instance
+ * @param configuration snapshot of the instance configuration. Unmodifiable.
+ */
+ void configurationChanged(ComponentInstance instance, Map<String, Object> configuration);
+
+}
diff --git a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/dependency/DependencyDescription.java b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/dependency/DependencyDescription.java
index 355ceaf..5a98d3a 100644
--- a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/dependency/DependencyDescription.java
+++ b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/dependency/DependencyDescription.java
@@ -21,6 +21,7 @@
import java.util.Comparator;
import java.util.List;
+import org.apache.felix.ipojo.util.DependencyModelListener;
import org.osgi.framework.Filter;
import org.osgi.framework.ServiceReference;
@@ -32,7 +33,7 @@
/**
* The described dependency.
*/
- private Dependency m_dependency;
+ private final Dependency m_dependency;
/**
* Creates a dependency description.
@@ -145,4 +146,25 @@
public Dependency getDependency() {
return m_dependency;
}
+
+ /**
+ * Add the given listener to the dependency model's list of listeners.
+ *
+ * @param listener the {@code DependencyModelListener} object to be added
+ * @throws NullPointerException if {@code listener} is {@code null}
+ */
+ public void addListener(DependencyModelListener listener) {
+ m_dependency.addListener(listener);
+ }
+
+ /**
+ * Remove the given listener from the dependency model's list of listeners.
+ *
+ * @param listener the {@code DependencyModelListener} object to be removed
+ * @throws NullPointerException if {@code listener} is {@code null}
+ * @throws java.util.NoSuchElementException if {@code listener} wasn't present in the dependency model's list of listeners
+ */
+ public void removeListener(DependencyModelListener listener) {
+ m_dependency.removeListener(listener);
+ }
}
diff --git a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/dependency/DependencyHandler.java b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/dependency/DependencyHandler.java
index 90a2e8f..b362473 100644
--- a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/dependency/DependencyHandler.java
+++ b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/dependency/DependencyHandler.java
@@ -720,4 +720,15 @@
public void reconfigure(Dictionary configuration) {
m_instanceConfigurationSource.reconfigure(configuration);
}
+
+ @Override
+ public void stateChanged(int state) {
+ if (state == ComponentInstance.DISPOSED) {
+ // Cleanup all dependencies
+ for (Dependency dep : m_dependencies) {
+ dep.cleanup();
+ }
+ }
+ super.stateChanged(state);
+ }
}
diff --git a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/providedservice/ProvidedService.java b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/providedservice/ProvidedService.java
index 339ce5c..d9b1461 100644
--- a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/providedservice/ProvidedService.java
+++ b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/providedservice/ProvidedService.java
@@ -21,16 +21,7 @@
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Dictionary;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
+import java.util.*;
import org.apache.felix.ipojo.*;
import org.apache.felix.ipojo.util.Callback;
@@ -145,6 +136,11 @@
private Dictionary m_publishedProperties = new Properties();
/**
+ * The provided service listeners.
+ */
+ private List<ProvidedServiceListener> m_listeners = new ArrayList<ProvidedServiceListener>();
+
+ /**
* Creates a provided service object.
*
* @param handler the the provided service handler.
@@ -419,32 +415,43 @@
}
}
}
+
+ // Notify: ProvidedServiceListeners.serviceRegistered()
+ notifyListeners(+1);
+
}
/**
* Unregisters the service.
*/
- protected synchronized void unregisterService() {
- // Create a copy of the service reference in the case we need
- // to inject it to the post-unregistration callback.
+ protected void unregisterService() {
ServiceReference ref = null;
- if (m_serviceRegistration != null) {
- ref = m_serviceRegistration.getReference();
- m_serviceRegistration.unregister();
- m_serviceRegistration = null;
+ synchronized (this) {
+ // Create a copy of the service reference in the case we need
+ // to inject it to the post-unregistration callback.
+ if (m_serviceRegistration != null) {
+ ref = m_serviceRegistration.getReference();
+ m_serviceRegistration.unregister();
+ m_serviceRegistration = null;
+ }
+
+ m_strategy.onUnpublication();
+
+ // Call the post-unregistration callback in the same thread holding the monitor lock.
+ // This allows to be sure that the callback is called once per unregistration.
+ // But the callback must take care to not create a deadlock
+ if (m_postUnregistration != null && ref != null) {
+ try {
+ m_postUnregistration.call(new Object[] { ref });
+ } catch (Exception e) {
+ m_handler.error("Cannot invoke the post-unregistration callback " + m_postUnregistration.getMethod(), e);
+ }
+ }
}
- m_strategy.onUnpublication();
-
- // Call the post-unregistration callback in the same thread holding the monitor lock.
- // This allows to be sure that the callback is called once per unregistration.
- // But the callback must take care to not create a deadlock
- if (m_postUnregistration != null && ref != null) {
- try {
- m_postUnregistration.call(new Object[] { ref });
- } catch (Exception e) {
- m_handler.error("Cannot invoke the post-unregistration callback " + m_postUnregistration.getMethod(), e);
- }
+ // Notify: ProvidedServiceListeners.serviceUnregistered()
+ if (ref != null) {
+ notifyListeners(-1);
}
}
@@ -495,54 +502,63 @@
* Update the service properties. The new list of properties is sent to
* the service registry.
*/
- public synchronized void update() {
- // Update the service registration
- if (m_serviceRegistration != null) {
- Properties updated = getServiceProperties();
- Dictionary oldProps = (Dictionary) ((Properties) m_publishedProperties).clone();
- Dictionary newProps = (Dictionary) (updated.clone());
+ public void update() {
+ boolean doCallListener = false;
+ synchronized (this) {
+ // Update the service registration
+ if (m_serviceRegistration != null) {
+ Properties updated = getServiceProperties();
+ Dictionary oldProps = (Dictionary) ((Properties) m_publishedProperties).clone();
+ Dictionary newProps = (Dictionary) (updated.clone());
- // Remove keys that must not be compared
- newProps.remove(Factory.INSTANCE_NAME_PROPERTY);
- oldProps.remove(Factory.INSTANCE_NAME_PROPERTY);
- newProps.remove(Constants.SERVICE_ID);
- oldProps.remove(Constants.SERVICE_ID);
- newProps.remove(Constants.SERVICE_PID);
- oldProps.remove(Constants.SERVICE_PID);
- newProps.remove("factory.name");
- oldProps.remove("factory.name");
- newProps.remove(ConfigurationAdmin.SERVICE_FACTORYPID);
- oldProps.remove(ConfigurationAdmin.SERVICE_FACTORYPID);
+ // Remove keys that must not be compared
+ newProps.remove(Factory.INSTANCE_NAME_PROPERTY);
+ oldProps.remove(Factory.INSTANCE_NAME_PROPERTY);
+ newProps.remove(Constants.SERVICE_ID);
+ oldProps.remove(Constants.SERVICE_ID);
+ newProps.remove(Constants.SERVICE_PID);
+ oldProps.remove(Constants.SERVICE_PID);
+ newProps.remove("factory.name");
+ oldProps.remove("factory.name");
+ newProps.remove(ConfigurationAdmin.SERVICE_FACTORYPID);
+ oldProps.remove(ConfigurationAdmin.SERVICE_FACTORYPID);
- // Trigger the update only if the properties have changed.
+ // Trigger the update only if the properties have changed.
- // First check, are the size equals
- if (oldProps.size() != newProps.size()) {
- if (SecurityHelper.canUpdateService(m_serviceRegistration)) {
- m_handler.info("Updating Registration : " + oldProps.size() + " / " + newProps.size());
- m_publishedProperties = updated;
- m_serviceRegistration.setProperties(updated);
- }
- } else {
- // Check changes
- Enumeration keys = oldProps.keys();
- boolean hasChanged = false;
- while (! hasChanged && keys.hasMoreElements()) {
- String k = (String) keys.nextElement();
- Object val = oldProps.get(k);
- if (! val.equals(updated.get(k))) {
- hasChanged = true;
+ // First check, are the size equals
+ if (oldProps.size() != newProps.size()) {
+ if (SecurityHelper.canUpdateService(m_serviceRegistration)) {
+ m_handler.info("Updating Registration : " + oldProps.size() + " / " + newProps.size());
+ m_publishedProperties = updated;
+ m_serviceRegistration.setProperties(updated);
+ doCallListener = true;
+ }
+ } else {
+ // Check changes
+ Enumeration keys = oldProps.keys();
+ boolean hasChanged = false;
+ while (! hasChanged && keys.hasMoreElements()) {
+ String k = (String) keys.nextElement();
+ Object val = oldProps.get(k);
+ if (! val.equals(updated.get(k))) {
+ hasChanged = true;
+ }
+ }
+ if (hasChanged && SecurityHelper.canUpdateService(m_serviceRegistration)) {
+ m_handler.info("Updating Registration : " + updated);
+ m_publishedProperties = updated;
+ m_serviceRegistration.setProperties(updated);
+ doCallListener = true;
}
}
- if (hasChanged && SecurityHelper.canUpdateService(m_serviceRegistration)) {
- m_handler.info("Updating Registration : " + updated);
- m_publishedProperties = updated;
- m_serviceRegistration.setProperties(updated);
- }
+ } else {
+ // Need to be updated later.
+ m_wasUpdated = true;
}
- } else {
- // Need to be updated later.
- m_wasUpdated = true;
+ }
+ if (doCallListener) {
+ // Notify: ProvidedServiceListeners.serviceUpdated()
+ notifyListeners(0);
}
}
@@ -726,6 +742,93 @@
}
/**
+ * Add the given listener to the provided service handler's list of listeners.
+ *
+ * @param listener the {@code ProvidedServiceListener} object to be added
+ * @throws NullPointerException if {@code listener} is {@code null}
+ */
+ public void addListener(ProvidedServiceListener listener) {
+ if (listener == null) {
+ throw new NullPointerException("null listener");
+ }
+ synchronized (m_listeners) {
+ m_listeners.add(listener);
+ }
+ }
+
+ /**
+ * Remove the given listener from the provided service handler's list of listeners.
+ *
+ * @param listener the {@code ProvidedServiceListener} object to be removed
+ * @throws NullPointerException if {@code listener} is {@code null}
+ * @throws NoSuchElementException if {@code listener} wasn't present the in provided service handler's list of listeners
+ */
+ public void removeListener(ProvidedServiceListener listener) {
+ if (listener == null) {
+ throw new NullPointerException("null listener");
+ }
+ synchronized (m_listeners) {
+ // We definitely cannot rely on listener's equals method...
+ // ...so we need to manually search for the listener, using ==.
+ int i = -1;
+ for(int j = m_listeners.size() -1; j>=0 ; j--) {
+ if (m_listeners.get(j) == listener) {
+ // Found!
+ i = j;
+ break;
+ }
+ }
+ if (i != -1) {
+ m_listeners.remove(i);
+ } else {
+ throw new NoSuchElementException("no such listener");
+ }
+ }
+ }
+
+ /**
+ * Notify all listeners that a change has occurred in this provided service.
+ *
+ * @param direction the "direction" of the change (+1:registration, 0:update, -1:unregistration)
+ */
+ private void notifyListeners(int direction) {
+ // Get a snapshot of the listeners
+ List<ProvidedServiceListener> tmp;
+ synchronized (m_listeners) {
+ tmp = new ArrayList<ProvidedServiceListener>(m_listeners);
+ }
+ // Do notify, outside the m_listeners lock
+ for (ProvidedServiceListener l : tmp) {
+ try {
+ if (direction > 0) {
+ l.serviceRegistered(m_handler.getInstanceManager(), this);
+ } else if (direction < 0) {
+ l.serviceUnregistered(m_handler.getInstanceManager(), this);
+ } else {
+ l.serviceModified(m_handler.getInstanceManager(), this);
+ }
+ } catch (Throwable e) {
+ // Put a warning on the logger, and continue
+ m_handler.warn(
+ String.format(
+ "[%s] A ProvidedServiceListener has failed: %s",
+ m_handler.getInstanceManager().getInstanceName(),
+ e.getMessage())
+ , e);
+ }
+ }
+ }
+
+ /**
+ * Removes all the listeners from this provided service before it gets disposed.
+ */
+ public void cleanup() {
+ synchronized (m_listeners) {
+ m_listeners.clear();
+ }
+ }
+
+ /**
* Service Controller.
*/
class ServiceController {
diff --git a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/providedservice/ProvidedServiceDescription.java b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/providedservice/ProvidedServiceDescription.java
index 3709edf..6584273 100644
--- a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/providedservice/ProvidedServiceDescription.java
+++ b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/providedservice/ProvidedServiceDescription.java
@@ -163,4 +163,25 @@
return m_ps.getCreationStrategy();
}
+ /**
+ * Add the given listener to the provided service handler's list of listeners.
+ *
+ * @param listener the {@code ProvidedServiceListener} object to be added
+ * @throws NullPointerException if {@code listener} is {@code null}
+ */
+ public void addListener(ProvidedServiceListener listener) {
+ m_ps.addListener(listener);
+ }
+
+ /**
+ * Remove the given listener from the provided service handler's list of listeners.
+ *
+ * @param listener the {@code ProvidedServiceListener} object to be removed
+ * @throws NullPointerException if {@code listener} is {@code null}
+ * @throws java.util.NoSuchElementException if {@code listener} wasn't present the in provided service handler's list of listeners
+ */
+ public void removeListener(ProvidedServiceListener listener) {
+ m_ps.removeListener(listener);
+ }
+
}
diff --git a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/providedservice/ProvidedServiceHandler.java b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/providedservice/ProvidedServiceHandler.java
index a0131d8..e92a069 100644
--- a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/providedservice/ProvidedServiceHandler.java
+++ b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/providedservice/ProvidedServiceHandler.java
@@ -484,6 +484,13 @@
ps.registerService();
}
}
+
+ // If the new state is DISPOSED => cleanup all the provided services listeners
+ if (state == InstanceManager.DISPOSED) {
+ for (ProvidedService ps : m_providedServices) {
+ ps.cleanup();
+ }
+ }
}
/**
diff --git a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/providedservice/ProvidedServiceListener.java b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/providedservice/ProvidedServiceListener.java
new file mode 100644
index 0000000..c32f1ba
--- /dev/null
+++ b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/providedservice/ProvidedServiceListener.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.ipojo.handlers.providedservice;
+
+import org.apache.felix.ipojo.ComponentInstance;
+
+/**
+ * Listener interface for services provided by iPOJO component instances.
+ */
+public interface ProvidedServiceListener {
+
+ /**
+ * Called when the service has been registered.
+ *
+ * @param instance the concerned component instance
+ * @param providedService the registered service
+ */
+ void serviceRegistered(ComponentInstance instance, ProvidedService providedService);
+
+ /**
+ * Called when the registered service has been updated.
+ *
+ * @param instance the concerned component instance
+ * @param providedService the updated service
+ */
+ void serviceModified(ComponentInstance instance, ProvidedService providedService);
+
+ /**
+ * Called when the service is unregistered.
+ *
+ * @param instance the concerned component instance
+ * @param providedService the unregistered service
+ */
+ void serviceUnregistered(ComponentInstance instance, ProvidedService providedService);
+
+}
diff --git a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/util/DependencyModel.java b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/util/DependencyModel.java
index ce3e49f..2882464 100644
--- a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/util/DependencyModel.java
+++ b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/util/DependencyModel.java
@@ -140,6 +140,11 @@
private ReentrantReadWriteLock m_lock = new ReentrantReadWriteLock();
/**
+ * The listeners of the dependency model.
+ */
+ private final List<DependencyModelListener> m_listeners = new ArrayList<DependencyModelListener>();
+
+ /**
* Creates a DependencyModel.
* If the dependency has no comparator and follows the
* {@link DependencyModel#DYNAMIC_PRIORITY_BINDING_POLICY} policy
@@ -503,6 +508,8 @@
*/
private void invalidate() {
m_listener.invalidate(this);
+ // Notify dependency invalidation to listeners
+ notifyListeners(DependencyEventType.INVALIDATE, null, null);
}
/**
@@ -512,6 +519,8 @@
*/
private void validate() {
m_listener.validate(this);
+ // Notify dependency validation to listeners
+ notifyListeners(DependencyEventType.VALIDATE, null, null);
}
/**
@@ -709,6 +718,9 @@
onDependencyReconfiguration(
dep.toArray(new ServiceReference[dep.size()]),
arr.toArray(new ServiceReference[arr.size()]));
+
+ // Notify dependency reconfiguration to listeners
+ notifyListeners(DependencyEventType.RECONFIGURED, null, null);
}
public boolean isAggregate() {
@@ -765,13 +777,19 @@
releaseWriteLockIfHeld();
}
+ // TODO shouldn't we call onDependencyReconfiguration here????
+
// Now call callbacks, the lock is not held anymore
// Only one of the list is not empty..
for (ServiceReference ref : arrivals) {
onServiceArrival(ref);
+ // Notify service binding to listeners
+ notifyListeners(DependencyEventType.BINDING, ref, getService(ref, false));
}
for (ServiceReference ref : departures) {
onServiceDeparture(ref);
+ // Notify service unbinding to listeners
+ notifyListeners(DependencyEventType.UNBINDING, ref, getService(ref, false));
}
@@ -944,18 +962,6 @@
return getSpecification().getName();
}
- private void breakDependency() {
- // Static dependency broken.
- m_state = BROKEN;
-
- // We are going to call callbacks, releasing the lock.
- releaseWriteLockIfHeld();
- invalidate(); // This will invalidate the instance.
- m_instance.stop(); // Stop the instance
- unfreeze();
- m_instance.start();
- }
-
/**
* Callbacks call by the ServiceReferenceManager when the selected service set has changed.
* @param set the change set.
@@ -968,7 +974,20 @@
for (ServiceReference ref : set.departures) {
// Check if any of the service that have left was in used.
if (m_boundServices.contains(ref)) {
- breakDependency();
+ // Static dependency broken.
+ m_state = BROKEN;
+
+ // We are going to call callbacks, releasing the lock.
+ releaseWriteLockIfHeld();
+
+ // Notify listeners
+ notifyListeners(DependencyEventType.UNBINDING, ref, getService(ref, false));
+ notifyListeners(DependencyEventType.DEPARTURE, ref, null);
+
+ invalidate(); // This will invalidate the instance.
+ m_instance.stop(); // Stop the instance
+ unfreeze();
+ m_instance.start();
return;
}
}
@@ -1045,13 +1064,18 @@
releaseWriteLockIfHeld();
for (ServiceReference ref : departures) {
onServiceDeparture(ref);
+ // Notify service unbinding to listeners
+ notifyListeners(DependencyEventType.UNBINDING, ref, getService(ref, false));
}
for (ServiceReference ref : arrivals) {
onServiceArrival(ref);
+ // Notify service binding to listeners
+ notifyListeners(DependencyEventType.BINDING, ref, getService(ref, false));
}
// Do we have a modified service ?
if (set.modified != null && m_boundServices.contains(set.modified)) {
onServiceModification(set.modified);
+ // TODO call boundServiceModified on listeners???
}
@@ -1070,4 +1094,123 @@
public Tracker getTracker() {
return m_tracker;
}
+
+ public enum DependencyEventType {
+ VALIDATE,
+ INVALIDATE,
+ ARRIVAL,
+ MODIFIED,
+ DEPARTURE,
+ BINDING,
+ UNBINDING,
+ RECONFIGURED
+ }
+
+ /**
+ * Add the given listener to the dependency model's list of listeners.
+ *
+ * @param listener the {@code DependencyModelListener} object to be added
+ * @throws NullPointerException if {@code listener} is {@code null}
+ */
+ public void addListener(DependencyModelListener listener) {
+ if (listener == null) {
+ throw new NullPointerException("null listener");
+ }
+ synchronized (m_listeners) {
+ m_listeners.add(listener);
+ }
+ }
+
+ /**
+ * Remove the given listener from the dependency model's list of listeners.
+ *
+ * @param listener the {@code DependencyModelListener} object to be removed
+ * @throws NullPointerException if {@code listener} is {@code null}
+ * @throws NoSuchElementException if {@code listener} wasn't present in the dependency model's list of listeners
+ */
+ public void removeListener(DependencyModelListener listener) {
+ if (listener == null) {
+ throw new NullPointerException("null listener");
+ }
+ synchronized (m_listeners) {
+ // We definitely cannot rely on listener's equals method...
+ // ...so we need to manually search for the listener, using ==.
+ int i = -1;
+ for(int j = m_listeners.size() -1; j>=0 ; j--) {
+ if (m_listeners.get(j) == listener) {
+ // Found!
+ i = j;
+ break;
+ }
+ }
+ if (i != -1) {
+ m_listeners.remove(i);
+ } else {
+ throw new NoSuchElementException("no such listener");
+ }
+ }
+ }
+
+ /**
+ * Notify all listeners that a change has occurred in this dependency model.
+ *
+ * @param type the type of event
+ * @param service the reference of the concerned service (may be null)
+ * @param object the concerned service object (may be null)
+ */
+ public void notifyListeners(DependencyEventType type, ServiceReference<?> service, Object object) {
+ // Get a snapshot of the listeners
+ List<DependencyModelListener> tmp;
+ synchronized (m_listeners) {
+ tmp = new ArrayList<DependencyModelListener>(m_listeners);
+ }
+ // Do notify, outside the m_listeners lock
+ for (DependencyModelListener l : tmp) {
+ try {
+ switch (type) {
+ case VALIDATE:
+ l.validate(this);
+ break;
+ case INVALIDATE:
+ l.invalidate(this);
+ break;
+ case ARRIVAL:
+ l.matchingServiceArrived(this, service);
+ break;
+ case MODIFIED:
+ l.matchingServiceModified(this, service);
+ break;
+ case DEPARTURE:
+ l.matchingServiceDeparted(this, service);
+ break;
+ case BINDING:
+ l.serviceBound(this, service, object);
+ break;
+ case UNBINDING:
+ l.serviceUnbound(this, service, object);
+ break;
+ case RECONFIGURED:
+ l.reconfigured(this);
+ break;
+ }
+ } catch (Throwable e) {
+ // Put a warning on the logger, and continue
+ getComponentInstance().getFactory().getLogger().log(Log.WARNING,
+ String.format(
+ "[%s] A DependencyModelListener has failed: %s",
+ getComponentInstance().getInstanceName(),
+ e.getMessage())
+ , e);
+ }
+ }
+ }
+
+ /**
+ * Removes all the listeners from this dependency before it gets disposed.
+ */
+ public void cleanup() {
+ synchronized (m_listeners) {
+ m_listeners.clear();
+ }
+ }
}
diff --git a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/util/DependencyModelListener.java b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/util/DependencyModelListener.java
new file mode 100644
index 0000000..787900f
--- /dev/null
+++ b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/util/DependencyModelListener.java
@@ -0,0 +1,40 @@
+/*
+ * 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.ipojo.util;
+
+import org.osgi.framework.ServiceReference;
+
+/**
+ * Listener interface for service dependencies of iPOJO component instances.
+ */
+public interface DependencyModelListener extends DependencyStateListener {
+
+ void matchingServiceArrived(DependencyModel dependency, ServiceReference<?> service);
+
+ void matchingServiceModified(DependencyModel dependency, ServiceReference<?> service);
+
+ void matchingServiceDeparted(DependencyModel dependency, ServiceReference<?> service);
+
+ void serviceBound(DependencyModel dependency, ServiceReference<?> service, Object object);
+
+ void serviceUnbound(DependencyModel dependency, ServiceReference<?> service, Object object);
+
+ void reconfigured(DependencyModel dependency);
+
+}