blob: a58af941326cd007e51553c2936a8b1da191faf2 [file] [log] [blame]
/*
* 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.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Dictionary;
import java.util.EventListener;
import java.util.EventObject;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.felix.framework.util.*;
import org.osgi.framework.AllServiceListener;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleListener;
import org.osgi.framework.Filter;
import org.osgi.framework.FrameworkEvent;
import org.osgi.framework.FrameworkListener;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServicePermission;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.SynchronousBundleListener;
import org.osgi.framework.UnfilteredServiceListener;
import org.osgi.framework.hooks.service.ListenerHook;
import org.osgi.framework.launch.Framework;
public class EventDispatcher
{
private final Logger m_logger;
private final ServiceRegistry m_registry;
private Map<BundleContext, List<ListenerInfo>>
m_fwkListeners = Collections.EMPTY_MAP;
private Map<BundleContext, List<ListenerInfo>>
m_bndlListeners = Collections.EMPTY_MAP;
private Map<BundleContext, List<ListenerInfo>>
m_syncBndlListeners = Collections.EMPTY_MAP;
private Map<BundleContext, List<ListenerInfo>>
m_svcListeners = Collections.EMPTY_MAP;
// A single thread is used to deliver events for all dispatchers.
private static Thread m_thread = null;
private final static String m_threadLock = new String("thread lock");
private static int m_references = 0;
private static volatile boolean m_stopping = false;
// List of requests.
private static final List<Request> m_requestList = new ArrayList<Request>();
// Pooled requests to avoid memory allocation.
private static final List<Request> m_requestPool = new ArrayList<Request>();
private static final SecureAction m_secureAction = new SecureAction();
public EventDispatcher(Logger logger, ServiceRegistry registry)
{
m_logger = logger;
m_registry = registry;
}
public void startDispatching()
{
synchronized (m_threadLock)
{
// Start event dispatching thread if necessary.
if (m_thread == null || !m_thread.isAlive())
{
m_stopping = false;
m_thread = new Thread(new Runnable() {
@Override
public void run()
{
try
{
EventDispatcher.run();
}
finally
{
// Ensure we update state even if stopped by external cause
// e.g. an Applet VM forceably killing threads
synchronized (m_threadLock)
{
m_thread = null;
m_stopping = false;
m_references = 0;
m_threadLock.notifyAll();
}
}
}
}, "FelixDispatchQueue");
m_thread.start();
}
// reference counting and flags
m_references++;
}
}
public void stopDispatching()
{
synchronized (m_threadLock)
{
// Return if already dead or stopping.
if (m_thread == null || m_stopping)
{
return;
}
// decrement use counter, don't continue if there are users
m_references--;
if (m_references > 0)
{
return;
}
m_stopping = true;
}
// Signal dispatch thread.
synchronized (m_requestList)
{
m_requestList.notify();
}
// Use separate lock for shutdown to prevent any chance of nested lock deadlock
synchronized (m_threadLock)
{
while (m_thread != null)
{
try
{
m_threadLock.wait();
}
catch (InterruptedException ex)
{
}
}
}
}
public Filter addListener(BundleContext bc, Class clazz, EventListener l, Filter filter)
{
// Verify the listener.
if (l == null)
{
throw new IllegalArgumentException("Listener is null");
}
else if (!clazz.isInstance(l))
{
throw new IllegalArgumentException(
"Listener not of type " + clazz.getName());
}
// See if we can simply update the listener, if so then
// return immediately.
Filter oldFilter = updateListener(bc, clazz, l, filter);
if (oldFilter != null)
{
return oldFilter;
}
// Lock the object to add the listener.
synchronized (this)
{
// Verify that the bundle context is still valid.
try
{
bc.getBundle();
}
catch (IllegalStateException ex)
{
// Bundle context is no longer valid, so just return.
return null;
}
Map<BundleContext, List<ListenerInfo>> listeners = null;
Object acc = null;
if (clazz == FrameworkListener.class)
{
listeners = m_fwkListeners;
}
else if (clazz == BundleListener.class)
{
if (SynchronousBundleListener.class.isInstance(l))
{
listeners = m_syncBndlListeners;
}
else
{
listeners = m_bndlListeners;
}
}
else if (clazz == ServiceListener.class)
{
// Remember security context for filtering service events.
Object sm = System.getSecurityManager();
if (sm != null)
{
acc = ((SecurityManager) sm).getSecurityContext();
}
// We need to create a Set for keeping track of matching service
// registrations so we can fire ServiceEvent.MODIFIED_ENDMATCH
// events. We need a Set even if filter is null, since the
// listener can be updated and have a filter added later.
listeners = m_svcListeners;
}
else
{
throw new IllegalArgumentException("Unknown listener: " + l.getClass());
}
// Add listener.
ListenerInfo info =
new ListenerInfo(bc.getBundle(), bc, clazz, l, filter, acc, false);
listeners = addListenerInfo(listeners, info);
if (clazz == FrameworkListener.class)
{
m_fwkListeners = listeners;
}
else if (clazz == BundleListener.class)
{
if (SynchronousBundleListener.class.isInstance(l))
{
m_syncBndlListeners = listeners;
}
else
{
m_bndlListeners = listeners;
}
}
else if (clazz == ServiceListener.class)
{
m_svcListeners = listeners;
}
}
return null;
}
public ListenerHook.ListenerInfo removeListener(
BundleContext bc, Class clazz, EventListener l)
{
ListenerHook.ListenerInfo returnInfo = null;
// Verify listener.
if (l == null)
{
throw new IllegalArgumentException("Listener is null");
}
else if (!clazz.isInstance(l))
{
throw new IllegalArgumentException(
"Listener not of type " + clazz.getName());
}
// Lock the object to remove the listener.
synchronized (this)
{
Map<BundleContext, List<ListenerInfo>> listeners = null;
if (clazz == FrameworkListener.class)
{
listeners = m_fwkListeners;
}
else if (clazz == BundleListener.class)
{
if (SynchronousBundleListener.class.isInstance(l))
{
listeners = m_syncBndlListeners;
}
else
{
listeners = m_bndlListeners;
}
}
else if (clazz == ServiceListener.class)
{
listeners = m_svcListeners;
}
else
{
throw new IllegalArgumentException("Unknown listener: " + l.getClass());
}
// Try to find the instance in our list.
int idx = -1;
for (Entry<BundleContext, List<ListenerInfo>> entry : listeners.entrySet())
{
List<ListenerInfo> infos = entry.getValue();
for (int i = 0; i < infos.size(); i++)
{
ListenerInfo info = infos.get(i);
if (info.getBundleContext().equals(bc) &&
(info.getListenerClass() == clazz) &&
(info.getListener() == l))
{
// For service listeners, we must return some info about
// the listener for the ListenerHook callback.
if (ServiceListener.class == clazz)
{
returnInfo = new ListenerInfo(infos.get(i), true);
}
idx = i;
break;
}
}
}
// If we have the instance, then remove it.
if (idx >= 0)
{
listeners = removeListenerInfo(listeners, bc, idx);
}
if (clazz == FrameworkListener.class)
{
m_fwkListeners = listeners;
}
else if (clazz == BundleListener.class)
{
if (SynchronousBundleListener.class.isInstance(l))
{
m_syncBndlListeners = listeners;
}
else
{
m_bndlListeners = listeners;
}
}
else if (clazz == ServiceListener.class)
{
m_svcListeners = listeners;
}
}
// Return information about the listener; this is null
// for everything but service listeners.
return returnInfo;
}
public void removeListeners(BundleContext bc)
{
if (bc == null)
{
return;
}
synchronized (this)
{
// Remove all framework listeners associated with the specified bundle.
m_fwkListeners = removeListenerInfos(m_fwkListeners, bc);
// Remove all bundle listeners associated with the specified bundle.
m_bndlListeners = removeListenerInfos(m_bndlListeners, bc);
// Remove all synchronous bundle listeners associated with
// the specified bundle.
m_syncBndlListeners = removeListenerInfos(m_syncBndlListeners, bc);
// Remove all service listeners associated with the specified bundle.
m_svcListeners = removeListenerInfos(m_svcListeners, bc);
}
}
public Filter updateListener(BundleContext bc, Class clazz, EventListener l, Filter filter)
{
if (clazz == ServiceListener.class)
{
synchronized (this)
{
// Verify that the bundle context is still valid.
try
{
bc.getBundle();
}
catch (IllegalStateException ex)
{
// Bundle context is no longer valid, so just return.
}
// See if the service listener is already registered; if so then
// update its filter per the spec.
List<ListenerInfo> infos = m_svcListeners.get(bc);
for (int i = 0; (infos != null) && (i < infos.size()); i++)
{
ListenerInfo info = infos.get(i);
if (info.getBundleContext().equals(bc) &&
(info.getListenerClass() == clazz) &&
(info.getListener() == l))
{
// The spec says to update the filter in this case.
Filter oldFilter = info.getParsedFilter();
ListenerInfo newInfo = new ListenerInfo(
info.getBundle(),
info.getBundleContext(),
info.getListenerClass(),
info.getListener(),
filter,
info.getSecurityContext(),
info.isRemoved());
m_svcListeners = updateListenerInfo(m_svcListeners, i, newInfo);
return oldFilter;
}
}
}
}
return null;
}
/**
* 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<ListenerHook.ListenerInfo> getAllServiceListeners()
{
List<ListenerHook.ListenerInfo> listeners = new ArrayList<ListenerHook.ListenerInfo>();
synchronized (this)
{
for (Entry<BundleContext, List<ListenerInfo>> entry : m_svcListeners.entrySet())
{
listeners.addAll(entry.getValue());
}
}
return listeners;
}
public void fireFrameworkEvent(FrameworkEvent event)
{
// Take a snapshot of the listener array.
Map<BundleContext, List<ListenerInfo>> listeners = null;
synchronized (this)
{
listeners = m_fwkListeners;
}
// Fire all framework listeners on a separate thread.
fireEventAsynchronously(this, Request.FRAMEWORK_EVENT, listeners, event);
}
public void fireBundleEvent(BundleEvent event, Felix felix)
{
// Take a snapshot of the listener array.
Map<BundleContext, List<ListenerInfo>> listeners = null;
Map<BundleContext, List<ListenerInfo>> syncListeners = null;
synchronized (this)
{
listeners = m_bndlListeners;
syncListeners = m_syncBndlListeners;
}
// Create a whitelist of bundle context for bundle listeners,
// if we have hooks.
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, event, null);
// The spec says that asynchronous bundle listeners do not get events
// of types STARTING, STOPPING, or LAZY_ACTIVATION.
if ((event.getType() != BundleEvent.STARTING) &&
(event.getType() != BundleEvent.STOPPING) &&
(event.getType() != BundleEvent.LAZY_ACTIVATION))
{
// Fire asynchronous bundle listeners on a separate thread.
fireEventAsynchronously(
this, Request.BUNDLE_EVENT, listeners, event);
}
}
public void fireServiceEvent(
final ServiceEvent event, final Dictionary oldProps, final Felix felix)
{
// Take a snapshot of the listener array.
Map<BundleContext, List<ListenerInfo>> listeners = null;
synchronized (this)
{
listeners = m_svcListeners;
}
// Use service registry hooks to filter target listeners.
listeners = filterListenersUsingHooks(event, felix, listeners);
// Fire all service events immediately on the calling thread.
fireEventImmediately(
this, Request.SERVICE_EVENT, listeners, event, oldProps);
}
// TODO: OSGi R4.3 - This is ugly and inefficient.
private Map<BundleContext, List<ListenerInfo>> filterListenersUsingHooks(
ServiceEvent event, Felix felix, Map<BundleContext, List<ListenerInfo>> listeners)
{
Set<ServiceReference<org.osgi.framework.hooks.service.EventHook>> ehs =
m_registry.getHookRegistry().getHooks(org.osgi.framework.hooks.service.EventHook.class);
if (!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.getHookRegistry().getHooks(org.osgi.framework.hooks.service.EventListenerHook.class);
if (!elhs.isEmpty())
{
List<ListenerInfo> systemBundleListeners = null;
// The mutable map is used to keep the lists that underpin the shrinkable collections.
Map<BundleContext, List<ListenerInfo>> mutableMap =
new HashMap<BundleContext, List<ListenerInfo>>();
// Create map with shrinkable collections.
Map<BundleContext, Collection<ListenerHook.ListenerInfo>> shrinkableMap =
new HashMap<BundleContext, Collection<ListenerHook.ListenerInfo>>();
for (Entry<BundleContext, List<ListenerInfo>> entry : listeners.entrySet())
{
BundleContext bc = entry.getKey();
ArrayList<ListenerInfo> mutableList = new ArrayList<ListenerInfo>(entry.getValue());
mutableMap.put(bc, mutableList);
// We want to pass the list as a generic collection to Shrinkable Collection.
// Need to convert to raw type before we can do this...
ArrayList ml = mutableList;
Collection<ListenerHook.ListenerInfo> shrinkableCollection =
new ShrinkableCollection<ListenerHook.ListenerInfo>(ml);
shrinkableMap.put(bc, shrinkableCollection);
// Keep a copy of the System Bundle Listeners, as they might be removed by the hooks, but we
// actually need to keep them in the end.
if (bc == felix._getBundleContext())
systemBundleListeners = new ArrayList<ListenerInfo>(entry.getValue());
}
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, false);
}
catch (Exception ex)
{
// If we can't get the hook, then ignore it.
}
if (elh != null)
{
try
{
m_secureAction.invokeServiceEventListenerHook(
elh, event, shrinkableMap);
}
catch (Throwable th)
{
m_logger.log(sr, Logger.LOG_WARNING,
"Problem invoking event hook", th);
}
finally
{
m_registry.ungetService(felix, sr, null);
}
}
}
}
// TODO: OSGi R4.3 - Should check and only do this if there was a change.
// the listeners map should only contain List values, and not Shrinkable
// Collections. Therefore we create a new map from the lists that are
// the delegates for the Shrinkable Collections. Any changes made by the
// hooks will have propagated to these maps.
Map<BundleContext, List<ListenerInfo>> newMap =
new HashMap<BundleContext, List<ListenerInfo>>();
for (Map.Entry<BundleContext, Collection<ListenerHook.ListenerInfo>> entry : shrinkableMap.entrySet())
{
if (!entry.getValue().isEmpty())
{
newMap.put(entry.getKey(), mutableMap.get(entry.getKey()));
}
}
// Put the system bundle listeners back, because they really need to be called
// regardless whether they were removed by the hooks or not.
if (systemBundleListeners != null)
newMap.put(felix._getBundleContext(), systemBundleListeners);
listeners = newMap;
}
return listeners;
}
private <T> Set<BundleContext> createWhitelistFromHooks(
EventObject event, Felix felix,
Map<BundleContext, List<ListenerInfo>> listeners1,
Map<BundleContext, List<ListenerInfo>> listeners2,
Class<T> hookClass)
{
// Create a whitelist of bundle context, if we have hooks.
Set<BundleContext> whitelist = null;
Set<ServiceReference<T>> hooks = m_registry.getHookRegistry().getHooks(hookClass);
if (!hooks.isEmpty())
{
boolean systemBundleListener = false;
BundleContext systemBundleContext = felix._getBundleContext();
whitelist = new HashSet<BundleContext>();
for (Entry<BundleContext, List<ListenerInfo>> entry : listeners1.entrySet())
{
whitelist.add(entry.getKey());
if (entry.getKey() == systemBundleContext)
systemBundleListener = true;
}
if (listeners2 != null)
{
for (Entry<BundleContext, List<ListenerInfo>> entry : listeners2.entrySet())
{
whitelist.add(entry.getKey());
if (entry.getKey() == systemBundleContext)
systemBundleListener = true;
}
}
int originalSize = whitelist.size();
ShrinkableCollection<BundleContext> shrinkable =
new ShrinkableCollection<BundleContext>(whitelist);
for (ServiceReference<T> sr : hooks)
{
if (felix != null)
{
T eh = null;
try
{
eh = m_registry.getService(felix, sr, false);
}
catch (Exception ex)
{
// If we can't get the hook, then ignore it.
}
if (eh != null)
{
try
{
if (eh instanceof org.osgi.framework.hooks.service.EventHook)
{
m_secureAction.invokeServiceEventHook(
(org.osgi.framework.hooks.service.EventHook) eh,
(ServiceEvent) event, shrinkable);
}
else if (eh instanceof org.osgi.framework.hooks.bundle.EventHook)
{
m_secureAction.invokeBundleEventHook(
(org.osgi.framework.hooks.bundle.EventHook) eh,
(BundleEvent) event, shrinkable);
}
}
catch (Throwable th)
{
m_logger.log(sr, Logger.LOG_WARNING,
"Problem invoking event hook", th);
}
finally
{
m_registry.ungetService(felix, sr, null);
}
}
}
}
if (systemBundleListener && !whitelist.contains(systemBundleContext))
{
// The system bundle cannot be removed from the listeners, so if it was
// removed, add it back in. Note that this cannot be prevented by the shrinkable
// since the effect of removing the system bundle from the listeners must be
// visible between the event hooks.
whitelist.add(systemBundleContext);
}
// If the whitelist hasn't changed, then null it to avoid having
// to do whitelist lookups during event delivery.
if (originalSize == whitelist.size())
{
whitelist = null;
}
}
return whitelist;
}
private static void fireEventAsynchronously(
EventDispatcher dispatcher, int type,
Map<BundleContext, List<ListenerInfo>> listeners,
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.
if (m_stopping || m_thread == null)
{
return;
}
// First get a request from the pool or create one if necessary.
Request req = null;
synchronized (m_requestPool)
{
if (m_requestPool.size() > 0)
{
req = m_requestPool.remove(0);
}
else
{
req = new Request();
}
}
// Initialize dispatch request.
req.m_dispatcher = dispatcher;
req.m_type = type;
req.m_listeners = listeners;
req.m_event = event;
// Lock the request list.
synchronized (m_requestList)
{
// Add our request to the list.
m_requestList.add(req);
// Notify the dispatch thread that there is work to do.
m_requestList.notify();
}
}
private static void fireEventImmediately(
EventDispatcher dispatcher, int type,
Map<BundleContext, List<ListenerInfo>> listeners,
EventObject event, Dictionary oldProps)
{
if (!listeners.isEmpty())
{
// Notify appropriate listeners.
for (Entry<BundleContext, List<ListenerInfo>> entry : listeners.entrySet())
{
for (ListenerInfo info : entry.getValue())
{
Bundle bundle = info.getBundle();
EventListener l = info.getListener();
Filter filter = info.getParsedFilter();
Object acc = info.getSecurityContext();
try
{
if (type == Request.FRAMEWORK_EVENT)
{
invokeFrameworkListenerCallback(bundle, l, event);
}
else if (type == Request.BUNDLE_EVENT)
{
invokeBundleListenerCallback(bundle, l, event);
}
else if (type == Request.SERVICE_EVENT)
{
invokeServiceListenerCallback(
bundle, l, filter, acc, event, oldProps);
}
}
catch (Throwable th)
{
if ((type != Request.FRAMEWORK_EVENT)
|| (((FrameworkEvent) event).getType() != FrameworkEvent.ERROR))
{
dispatcher.m_logger.log(bundle,
Logger.LOG_ERROR,
"EventDispatcher: Error during dispatch.", th);
dispatcher.fireFrameworkEvent(
new FrameworkEvent(FrameworkEvent.ERROR, bundle, th));
}
}
}
}
}
}
private static void invokeFrameworkListenerCallback(
Bundle bundle, final EventListener l, final EventObject event)
{
// The spec says only active bundles receive asynchronous events,
// but we will include starting bundles too otherwise
// it is impossible to see everything.
if ((bundle.getState() == Bundle.STARTING) ||
(bundle.getState() == Bundle.ACTIVE))
{
if (System.getSecurityManager() != null)
{
AccessController.doPrivileged(new PrivilegedAction() {
@Override
public Object run()
{
((FrameworkListener) l).frameworkEvent((FrameworkEvent) event);
return null;
}
});
}
else
{
((FrameworkListener) l).frameworkEvent((FrameworkEvent) event);
}
}
}
private static void invokeBundleListenerCallback(
Bundle bundle, final EventListener l, final EventObject event)
{
// A bundle listener is either synchronous or asynchronous.
// If the bundle listener is synchronous, then deliver the
// event to bundles with a state of STARTING, STOPPING, or
// ACTIVE. If the listener is asynchronous, then deliver the
// event only to bundles that are STARTING or ACTIVE.
if (((SynchronousBundleListener.class.isAssignableFrom(l.getClass())) &&
((bundle.getState() == Bundle.STARTING) ||
(bundle.getState() == Bundle.STOPPING) ||
(bundle.getState() == Bundle.ACTIVE)))
||
((bundle.getState() == Bundle.STARTING) ||
(bundle.getState() == Bundle.ACTIVE)))
{
if (System.getSecurityManager() != null)
{
AccessController.doPrivileged(new PrivilegedAction() {
@Override
public Object run()
{
((BundleListener) l).bundleChanged((BundleEvent) event);
return null;
}
});
}
else
{
((BundleListener) l).bundleChanged((BundleEvent) event);
}
}
}
private static void invokeServiceListenerCallback(
Bundle bundle, final EventListener l, Filter filter, Object acc,
final EventObject event, final Dictionary oldProps)
{
// Service events should be delivered to STARTING,
// STOPPING, and ACTIVE bundles.
if ((bundle.getState() != Bundle.STARTING) &&
(bundle.getState() != Bundle.STOPPING) &&
(bundle.getState() != Bundle.ACTIVE))
{
return;
}
// Check that the bundle has permission to get at least
// one of the service interfaces; the objectClass property
// of the service stores its service interfaces.
ServiceReference ref = ((ServiceEvent) event).getServiceReference();
boolean hasPermission = true;
Object sm = System.getSecurityManager();
if ((acc != null) && (sm != null))
{
try
{
ServicePermission perm =
new ServicePermission(
ref, ServicePermission.GET);
((SecurityManager) sm).checkPermission(perm, acc);
}
catch (Exception ex)
{
hasPermission = false;
}
}
if (hasPermission)
{
// Dispatch according to the filter.
boolean matched;
if (l instanceof UnfilteredServiceListener)
{
// An UnfilteredServiceListener always matches, regardless of the filter.
// The filter is still passed on to the Service Registry Hooks.
matched = true;
}
else
{
matched = (filter == null)
|| filter.match(((ServiceEvent) event).getServiceReference());
}
if (matched)
{
if ((l instanceof AllServiceListener) ||
Util.isServiceAssignable(bundle, ((ServiceEvent) event).getServiceReference()))
{
if (System.getSecurityManager() != null)
{
AccessController.doPrivileged(new PrivilegedAction()
{
@Override
public Object run()
{
((ServiceListener) l).serviceChanged((ServiceEvent) event);
return null;
}
});
}
else
{
((ServiceListener) l).serviceChanged((ServiceEvent) event);
}
}
}
// We need to send an MODIFIED_ENDMATCH event if the listener
// matched previously.
else if (((ServiceEvent) event).getType() == ServiceEvent.MODIFIED)
{
if (filter.match(oldProps))
{
final ServiceEvent se = new ServiceEvent(
ServiceEvent.MODIFIED_ENDMATCH,
((ServiceEvent) event).getServiceReference());
if (System.getSecurityManager() != null)
{
AccessController.doPrivileged(new PrivilegedAction()
{
@Override
public Object run()
{
((ServiceListener) l).serviceChanged(se);
return null;
}
});
}
else
{
((ServiceListener) l).serviceChanged(se);
}
}
}
}
}
private static Map<BundleContext, List<ListenerInfo>> addListenerInfo(
Map<BundleContext, List<ListenerInfo>> listeners, ListenerInfo info)
{
// Make a copy of the map, since we will be mutating it.
Map<BundleContext, List<ListenerInfo>> copy =
new HashMap<BundleContext, List<ListenerInfo>>(listeners);
// Remove the affected entry and make a copy so we can modify it.
List<ListenerInfo> infos = copy.remove(info.getBundleContext());
if (infos == null)
{
infos = new ArrayList<ListenerInfo>();
}
else
{
infos = new ArrayList<ListenerInfo>(infos);
}
// Add the new listener info.
infos.add(info);
// Put the listeners back into the copy of the map and return it.
copy.put(info.getBundleContext(), infos);
return copy;
}
private static Map<BundleContext, List<ListenerInfo>> updateListenerInfo(
Map<BundleContext, List<ListenerInfo>> listeners, int idx,
ListenerInfo info)
{
// Make a copy of the map, since we will be mutating it.
Map<BundleContext, List<ListenerInfo>> copy =
new HashMap<BundleContext, List<ListenerInfo>>(listeners);
// Remove the affected entry and make a copy so we can modify it.
List<ListenerInfo> infos = copy.remove(info.getBundleContext());
if (infos != null)
{
infos = new ArrayList<ListenerInfo>(infos);
// Update the new listener info.
infos.set(idx, info);
// Put the listeners back into the copy of the map and return it.
copy.put(info.getBundleContext(), infos);
return copy;
}
return listeners;
}
private static Map<BundleContext, List<ListenerInfo>> removeListenerInfo(
Map<BundleContext, List<ListenerInfo>> listeners, BundleContext bc, int idx)
{
// Make a copy of the map, since we will be mutating it.
Map<BundleContext, List<ListenerInfo>> copy =
new HashMap<BundleContext, List<ListenerInfo>>(listeners);
// Remove the affected entry and make a copy so we can modify it.
List<ListenerInfo> infos = copy.remove(bc);
if (infos != null)
{
infos = new ArrayList<ListenerInfo>(infos);
// Remove the listener info.
infos.remove(idx);
if (!infos.isEmpty())
{
// Put the listeners back into the copy of the map and return it.
copy.put(bc, infos);
}
return copy;
}
return listeners;
}
private static Map<BundleContext, List<ListenerInfo>> removeListenerInfos(
Map<BundleContext, List<ListenerInfo>> listeners, BundleContext bc)
{
// Make a copy of the map, since we will be mutating it.
Map<BundleContext, List<ListenerInfo>> copy =
new HashMap<BundleContext, List<ListenerInfo>>(listeners);
// Remove the affected entry and return the copy.
copy.remove(bc);
return copy;
}
/**
* This is the dispatching thread's main loop.
**/
private static void run()
{
Request req = null;
while (true)
{
// Lock the request list so we can try to get a
// dispatch request from it.
synchronized (m_requestList)
{
// Wait while there are no requests to dispatch. If the
// dispatcher thread is supposed to stop, then let the
// dispatcher thread exit the loop and stop.
while (m_requestList.isEmpty() && !m_stopping)
{
// Wait until some signals us for work.
try
{
m_requestList.wait();
}
catch (InterruptedException ex)
{
// Not much we can do here except for keep waiting.
}
}
// If there are no events to dispatch and shutdown
// has been called then exit, otherwise dispatch event.
if (m_requestList.isEmpty() && m_stopping)
{
return;
}
// Get the dispatch request.
req = m_requestList.remove(0);
}
// Deliver event outside of synchronized block
// so that we don't block other requests from being
// queued during event processing.
// NOTE: We don't catch any exceptions here, because
// the invoked method shields us from exceptions by
// catching Throwables when it invokes callbacks.
fireEventImmediately(
req.m_dispatcher, req.m_type, req.m_listeners,
req.m_event, null);
// Put dispatch request in cache.
synchronized (m_requestPool)
{
req.m_dispatcher = null;
req.m_type = -1;
req.m_listeners = null;
req.m_event = null;
m_requestPool.add(req);
}
}
}
private static class Request
{
public static final int FRAMEWORK_EVENT = 0;
public static final int BUNDLE_EVENT = 1;
public static final int SERVICE_EVENT = 2;
public EventDispatcher m_dispatcher = null;
public int m_type = -1;
public Map<BundleContext, List<ListenerInfo>> m_listeners = null;
public EventObject m_event = null;
}
}