Added final patch for service registry hooks. (FELIX-905,FELIX-906)


git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@782664 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/framework/src/main/java/org/apache/felix/framework/Felix.java b/framework/src/main/java/org/apache/felix/framework/Felix.java
index b2afc02..3ac2488 100644
--- a/framework/src/main/java/org/apache/felix/framework/Felix.java
+++ b/framework/src/main/java/org/apache/felix/framework/Felix.java
@@ -2398,9 +2398,6 @@
         return null;
     }
 
-    // Private member for method below.
-    private Comparator m_comparator = null;
-
     /**
      * Implementation for BundleContext.getBundles(). Retrieves
      * all installed bundles.
@@ -2449,13 +2446,20 @@
 
         // Invoke the ListenerHook.added() on all hooks.
         List listenerHooks = m_registry.getListenerHooks();
-        Collection c = Collections.singleton(new ListenerHookInfoImpl(bundle.getBundleContext(), f));
+        final Collection c = Collections.singleton(
+            new ListenerHookInfoImpl(bundle.getBundleContext(), f));
         for (int i = 0; i < listenerHooks.size(); i++)
         {
-            ((ListenerHook) listenerHooks.get(i)).added(c);
+            ServiceRegistry.invokeHook(listenerHooks.get(i), this, new InvokeHookCallback() 
+            {
+                public void invokeHook(Object hook) 
+                {
+                    ((ListenerHook) hook).added(c);
+                }                
+            });
         }
     }
-
+        
     /**
      * Implementation for BundleContext.removeServiceListener().
      * Removes service listeners from the listener list.
@@ -2570,8 +2574,15 @@
         // to invoke the callback with all existing service listeners.
         if (m_registry.isHook(classNames, ListenerHook.class, svcObj))
         {
-            ListenerHook lHook = (ListenerHook) svcObj;
-            lHook.added(m_dispatcher.wrapAllServiceListeners());
+            Object hookRef = ServiceRegistry.getHookRef(svcObj, reg);
+            ServiceRegistry.invokeHook(hookRef, this, new InvokeHookCallback() 
+            {
+                public void invokeHook(Object hook) 
+                {
+                    ((ListenerHook) hook).
+                        added(m_dispatcher.wrapAllServiceListeners());                    
+                }                
+            });
         }
 
         // TODO: CONCURRENCY - Reconsider firing event here, outside of the
@@ -2595,7 +2606,8 @@
      * @throws InvalidSyntaxException
      */
     ServiceReference[] getServiceReferences(
-        BundleImpl bundle, String className, String expr, boolean checkAssignable)
+        final BundleImpl bundle, final String className,
+        final String expr, final boolean checkAssignable)
         throws InvalidSyntaxException
     {
         // Define filter if expression is not null.
@@ -2606,7 +2618,7 @@
         }
 
         // Ask the service registry for all matching service references.
-        List refList = m_registry.getServiceReferences(className, filter);
+        final List refList = m_registry.getServiceReferences(className, filter);
 
         // Filter on assignable references
         if (checkAssignable)
@@ -2625,22 +2637,27 @@
             }
         }
 
+        // activate findhooks
+        List findHooks = m_registry.getFindHooks();
+        for (int i = 0; i < findHooks.size(); i++)
+        {
+            ServiceRegistry.invokeHook(findHooks.get(i), this, new InvokeHookCallback() 
+            {
+                public void invokeHook(Object hook) 
+                {
+                    ((FindHook) hook).find(bundle.getBundleContext(),
+                        className,
+                        expr,
+                        !checkAssignable,
+                        new ShrinkableCollection(refList));
+                }                
+            });
+        }
+
         if (refList.size() > 0)
         {
-            // activate findhooks
-            List findHooks = m_registry.getFindHooks();
-            for (int i = 0; i < findHooks.size(); i++)
-            {
-                ((FindHook) findHooks.get(i)).find(
-                    bundle.getBundleContext(),
-                    className,
-                    expr,
-                    !checkAssignable,
-                    new ShrinkableCollection(refList));
-            }
-
             return (ServiceReference[]) refList.toArray(new ServiceReference[refList.size()]);
-        }
+        } 
 
         return null;
     }
@@ -3432,7 +3449,7 @@
     **/
     private void fireServiceEvent(ServiceEvent event)
     {
-        m_dispatcher.fireServiceEvent(event);
+        m_dispatcher.fireServiceEvent(event, this);
     }
 
     //
diff --git a/framework/src/main/java/org/apache/felix/framework/InvokeHookCallback.java b/framework/src/main/java/org/apache/felix/framework/InvokeHookCallback.java
new file mode 100644
index 0000000..62dde99
--- /dev/null
+++ b/framework/src/main/java/org/apache/felix/framework/InvokeHookCallback.java
@@ -0,0 +1,24 @@
+/*
+ * 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.framework;
+
+public interface InvokeHookCallback 
+{
+    void invokeHook(Object hook);
+}
\ No newline at end of file
diff --git a/framework/src/main/java/org/apache/felix/framework/ServiceRegistry.java b/framework/src/main/java/org/apache/felix/framework/ServiceRegistry.java
index 90fd2e5..fc35627 100644
--- a/framework/src/main/java/org/apache/felix/framework/ServiceRegistry.java
+++ b/framework/src/main/java/org/apache/felix/framework/ServiceRegistry.java
@@ -23,6 +23,7 @@
 import org.apache.felix.framework.util.FelixConstants;
 import org.osgi.framework.*;
 import org.osgi.framework.hooks.service.*;
+import org.osgi.framework.launch.Framework;
 
 public class ServiceRegistry
 {
@@ -73,12 +74,13 @@
 
         synchronized (this)
         {
-            // Keep track of registered hooks.
-            addHooks(classNames, svcObj);
-
             // Create the service registration.
             reg = new ServiceRegistrationImpl(
                 this, bundle, classNames, new Long(m_currentServiceId++), svcObj, dict);
+                        
+            // Keep track of registered hooks.
+            addHooks(classNames, svcObj, reg);
+
             // Get the bundles current registered services.
             ServiceRegistration[] regs = (ServiceRegistration[]) m_serviceRegsMap.get(bundle);
             m_serviceRegsMap.put(bundle, addServiceRegistration(regs, reg));
@@ -90,7 +92,7 @@
     public void unregisterService(Bundle bundle, ServiceRegistration reg)
     {
         // If this is a hook, it should be removed.
-        removeHook(((ServiceRegistrationImpl) reg).getService());
+        removeHook(reg);
 
         synchronized (this)
         {
@@ -731,13 +733,13 @@
         }
     }
 
-    private void addHooks(String[] classNames, Object svcObj)
+    private void addHooks(String[] classNames, Object svcObj, ServiceRegistration reg)
     {
         if (isHook(classNames, EventHook.class, svcObj))
         {
             synchronized (m_eventHookLock)
             {
-                m_eventHooks = addToArray(m_eventHooks, svcObj);
+                m_eventHooks = addHookToArray(m_eventHooks, svcObj, reg);
             }
         }
 
@@ -745,7 +747,7 @@
         {
             synchronized (m_findHookLock)
             {
-                m_findHooks = addToArray(m_findHooks, svcObj);
+                m_findHooks = addHookToArray(m_findHooks, svcObj, reg);
             }
         }
 
@@ -753,21 +755,28 @@
         {
             synchronized (m_listenerHookLock)
             {
-                m_listenerHooks = addToArray(m_listenerHooks, svcObj);
+                m_listenerHooks = addHookToArray(m_listenerHooks, svcObj, reg);
             }
         }
     }
 
-    private static Object[] addToArray(Object[] src, Object svcObj)
+    private static Object[] addHookToArray(Object[] src, Object svcObj, ServiceRegistration reg)
     {
+        Object hookRef = getHookRef(svcObj, reg);
+        
         Object[] dst = new Object[src.length + 1];
         System.arraycopy(src, 0, dst, 0, src.length);
-        dst[src.length] = svcObj;
+        dst[src.length] = hookRef;
         return dst;
     }
 
     boolean isHook(String[] classNames, Class hookClass, Object svcObj)
     {
+        if (svcObj instanceof ServiceFactory) 
+        {
+            return Arrays.asList(classNames).contains(hookClass.getName());
+        }
+        
         if (hookClass.isAssignableFrom(svcObj.getClass()))
         {
             String hookName = hookClass.getName();
@@ -782,41 +791,64 @@
         return false;
     }
 
-    private void removeHook(Object svcObj)
+    private void removeHook(ServiceRegistration reg) 
     {
-        if (svcObj instanceof EventHook)
+        Object svcObj = ((ServiceRegistrationImpl) reg).getService();
+        String [] classNames = (String[]) reg.getReference().getProperty(Constants.OBJECTCLASS);
+        
+        if (isHook(classNames, EventHook.class, svcObj)) 
         {
-            synchronized (m_eventHookLock)
+            synchronized (m_eventHookLock) 
             {
-                m_eventHooks = removeFromArray(m_eventHooks, svcObj);
+                m_eventHooks = removeFromHookArray(m_eventHooks, svcObj, reg);
             }
         }
-
-        if (svcObj instanceof FindHook)
+        
+        if (isHook(classNames, FindHook.class, svcObj)) 
         {
             synchronized (m_findHookLock)
             {
-                m_findHooks = removeFromArray(m_findHooks, svcObj);
+                m_findHooks = removeFromHookArray(m_findHooks, svcObj, reg);
             }
         }
 
-        if (svcObj instanceof ListenerHook)
+        if (isHook(classNames, ListenerHook.class, svcObj))
         {
             synchronized (m_listenerHookLock)
             {
-                m_listenerHooks = removeFromArray(m_listenerHooks, svcObj);
+                m_listenerHooks = removeFromHookArray(m_listenerHooks, svcObj, reg);
             }
         }
     }
 
-    private static Object[] removeFromArray(Object[] src, Object svcObj)
+    private static Object[] removeFromHookArray(Object[] src, Object svcObj, ServiceRegistration reg)
     {
         Object[] dst = src;
 
         int idx = -1;
         for (int i = 0; i < src.length; i++)
         {
-            if (src[i].equals(svcObj))
+            boolean found = false;
+            if (svcObj instanceof ServiceFactory)
+            {
+                if (src[i] instanceof Object[]) 
+                {
+                    Object [] arrElem = (Object []) src[i];
+                    if (arrElem[0].equals(svcObj) && arrElem[1].equals(reg)) 
+                    {
+                        found = true;
+                    }
+                }
+            }
+            else
+            {
+                if (src[i].equals(svcObj))
+                {
+                    found = true;
+                }                
+            }
+            
+            if (found) 
             {
                 idx = i;
                 break;
@@ -868,6 +900,76 @@
         }
     }
 
+    /**
+     * Returns a hook reference object that can be passed to the 
+     * {@link #invokeHook(Object, Framework, InvokeHookCallback)} method.
+     * @param svcObj The Service Object. Either the service itself, or a <tt>ServiceFactory</tt>
+     *        object.
+     * @param reg The associated <tt>ServiceRegistration</tt>.
+     * @return In the case of a <tt>ServiceFactory</tt> an Object[2] with both the
+     *         factory and the <tt>ServiceRegistration</tt> is returned. In all other
+     *         cases the svcObj is returned as passed in. This is the behavior expected
+     *         by the {@link #invokeHook(Object, Framework, InvokeHookCallback)} method.
+     */
+    static Object getHookRef(Object svcObj, ServiceRegistration reg) 
+    {
+        if (svcObj instanceof ServiceFactory) 
+        {
+            return new Object[] {svcObj, reg};
+        }
+        else
+        {
+            return svcObj;
+        }
+    }
+    
+    /**
+     * Invokes a Service Registry Hook
+     * @param hookRef The hook to be invoked, this is an element on the list returned by the 
+     *        {@link #getEventHooks()}, {@link #getFindHooks()} or {@link #getListenerHooks()}
+     *        methods. It could either contain the hook object itself or an Object[2] when a
+     *        ServiceFactory is used. In that case the first element is the hook ServiceFactory
+     *        and the second element is its ServiceRegistration.
+     * @param framework The framework that is invoking the hook, typically the Felix object.
+     * @param callback This is a callback object that is invoked with the actual hook object to 
+     *        be used, either the plain hook, or one obtained from the ServiceFactory.
+     */
+    public static void invokeHook(
+        Object hookRef, Framework framework, InvokeHookCallback callback)
+    {
+        Object hook;
+        boolean serviceFactory;
+        if (hookRef instanceof Object[]) 
+        {
+            serviceFactory = true;
+            Object[] array = (Object[]) hookRef; 
+            ServiceFactory hookFactory = (ServiceFactory) array[0];
+            ServiceRegistration sr = (ServiceRegistration) array[1];
+            hook = hookFactory.getService(framework, sr);
+        }
+        else
+        {
+            serviceFactory = false; 
+            hook = hookRef;
+        }
+        
+        try 
+        {
+            callback.invokeHook(hook);
+            // TODO: SERVICE HOOKS - Check that "services in use" is handled
+        }
+        finally
+        {
+            if (serviceFactory) 
+            {
+                Object [] array = (Object []) hookRef; 
+                ServiceFactory hookFactory = (ServiceFactory) array[0];
+                ServiceRegistration sr = (ServiceRegistration) array[1];
+                hookFactory.ungetService(framework, sr, hook);
+            }
+        }        
+    }    
+    
     private static class UsageCount
     {
         public int m_count = 0;
diff --git a/framework/src/main/java/org/apache/felix/framework/util/EventDispatcher.java b/framework/src/main/java/org/apache/felix/framework/util/EventDispatcher.java
index 96b7e97..38cac23 100644
--- a/framework/src/main/java/org/apache/felix/framework/util/EventDispatcher.java
+++ b/framework/src/main/java/org/apache/felix/framework/util/EventDispatcher.java
@@ -28,6 +28,7 @@
 import java.util.List;
 import java.util.NoSuchElementException;
 
+import org.apache.felix.framework.InvokeHookCallback;
 import org.apache.felix.framework.Logger;
 import org.apache.felix.framework.ServiceRegistry;
 import org.osgi.framework.AllServiceListener;
@@ -44,8 +45,9 @@
 import org.osgi.framework.ServicePermission;
 import org.osgi.framework.ServiceReference;
 import org.osgi.framework.SynchronousBundleListener;
-import org.osgi.framework.hooks.service.ListenerHook;
 import org.osgi.framework.hooks.service.EventHook;
+import org.osgi.framework.hooks.service.ListenerHook;
+import org.osgi.framework.launch.Framework;
 
 public class EventDispatcher
 {
@@ -69,7 +71,7 @@
 
     // A single thread is used to deliver events for all dispatchers.
     private static Thread m_thread = null;
-    private static String m_threadLock = new String("thread lock");
+    private final static String m_threadLock = new String("thread lock");
     private static int m_references = 0;
     private static volatile boolean m_stopping = false;
 
@@ -606,7 +608,7 @@
         }
     }
 
-    public void fireServiceEvent(ServiceEvent event)
+    public void fireServiceEvent(final ServiceEvent event, Framework felix)
     {
         // Take a snapshot of the listener array.
         Object[] listeners = null;
@@ -620,11 +622,20 @@
             List eventHooks = m_serviceRegistry.getEventHooks();
             if ((eventHooks != null) && (eventHooks.size() > 0))
             {
-                ListenerBundleContextCollectionWrapper wrapper =
+                final ListenerBundleContextCollectionWrapper wrapper =
                     new ListenerBundleContextCollectionWrapper(listeners);
                 for (int i = 0; i < eventHooks.size(); i++)
                 {
-                    ((EventHook) eventHooks.get(i)).event(event, wrapper);
+                    if (felix != null) 
+                    {
+                        ServiceRegistry.invokeHook(eventHooks.get(i), felix, new InvokeHookCallback() 
+                        {
+                            public void invokeHook(Object hook) 
+                            {
+                                ((EventHook) hook).event(event, wrapper);                            
+                            }                        
+                        });
+                    }
                 }
 
                 listeners = wrapper.getListeners();
diff --git a/framework/src/test/java/org/apache/felix/framework/ServiceRegistryTest.java b/framework/src/test/java/org/apache/felix/framework/ServiceRegistryTest.java
new file mode 100644
index 0000000..d0c8845
--- /dev/null
+++ b/framework/src/test/java/org/apache/felix/framework/ServiceRegistryTest.java
@@ -0,0 +1,389 @@
+/*
+ * 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.framework;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Hashtable;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import org.easymock.MockControl;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceFactory;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.framework.hooks.service.EventHook;
+import org.osgi.framework.hooks.service.FindHook;
+import org.osgi.framework.hooks.service.ListenerHook;
+import org.osgi.framework.launch.Framework;
+
+public class ServiceRegistryTest extends TestCase 
+{
+    public void testRegisterEventHookService() 
+    {
+        MockControl control = MockControl.createNiceControl(Bundle.class);
+        Bundle b = (Bundle) control.getMock();
+        control.replay();
+        
+        ServiceRegistry sr = new ServiceRegistry(new Logger());
+        EventHook hook = new EventHook() 
+        {
+            public void event(ServiceEvent event, Collection contexts) 
+            {
+            }            
+        };
+        
+        assertEquals("Precondition failed", 0, sr.getEventHooks().size());
+        assertEquals("Precondition failed", 0, sr.getFindHooks().size());
+        assertEquals("Precondition failed", 0, sr.getListenerHooks().size());
+        ServiceRegistration reg = sr.registerService(b, new String [] {EventHook.class.getName()}, hook, new Hashtable());
+        assertEquals(1, sr.getEventHooks().size());
+        assertSame(hook, sr.getEventHooks().iterator().next());
+        assertEquals("Postcondition failed", 0, sr.getFindHooks().size());
+        assertEquals("Postcondition failed", 0, sr.getListenerHooks().size());
+        
+        sr.unregisterService(b, reg);
+        assertEquals("Should be no hooks left after unregistration", 0, sr.getEventHooks().size());
+        assertEquals("Should be no hooks left after unregistration", 0, sr.getFindHooks().size());
+        assertEquals("Should be no hooks left after unregistration", 0, sr.getListenerHooks().size());
+    }
+
+    public void testRegisterEventHookServiceFactory() 
+    {
+        MockControl control = MockControl.createNiceControl(Bundle.class);
+        Bundle b = (Bundle) control.getMock();
+        control.replay();
+        
+        ServiceRegistry sr = new ServiceRegistry(new Logger());
+        MockControl sfControl = MockControl.createNiceControl(ServiceFactory.class);
+        sfControl.replay();
+        ServiceFactory sf = (ServiceFactory) sfControl.getMock();
+        
+        assertEquals("Precondition failed", 0, sr.getEventHooks().size());
+        assertEquals("Precondition failed", 0, sr.getFindHooks().size());
+        assertEquals("Precondition failed", 0, sr.getListenerHooks().size());
+        ServiceRegistration reg = sr.registerService(b, new String [] {EventHook.class.getName()}, sf, new Hashtable());
+        assertEquals(1, sr.getEventHooks().size());
+        Object [] arr = (Object[]) sr.getEventHooks().iterator().next();
+        assertEquals(2, arr.length);
+        assertSame(sf, arr[0]);
+        assertTrue(arr[1] instanceof ServiceRegistration);
+        assertEquals("Postcondition failed", 0, sr.getFindHooks().size());
+        assertEquals("Postcondition failed", 0, sr.getListenerHooks().size());
+
+        sr.unregisterService(b, reg);
+        assertEquals("Should be no hooks left after unregistration", 0, sr.getEventHooks().size());
+        assertEquals("Should be no hooks left after unregistration", 0, sr.getFindHooks().size());
+        assertEquals("Should be no hooks left after unregistration", 0, sr.getListenerHooks().size());
+    }
+    
+    public void testRegisterFindHookService() 
+    {
+        MockControl control = MockControl.createNiceControl(Bundle.class);
+        Bundle b = (Bundle) control.getMock();
+        control.replay();
+        
+        ServiceRegistry sr = new ServiceRegistry(new Logger());
+        FindHook hook = new FindHook() 
+        {
+            public void find(BundleContext context, String name, String filter,
+                boolean allServices, Collection references) 
+            {
+            }
+        };
+        
+        assertEquals("Precondition failed", 0, sr.getEventHooks().size());
+        assertEquals("Precondition failed", 0, sr.getFindHooks().size());
+        assertEquals("Precondition failed", 0, sr.getListenerHooks().size());
+        ServiceRegistration reg = sr.registerService(b, new String [] {FindHook.class.getName()}, hook, new Hashtable());
+        assertEquals(1, sr.getFindHooks().size());
+        assertSame(hook, sr.getFindHooks().iterator().next());
+        assertEquals("Postcondition failed", 0, sr.getEventHooks().size());
+        assertEquals("Postcondition failed", 0, sr.getListenerHooks().size());
+
+        sr.unregisterService(b, reg);
+        assertEquals("Should be no hooks left after unregistration", 0, sr.getEventHooks().size());
+        assertEquals("Should be no hooks left after unregistration", 0, sr.getFindHooks().size());
+        assertEquals("Should be no hooks left after unregistration", 0, sr.getListenerHooks().size());
+    }
+
+    public void testRegisterFindHookServiceFactory() 
+    {
+        MockControl control = MockControl.createNiceControl(Bundle.class);
+        Bundle b = (Bundle) control.getMock();
+        control.replay();
+        
+        ServiceRegistry sr = new ServiceRegistry(new Logger());
+        MockControl sfControl = MockControl.createNiceControl(ServiceFactory.class);
+        sfControl.replay();
+        ServiceFactory sf = (ServiceFactory) sfControl.getMock();
+        
+        assertEquals("Precondition failed", 0, sr.getEventHooks().size());
+        assertEquals("Precondition failed", 0, sr.getFindHooks().size());
+        assertEquals("Precondition failed", 0, sr.getListenerHooks().size());
+        ServiceRegistration reg = sr.registerService(b, new String [] {FindHook.class.getName()}, sf, new Hashtable());
+        assertEquals(1, sr.getFindHooks().size());
+        Object [] arr = (Object[]) sr.getFindHooks().iterator().next();
+        assertEquals(2, arr.length);
+        assertSame(sf, arr[0]);
+        assertTrue(arr[1] instanceof ServiceRegistration);
+        assertEquals("Postcondition failed", 0, sr.getEventHooks().size());
+        assertEquals("Postcondition failed", 0, sr.getListenerHooks().size());
+
+        sr.unregisterService(b, reg);
+        assertEquals("Should be no hooks left after unregistration", 0, sr.getEventHooks().size());
+        assertEquals("Should be no hooks left after unregistration", 0, sr.getFindHooks().size());
+        assertEquals("Should be no hooks left after unregistration", 0, sr.getListenerHooks().size());
+    }
+
+    public void testRegisterListenerHookService() 
+    {
+        MockControl control = MockControl.createNiceControl(Bundle.class);
+        Bundle b = (Bundle) control.getMock();
+        control.replay();
+        
+        ServiceRegistry sr = new ServiceRegistry(new Logger());
+        ListenerHook hook = new ListenerHook() 
+        {
+
+            public void added(Collection listeners) 
+            {
+            }
+
+            public void removed(Collection listener) 
+            {
+            }
+        };
+        
+        assertEquals("Precondition failed", 0, sr.getEventHooks().size());
+        assertEquals("Precondition failed", 0, sr.getFindHooks().size());
+        assertEquals("Precondition failed", 0, sr.getListenerHooks().size());
+        ServiceRegistration reg = sr.registerService(b, new String [] {ListenerHook.class.getName()}, hook, new Hashtable());
+        assertEquals(1, sr.getListenerHooks().size());
+        assertSame(hook, sr.getListenerHooks().iterator().next());
+        assertEquals("Postcondition failed", 0, sr.getEventHooks().size());
+        assertEquals("Postcondition failed", 0, sr.getFindHooks().size());
+
+        sr.unregisterService(b, reg);
+        assertEquals("Should be no hooks left after unregistration", 0, sr.getEventHooks().size());
+        assertEquals("Should be no hooks left after unregistration", 0, sr.getFindHooks().size());
+        assertEquals("Should be no hooks left after unregistration", 0, sr.getListenerHooks().size());
+    }
+
+    public void testRegisterListenerHookServiceFactory() 
+    {
+        MockControl control = MockControl.createNiceControl(Bundle.class);
+        Bundle b = (Bundle) control.getMock();
+        control.replay();
+        
+        ServiceRegistry sr = new ServiceRegistry(new Logger());
+        MockControl sfControl = MockControl.createNiceControl(ServiceFactory.class);
+        sfControl.replay();
+        ServiceFactory sf = (ServiceFactory) sfControl.getMock();
+        
+        assertEquals("Precondition failed", 0, sr.getEventHooks().size());
+        assertEquals("Precondition failed", 0, sr.getFindHooks().size());
+        assertEquals("Precondition failed", 0, sr.getListenerHooks().size());
+        ServiceRegistration reg = sr.registerService(b, new String [] {ListenerHook.class.getName()}, sf, new Hashtable());
+        assertEquals(1, sr.getListenerHooks().size());
+        Object [] arr = (Object[]) sr.getListenerHooks().iterator().next();
+        assertEquals(2, arr.length);
+        assertSame(sf, arr[0]);
+        assertTrue(arr[1] instanceof ServiceRegistration);
+        assertEquals("Postcondition failed", 0, sr.getEventHooks().size());
+        assertEquals("Postcondition failed", 0, sr.getFindHooks().size());
+
+        sr.unregisterService(b, reg);
+        assertEquals("Should be no hooks left after unregistration", 0, sr.getEventHooks().size());
+        assertEquals("Should be no hooks left after unregistration", 0, sr.getFindHooks().size());
+        assertEquals("Should be no hooks left after unregistration", 0, sr.getListenerHooks().size());
+    }
+
+    public void testRegisterCombinedService() 
+    {
+        MockControl control = MockControl.createNiceControl(Bundle.class);
+        Bundle b = (Bundle) control.getMock();
+        control.replay();
+        
+        ServiceRegistry sr = new ServiceRegistry(new Logger());
+        class CombinedService implements ListenerHook, FindHook, EventHook, Runnable
+        {
+            public void added(Collection listeners) 
+            {
+            }
+
+            public void removed(Collection listener) 
+            {
+            }
+
+            public void find(BundleContext context, String name, String filter,
+                    boolean allServices, Collection references) 
+            {
+            }
+
+            public void event(ServiceEvent event, Collection contexts) 
+            {
+            }
+
+            public void run() 
+            {
+            }
+            
+        }
+        CombinedService hook = new CombinedService();
+        
+        assertEquals("Precondition failed", 0, sr.getEventHooks().size());
+        assertEquals("Precondition failed", 0, sr.getFindHooks().size());
+        assertEquals("Precondition failed", 0, sr.getListenerHooks().size());
+        ServiceRegistration reg = sr.registerService(b, new String [] {
+                Runnable.class.getName(),
+                ListenerHook.class.getName(),
+                FindHook.class.getName(),
+                EventHook.class.getName()}, hook, new Hashtable());
+        assertEquals(1, sr.getListenerHooks().size());
+        assertSame(hook, sr.getListenerHooks().iterator().next());
+        assertEquals(1, sr.getEventHooks().size());
+        assertSame(hook, sr.getEventHooks().iterator().next());
+        assertEquals(1, sr.getFindHooks().size());
+        assertSame(hook, sr.getFindHooks().iterator().next());
+
+        sr.unregisterService(b, reg);
+        assertEquals("Should be no hooks left after unregistration", 0, sr.getEventHooks().size());
+        assertEquals("Should be no hooks left after unregistration", 0, sr.getFindHooks().size());
+        assertEquals("Should be no hooks left after unregistration", 0, sr.getListenerHooks().size());
+    }
+
+    public void testRegisterPlainService() 
+    {
+        MockControl control = MockControl.createNiceControl(Bundle.class);
+        Bundle b = (Bundle) control.getMock();
+        control.replay();
+        
+        ServiceRegistry sr = new ServiceRegistry(new Logger());
+        String svcObj = "hello";        
+        assertEquals("Precondition failed", 0, sr.getEventHooks().size());
+        assertEquals("Precondition failed", 0, sr.getFindHooks().size());
+        assertEquals("Precondition failed", 0, sr.getListenerHooks().size());
+        ServiceRegistration reg = sr.registerService(b, new String [] {String.class.getName()}, svcObj, new Hashtable());
+        assertEquals("Postcondition failed", 0, sr.getEventHooks().size());
+        assertEquals("Postcondition failed", 0, sr.getFindHooks().size());
+        assertEquals("Postcondition failed", 0, sr.getListenerHooks().size());
+
+        sr.unregisterService(b, reg);
+        assertEquals("Unregistration should have no effect", 0, sr.getEventHooks().size());
+        assertEquals("Unregistration should have no effect", 0, sr.getFindHooks().size());
+        assertEquals("Unregistration should have no effect", 0, sr.getListenerHooks().size());
+    }
+    
+    public void testInvokeHook() 
+    {
+        final List result = new ArrayList();
+        InvokeHookCallback callback = new InvokeHookCallback() 
+        {
+            public void invokeHook(Object hook) 
+            {
+                result.add(hook);
+            }            
+        };
+        
+        MockControl control = MockControl.createNiceControl(Framework.class);
+        Framework fr = (Framework) control.getMock();
+        control.replay();
+        
+        FindHook hook = new FindHook() 
+        {
+            public void find(BundleContext context, String name, String filter,
+                    boolean allServices, Collection references) 
+            {
+            }            
+        };
+        assertSame(hook, ServiceRegistry.getHookRef(hook, null));
+        
+        assertEquals("Precondition failed", 0, result.size());
+        ServiceRegistry.invokeHook(hook, fr, callback);
+        assertEquals(1, result.size());
+        assertSame(hook, result.iterator().next());
+    }
+    
+    public void testInvokeHookFactory() 
+    {
+        final List result = new ArrayList();
+        InvokeHookCallback callback = new InvokeHookCallback() 
+        {
+            public void invokeHook(Object hook) 
+            {
+                result.add(hook);
+            }            
+        };
+
+        MockControl control = MockControl.createNiceControl(Framework.class);
+        Framework fr = (Framework) control.getMock();
+        control.replay();
+        
+        final FindHook hook = new FindHook() 
+        {
+            public void find(BundleContext context, String name, String filter,
+                    boolean allServices, Collection references) 
+            {
+            }            
+        };
+        
+        final List sfGet = new ArrayList();
+        final List sfUnget = new ArrayList();
+        ServiceFactory sf = new ServiceFactory() 
+        {
+            public Object getService(Bundle b, ServiceRegistration reg) 
+            {
+                sfGet.add(reg);
+                return hook;
+            }
+
+            public void ungetService(Bundle b, ServiceRegistration reg, Object svcObj) 
+            {
+                sfUnget.add(reg);
+                assertSame(svcObj, hook);
+            }            
+        };
+
+        MockControl control2 = MockControl.createNiceControl(ServiceRegistration.class);
+        ServiceRegistration reg = (ServiceRegistration) control2.getMock();
+        control2.replay();
+        
+        Object [] arg = new Object[2];
+        arg[0] = sf;
+        arg[1] = reg;
+        Object [] arg2 = (Object[]) ServiceRegistry.getHookRef(sf, reg);
+        assertTrue(Arrays.equals(arg, arg2));
+    
+        assertEquals("Precondition failed", 0, result.size());
+        assertEquals("Precondition failed", 0, sfGet.size());
+        assertEquals("Precondition failed", 0, sfUnget.size());
+        ServiceRegistry.invokeHook(arg, fr, callback);
+        assertEquals(1, result.size());
+        assertSame(hook, result.iterator().next());
+        assertEquals(1, sfGet.size());
+        assertEquals(1, sfUnget.size());
+        assertSame(reg, sfGet.iterator().next());        
+        assertSame(reg, sfUnget.iterator().next());        
+    }
+}
diff --git a/framework/src/test/java/org/apache/felix/framework/util/EventDispatcherTest.java b/framework/src/test/java/org/apache/felix/framework/util/EventDispatcherTest.java
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/framework/src/test/java/org/apache/felix/framework/util/EventDispatcherTest.java