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);
+
+}