Applied a patch (FELIX-804) to add partial support for new service registry
hooks as proposed for OSGi R4.2; currently, only listener hooks are supported.


git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@714075 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 973c4f8..c640a75 100644
--- a/framework/src/main/java/org/apache/felix/framework/Felix.java
+++ b/framework/src/main/java/org/apache/felix/framework/Felix.java
@@ -31,6 +31,7 @@
 import org.apache.felix.framework.util.manifestparser.*;
 import org.apache.felix.moduleloader.*;
 import org.osgi.framework.*;
+import org.osgi.framework.hooks.service.ListenerHook;
 import org.osgi.service.packageadmin.ExportedPackage;
 import org.osgi.service.startlevel.StartLevel;
 
@@ -2625,6 +2626,14 @@
     {
         m_dispatcher.addListener(
             bundle, ServiceListener.class, l, (f == null) ? null : new FilterImpl(m_logger, f));
+
+        // Invoke the ListenerHook.added() on all hooks.
+        ListenerHook[] hooks = m_registry.getListenerHooks();
+        Collection c = Collections.singleton(new ListenerHookInfoImpl(bundle.getBundleContext(), f));
+        for (int i = 0; i < hooks.length; i++)
+        {
+            hooks[i].added(c);
+        }
     }
 
     /**
@@ -2636,7 +2645,19 @@
     **/
     protected void removeServiceListener(Bundle bundle, ServiceListener l)
     {
-        m_dispatcher.removeListener(bundle, ServiceListener.class, l);
+        ListenerHook.ListenerInfo listener =
+            m_dispatcher.removeListener(bundle, ServiceListener.class, l);
+
+        if (listener != null)
+        {
+            // Invoke the ListenerHook.removed() on all hooks.
+            ListenerHook[] hooks = m_registry.getListenerHooks();
+            Collection c = Collections.singleton(listener);
+            for (int i = 0; i < hooks.length; i++)
+            {
+                hooks[i].removed(c);
+            }
+        }
     }
 
     protected void addFrameworkListener(Bundle bundle, FrameworkListener l)
@@ -2718,6 +2739,14 @@
             releaseBundleLock(bundle);
         }
 
+        // Check to see if this a listener hook; if so, then we need
+        // 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());
+        }
+
         // TODO: CONCURRENCY - Reconsider firing event here, outside of the
         // bundle lock.
 
diff --git a/framework/src/main/java/org/apache/felix/framework/ServiceRegistrationImpl.java b/framework/src/main/java/org/apache/felix/framework/ServiceRegistrationImpl.java
index c338830..47073bb 100644
--- a/framework/src/main/java/org/apache/felix/framework/ServiceRegistrationImpl.java
+++ b/framework/src/main/java/org/apache/felix/framework/ServiceRegistrationImpl.java
@@ -167,6 +167,17 @@
         return m_registry.getUsingBundles(m_ref);
     }
 
+    /**
+     * This method provides direct access to the associated service object;
+     * it generally should not be used by anyone other than the service registry
+     * itself.
+     * @return The service object associated with the registration.
+    **/
+    Object getService()
+    {
+        return m_svcObj;
+    }
+
     protected Object getService(Bundle acqBundle)
     {
         // If the service object is a service factory, then
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 200b633..03e87b0 100644
--- a/framework/src/main/java/org/apache/felix/framework/ServiceRegistry.java
+++ b/framework/src/main/java/org/apache/felix/framework/ServiceRegistry.java
@@ -22,6 +22,7 @@
 
 import org.apache.felix.framework.util.FelixConstants;
 import org.osgi.framework.*;
+import org.osgi.framework.hooks.service.ListenerHook;
 
 public class ServiceRegistry
 {
@@ -38,6 +39,8 @@
 
     private ServiceListener m_serviceListener = null;
 
+    private final List m_listenerHooks = new ArrayList();
+
     public ServiceRegistry(Logger logger)
     {
         m_logger = logger;
@@ -65,6 +68,9 @@
 
         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);
@@ -78,6 +84,9 @@
 
     public void unregisterService(Bundle bundle, ServiceRegistration reg)
     {
+        // If this is a hook, it should be removed.
+        removeHook(((ServiceRegistrationImpl) reg).getService());
+
         synchronized (this)
         {
             // Note that we don't lock the service registration here using
@@ -426,7 +435,7 @@
         {
             usages = (UsageCount[]) m_inUseMap.get(bundle);
         }
-        
+
         if (usages == null)
         {
             return;
@@ -714,6 +723,53 @@
         }
     }
 
+    private void addHooks(String[] classNames, Object svcObj)
+    {
+        if (isHook(classNames, ListenerHook.class, svcObj))
+        {
+            synchronized (m_listenerHooks)
+            {
+                m_listenerHooks.add(svcObj);
+            }
+        }
+    }
+
+    boolean isHook(String[] classNames, Class hookClass, Object svcObj)
+    {
+        if (hookClass.isAssignableFrom(svcObj.getClass()))
+        {
+            String hookName = hookClass.getName();
+            for (int i = 0; i < classNames.length; i++)
+            {
+                if (classNames[i].equals(hookName))
+                {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private void removeHook(Object svcObj)
+    {
+        if (svcObj instanceof ListenerHook)
+        {
+            synchronized (m_listenerHooks)
+            {
+                m_listenerHooks.remove(svcObj);
+            }
+        }
+    }
+
+    ListenerHook[] getListenerHooks()
+    {
+        synchronized (m_listenerHooks)
+        {
+            return (ListenerHook[])
+                m_listenerHooks.toArray(new ListenerHook[m_listenerHooks.size()]);
+        }
+    }
+
     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 2774c26..8b3a565 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
@@ -21,9 +21,11 @@
 import java.security.AccessController;
 import java.security.PrivilegedAction;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.EventListener;
 import java.util.EventObject;
 
+import java.util.List;
 import org.apache.felix.framework.Logger;
 import org.osgi.framework.AllServiceListener;
 import org.osgi.framework.Bundle;
@@ -38,6 +40,7 @@
 import org.osgi.framework.ServicePermission;
 import org.osgi.framework.ServiceReference;
 import org.osgi.framework.SynchronousBundleListener;
+import org.osgi.framework.hooks.service.ListenerHook;
 
 public class EventDispatcher
 {
@@ -93,9 +96,9 @@
                         {
                             EventDispatcher.run();
                         }
-                        finally 
+                        finally
                         {
-                             // Ensure we update state even if stopped by external cause
+                            // Ensure we update state even if stopped by external cause
                             // e.g. an Applet VM forceably killing threads
                             synchronized (m_threadLock)
                             {
@@ -265,8 +268,11 @@
         }
     }
 
-    public void removeListener(Bundle bundle, Class clazz, EventListener l)
+    public ListenerHook.ListenerInfo removeListener(
+        Bundle bundle, Class clazz, EventListener l)
     {
+        ListenerHook.ListenerInfo listenerInfo = null;
+
         // Verify listener.
         if (l == null)
         {
@@ -315,6 +321,12 @@
                     (listeners[i + LISTENER_CLASS_OFFSET] == clazz) &&
                     (listeners[i + LISTENER_OBJECT_OFFSET] == l))
                 {
+                    // For service listeners, we must return some info about
+                    // the listener for the ListenerHook callback.
+                    if (ServiceListener.class == clazz)
+                    {
+                        listenerInfo = wrapListener(listeners, i);
+                    }
                     idx = i;
                     break;
                 }
@@ -367,6 +379,10 @@
                 m_serviceListeners = listeners;
             }
         }
+
+        // Return information about the listener; this is null
+        // for everything but service listeners.
+        return listenerInfo;
     }
 
     public void removeListeners(Bundle bundle)
@@ -504,6 +520,45 @@
         return false;
     }
 
+    /**
+     * Returns all existing service listener information into a collection of
+     * ListenerHook.ListenerInfo objects. This is used the first time a listener
+     * hook is registered to synchronize it with the existing set of listeners.
+     * @return Returns all existing service listener information into a collection of
+     *         ListenerHook.ListenerInfo objects
+    **/
+    public Collection /* <? extends ListenerHook.ListenerInfo> */ wrapAllServiceListeners()
+    {
+        Object[] listeners = null;
+        synchronized (this)
+        {
+            listeners = m_serviceListeners;
+        }
+
+        List existingListeners = new ArrayList();
+        for (int i = 0, j = 0; i < listeners.length; i += LISTENER_ARRAY_INCREMENT, j++)
+        {
+            existingListeners.add(wrapListener(listeners, i));
+        }
+        return existingListeners;
+    }
+
+    /**
+     * Wraps the information about a given listener in a ListenerHook.ListenerInfo
+     * object.
+     * @param listeners The array of listeners.
+     * @param offset The offset into the array of the listener to wrap.
+     * @return A ListenerHook.ListenerInfo object for the specified listener.
+     */
+    private static ListenerHook.ListenerInfo wrapListener(Object[] listeners, int offset)
+    {
+        Filter filter = ((Filter)listeners[offset + LISTENER_FILTER_OFFSET]);
+
+        return new ListenerHookInfoImpl(
+            ((Bundle)listeners[offset + LISTENER_BUNDLE_OFFSET]).getBundleContext(),
+            filter == null ? null : filter.toString());
+    }
+
     public void fireFrameworkEvent(FrameworkEvent event)
     {
         // Take a snapshot of the listener array.
diff --git a/framework/src/main/java/org/apache/felix/framework/util/ListenerHookInfoImpl.java b/framework/src/main/java/org/apache/felix/framework/util/ListenerHookInfoImpl.java
new file mode 100644
index 0000000..db36a8f
--- /dev/null
+++ b/framework/src/main/java/org/apache/felix/framework/util/ListenerHookInfoImpl.java
@@ -0,0 +1,44 @@
+/*
+ * 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.util;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.hooks.service.ListenerHook;
+
+public class ListenerHookInfoImpl implements ListenerHook.ListenerInfo
+{
+    private final BundleContext m_context;
+    private final String m_filter;
+
+    public ListenerHookInfoImpl(BundleContext context, String filter)
+    {
+        m_context = context;
+        m_filter = filter;
+    }
+
+    public BundleContext getBundleContext()
+    {
+        return m_context;
+    }
+
+    public String getFilter()
+    {
+        return m_filter;
+    }
+}
\ No newline at end of file
diff --git a/framework/src/main/java/org/osgi/framework/ServiceException.java b/framework/src/main/java/org/osgi/framework/ServiceException.java
new file mode 100644
index 0000000..c67d3da
--- /dev/null
+++ b/framework/src/main/java/org/osgi/framework/ServiceException.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.osgi.framework;
+
+public class ServiceException extends RuntimeException
+{
+    private String type;
+
+    public ServiceException(String type)
+    {
+        this(type, null);
+    }
+
+    public ServiceException(String type, Throwable cause)
+    {
+        super(cause);
+        this.type = type;
+    }
+
+    public String getType()
+    {
+        return type;
+    }
+}
\ No newline at end of file
diff --git a/framework/src/main/java/org/osgi/framework/hooks/service/ListenerHook.java b/framework/src/main/java/org/osgi/framework/hooks/service/ListenerHook.java
new file mode 100644
index 0000000..62e2a75
--- /dev/null
+++ b/framework/src/main/java/org/osgi/framework/hooks/service/ListenerHook.java
@@ -0,0 +1,37 @@
+/*
+ * 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.osgi.framework.hooks.service;
+
+import java.util.Collection;
+
+import org.osgi.framework.BundleContext;
+
+public interface ListenerHook
+{
+    public static interface ListenerInfo
+    {
+        BundleContext getBundleContext();
+
+        String getFilter();
+    }
+
+    void added(Collection /* <? extends ListenerInfo> */ listeners);
+
+    void removed(Collection /* <? extends ListenerInfo> */ listener);
+}
\ No newline at end of file
diff --git a/framework/src/main/resources/default.properties b/framework/src/main/resources/default.properties
index 62ceb6a..71e01af 100644
--- a/framework/src/main/resources/default.properties
+++ b/framework/src/main/resources/default.properties
@@ -19,6 +19,7 @@
 # Framework config properties.
 #
 org.osgi.framework.system.packages=org.osgi.framework; version=1.4.0, \
+ org.osgi.framework.hooks.service; version=1.4.0 \
  org.osgi.service.packageadmin; version=1.2.0, \
  org.osgi.service.startlevel; version=1.1.0, \
  org.osgi.service.url; version=1.0.0, \