Implement service registry event listener hook. (FELIX-3056)


git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1151630 13f79535-47bb-0310-9956-ffa450edef68
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 91436c0..1206cc8 100644
--- a/framework/src/main/java/org/apache/felix/framework/ServiceRegistry.java
+++ b/framework/src/main/java/org/apache/felix/framework/ServiceRegistry.java
@@ -56,6 +56,7 @@
         org.osgi.framework.hooks.bundle.FindHook.class,
         org.osgi.framework.hooks.bundle.EventHook.class,
         org.osgi.framework.hooks.service.EventHook.class,
+        org.osgi.framework.hooks.service.EventListenerHook.class,
         org.osgi.framework.hooks.service.FindHook.class,
         org.osgi.framework.hooks.service.ListenerHook.class,
         org.osgi.framework.hooks.weaving.WeavingHook.class,
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 407ca63..30cdf80 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
@@ -440,7 +440,7 @@
         }
 
         // Fire all framework listeners on a separate thread.
-        fireEventAsynchronously(this, Request.FRAMEWORK_EVENT, listeners, null, event);
+        fireEventAsynchronously(this, Request.FRAMEWORK_EVENT, listeners, event);
     }
 
     public void fireBundleEvent(BundleEvent event, Framework felix)
@@ -459,9 +459,36 @@
         Set<BundleContext> whitelist = createWhitelistFromHooks(event, felix,
             listeners, syncListeners, org.osgi.framework.hooks.bundle.EventHook.class);
 
+        // If we have a whitelist, then create copies of only the whitelisted
+        // listeners.
+        if (whitelist != null)
+        {
+            Map<BundleContext, List<ListenerInfo>> copy =
+                new HashMap<BundleContext, List<ListenerInfo>>();
+            for (BundleContext bc : whitelist)
+            {
+                List<ListenerInfo> infos = listeners.get(bc);
+                if (infos != null)
+                {
+                    copy.put(bc, infos);
+                }
+            }
+            listeners = copy;
+            copy = new HashMap<BundleContext, List<ListenerInfo>>();
+            for (BundleContext bc : whitelist)
+            {
+                List<ListenerInfo> infos = syncListeners.get(bc);
+                if (infos != null)
+                {
+                    copy.put(bc, infos);
+                }
+            }
+            syncListeners = copy;
+        }
+
         // Fire synchronous bundle listeners immediately on the calling thread.
         fireEventImmediately(
-            this, Request.BUNDLE_EVENT, syncListeners, whitelist, event, null);
+            this, Request.BUNDLE_EVENT, syncListeners, event, null);
 
         // The spec says that asynchronous bundle listeners do not get events
         // of types STARTING, STOPPING, or LAZY_ACTIVATION.
@@ -471,7 +498,7 @@
         {
             // Fire asynchronous bundle listeners on a separate thread.
             fireEventAsynchronously(
-                this, Request.BUNDLE_EVENT, listeners, whitelist, event);
+                this, Request.BUNDLE_EVENT, listeners, event);
         }
     }
 
@@ -485,14 +512,107 @@
             listeners = m_svcListeners;
         }
 
-        // Create a whitelist of bundle contexts for service listeners,
-        // if we have hooks.
-        Set<BundleContext> whitelist = createWhitelistFromHooks(event, felix,
-            listeners, null, org.osgi.framework.hooks.service.EventHook.class);
+        // Use service registry hooks to filter target listeners.
+        listeners = filterListenersUsingHooks(event, felix, listeners);
 
-        // Fire synchronous bundle listeners immediately on the calling thread.
+        // Fire all service events immediately on the calling thread.
         fireEventImmediately(
-            this, Request.SERVICE_EVENT, listeners, whitelist, event, oldProps);
+            this, Request.SERVICE_EVENT, listeners, event, oldProps);
+    }
+
+// TODO: OSGi R4.3 - This is ugly and inefficient.
+    private Map<BundleContext, List<ListenerInfo>> filterListenersUsingHooks(
+        ServiceEvent event, Framework felix, Map<BundleContext, List<ListenerInfo>> listeners)
+    {
+        Set<ServiceReference<org.osgi.framework.hooks.service.EventHook>> ehs =
+            m_registry.getHooks(org.osgi.framework.hooks.service.EventHook.class);
+        if ((ehs != null) && !ehs.isEmpty())
+        {
+            // Create a whitelist of bundle context for bundle listeners,
+            // if we have hooks.
+            Set<BundleContext> whitelist = createWhitelistFromHooks(event, felix,
+                listeners, null, org.osgi.framework.hooks.service.EventHook.class);
+
+            // If we have a whitelist, then create copies of only the whitelisted
+            // listeners.
+            if (whitelist != null)
+            {
+                Map<BundleContext, List<ListenerInfo>> copy =
+                    new HashMap<BundleContext, List<ListenerInfo>>();
+                for (BundleContext bc : whitelist)
+                {
+                    copy.put(bc, listeners.get(bc));
+                }
+                listeners = copy;
+            }
+        }
+
+        Set<ServiceReference<org.osgi.framework.hooks.service.EventListenerHook>> elhs =
+            m_registry.getHooks(org.osgi.framework.hooks.service.EventListenerHook.class);
+        if ((elhs != null) && !elhs.isEmpty())
+        {
+            // Create shrinkable map with shrinkable collections.
+            Map<BundleContext, Collection<ListenerHook.ListenerInfo>> shrinkableMap =
+                new HashMap<BundleContext, Collection<ListenerHook.ListenerInfo>>();
+            for (Entry<BundleContext, List<ListenerInfo>> entry : listeners.entrySet())
+            {
+                Collection shrinkableCollection =
+                    new ShrinkableCollection(new ArrayList(entry.getValue()));
+                shrinkableMap.put(
+                    entry.getKey(),
+                    (Collection<ListenerHook.ListenerInfo>) shrinkableCollection);
+            }
+            shrinkableMap =
+                new ShrinkableMap<BundleContext, Collection<ListenerHook.ListenerInfo>>
+                    (shrinkableMap);
+
+            for (ServiceReference<org.osgi.framework.hooks.service.EventListenerHook> sr : elhs)
+            {
+                if (felix != null)
+                {
+                    org.osgi.framework.hooks.service.EventListenerHook elh = null;
+                    try
+                    {
+                        elh = m_registry.getService(felix, sr);
+                    }
+                    catch (Exception ex)
+                    {
+                        // If we can't get the hook, then ignore it.
+                    }
+                    if (elh != null)
+                    {
+                        try
+                        {
+                            elh.event(event, shrinkableMap);
+                        }
+                        catch (Throwable th)
+                        {
+                            m_logger.log(sr, Logger.LOG_WARNING,
+                                "Problem invoking event hook", th);
+                        }
+                        finally
+                        {
+                            m_registry.ungetService(felix, sr);
+                        }
+                    }
+                }
+            }
+// TODO: OSGi R4.3 - Should check and only do this if there was a change.
+//       Also, it is inefficient to have to create new lists for the values.
+            Map<BundleContext, List<ListenerInfo>> newMap =
+                new HashMap<BundleContext, List<ListenerInfo>>();
+            for (Entry entry : shrinkableMap.entrySet())
+            {
+                if (!((Collection) entry.getValue()).isEmpty())
+                {
+                    newMap.put((BundleContext) entry.getKey(),
+                        new ArrayList<ListenerInfo>((Collection) entry.getValue()));
+                }
+            }
+            listeners = newMap;
+        }
+
+        return listeners;
     }
 
     private Set<BundleContext> createWhitelistFromHooks(
@@ -575,7 +695,7 @@
     private static void fireEventAsynchronously(
         EventDispatcher dispatcher, int type,
         Map<BundleContext, List<ListenerInfo>> listeners,
-        Set<BundleContext> whitelist, EventObject event)
+        EventObject event)
     {
         //TODO: should possibly check this within thread lock, seems to be ok though without
         // If dispatch thread is stopped, then ignore dispatch request.
@@ -602,7 +722,6 @@
         req.m_dispatcher = dispatcher;
         req.m_type = type;
         req.m_listeners = listeners;
-        req.m_whitelist = whitelist;
         req.m_event = event;
 
         // Lock the request list.
@@ -618,7 +737,7 @@
     private static void fireEventImmediately(
         EventDispatcher dispatcher, int type,
         Map<BundleContext, List<ListenerInfo>> listeners,
-        Set<BundleContext> whitelist, EventObject event, Dictionary oldProps)
+        EventObject event, Dictionary oldProps)
     {
         if (!listeners.isEmpty())
         {
@@ -632,36 +751,32 @@
                     Filter filter = info.getParsedFilter();
                     Object acc = info.getSecurityContext();
 
-                    // Only deliver events to bundles in the whitelist, if we have one.
-                    if ((whitelist == null) || whitelist.contains(bc))
+                    try
                     {
-                        try
+                        if (type == Request.FRAMEWORK_EVENT)
                         {
-                            if (type == Request.FRAMEWORK_EVENT)
-                            {
-                                invokeFrameworkListenerCallback(bc.getBundle(), l, event);
-                            }
-                            else if (type == Request.BUNDLE_EVENT)
-                            {
-                                invokeBundleListenerCallback(bc.getBundle(), l, event);
-                            }
-                            else if (type == Request.SERVICE_EVENT)
-                            {
-                                invokeServiceListenerCallback(
-                                    bc.getBundle(), l, filter, acc, event, oldProps);
-                            }
+                            invokeFrameworkListenerCallback(bc.getBundle(), l, event);
                         }
-                        catch (Throwable th)
+                        else if (type == Request.BUNDLE_EVENT)
                         {
-                            if ((type != Request.FRAMEWORK_EVENT)
-                                || (((FrameworkEvent) event).getType() != FrameworkEvent.ERROR))
-                            {
-                                dispatcher.m_logger.log(bc.getBundle(),
-                                    Logger.LOG_ERROR,
-                                    "EventDispatcher: Error during dispatch.", th);
-                                dispatcher.fireFrameworkEvent(
-                                    new FrameworkEvent(FrameworkEvent.ERROR, bc.getBundle(), th));
-                            }
+                            invokeBundleListenerCallback(bc.getBundle(), l, event);
+                        }
+                        else if (type == Request.SERVICE_EVENT)
+                        {
+                            invokeServiceListenerCallback(
+                                bc.getBundle(), l, filter, acc, event, oldProps);
+                        }
+                    }
+                    catch (Throwable th)
+                    {
+                        if ((type != Request.FRAMEWORK_EVENT)
+                            || (((FrameworkEvent) event).getType() != FrameworkEvent.ERROR))
+                        {
+                            dispatcher.m_logger.log(bc.getBundle(),
+                                Logger.LOG_ERROR,
+                                "EventDispatcher: Error during dispatch.", th);
+                            dispatcher.fireFrameworkEvent(
+                                new FrameworkEvent(FrameworkEvent.ERROR, bc.getBundle(), th));
                         }
                     }
                 }
@@ -945,7 +1060,7 @@
             // catching Throwables when it invokes callbacks.
             fireEventImmediately(
                 req.m_dispatcher, req.m_type, req.m_listeners,
-                req.m_whitelist, req.m_event, null);
+                req.m_event, null);
 
             // Put dispatch request in cache.
             synchronized (m_requestPool)
@@ -953,7 +1068,6 @@
                 req.m_dispatcher = null;
                 req.m_type = -1;
                 req.m_listeners = null;
-                req.m_whitelist = null;
                 req.m_event = null;
                 m_requestPool.add(req);
             }
@@ -969,7 +1083,6 @@
         public EventDispatcher m_dispatcher = null;
         public int m_type = -1;
         public Map<BundleContext, List<ListenerInfo>> m_listeners = null;
-        public Set<BundleContext> m_whitelist = null;
         public EventObject m_event = null;
     }
 }
\ No newline at end of file
diff --git a/framework/src/main/java/org/apache/felix/framework/util/ShrinkableCollection.java b/framework/src/main/java/org/apache/felix/framework/util/ShrinkableCollection.java
index 8313d70..4d9bd96 100644
--- a/framework/src/main/java/org/apache/felix/framework/util/ShrinkableCollection.java
+++ b/framework/src/main/java/org/apache/felix/framework/util/ShrinkableCollection.java
@@ -21,9 +21,9 @@
 import java.util.Collection;
 import java.util.Iterator;
 
-/** This collection wraps any other collection but prohibits calls to add
- *  elements to the collection.
- */
+/**
+ * A collection wrapper that only permits clients to shrink the collection.
+**/
 public class ShrinkableCollection<T> implements Collection<T>
 {
     private final Collection<T> m_delegate;
diff --git a/framework/src/main/java/org/apache/felix/framework/util/ShrinkableMap.java b/framework/src/main/java/org/apache/felix/framework/util/ShrinkableMap.java
new file mode 100644
index 0000000..0923f1d
--- /dev/null
+++ b/framework/src/main/java/org/apache/felix/framework/util/ShrinkableMap.java
@@ -0,0 +1,94 @@
+/*
+ * 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 java.util.Collection;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+public class ShrinkableMap<K, V> implements Map<K, V>
+{
+    private final Map<K, V> m_delegate;
+
+    public ShrinkableMap(Map<K, V> delegate)
+    {
+        m_delegate = delegate;
+    }
+
+    public int size()
+    {
+        return m_delegate.size();
+    }
+
+    public boolean isEmpty()
+    {
+        return m_delegate.isEmpty();
+    }
+
+    public boolean containsKey(Object o)
+    {
+        return m_delegate.containsKey(o);
+    }
+
+    public boolean containsValue(Object o)
+    {
+        return m_delegate.containsValue(o);
+    }
+
+    public V get(Object o)
+    {
+        return m_delegate.get(o);
+    }
+
+    public V put(K k, V v)
+    {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    public V remove(Object o)
+    {
+        return m_delegate.remove(o);
+    }
+
+    public void putAll(Map<? extends K, ? extends V> map)
+    {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    public void clear()
+    {
+        m_delegate.clear();
+    }
+
+    public Set<K> keySet()
+    {
+        return m_delegate.keySet();
+    }
+
+    public Collection<V> values()
+    {
+        return m_delegate.values();
+    }
+
+    public Set<Entry<K, V>> entrySet()
+    {
+        return m_delegate.entrySet();
+    }
+}
\ No newline at end of file