| /* |
| * 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; |
| } |
| } |