| /******************************************************************************* |
| * Copyright (c) 2004, 2020 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.osgi.internal.serviceregistry; |
| |
| import java.security.AccessController; |
| import java.security.PrivilegedAction; |
| import java.security.ProtectionDomain; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Dictionary; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import org.eclipse.osgi.container.Module; |
| import org.eclipse.osgi.container.ModuleRevision; |
| import org.eclipse.osgi.framework.eventmgr.CopyOnWriteIdentityMap; |
| import org.eclipse.osgi.framework.eventmgr.EventDispatcher; |
| import org.eclipse.osgi.framework.eventmgr.ListenerQueue; |
| import org.eclipse.osgi.internal.debug.Debug; |
| import org.eclipse.osgi.internal.framework.BundleContextImpl; |
| import org.eclipse.osgi.internal.framework.EquinoxContainer; |
| import org.eclipse.osgi.internal.messages.Msg; |
| import org.eclipse.osgi.storage.BundleInfo.Generation; |
| import org.eclipse.osgi.util.NLS; |
| import org.osgi.framework.Bundle; |
| import org.osgi.framework.BundleContext; |
| import org.osgi.framework.Constants; |
| import org.osgi.framework.Filter; |
| import org.osgi.framework.FrameworkEvent; |
| import org.osgi.framework.InvalidSyntaxException; |
| import org.osgi.framework.ServiceEvent; |
| import org.osgi.framework.ServiceException; |
| import org.osgi.framework.ServiceFactory; |
| import org.osgi.framework.ServiceListener; |
| import org.osgi.framework.ServiceObjects; |
| import org.osgi.framework.ServicePermission; |
| import org.osgi.framework.ServiceReference; |
| import org.osgi.framework.ServiceRegistration; |
| import org.osgi.framework.hooks.service.EventHook; |
| import org.osgi.framework.hooks.service.EventListenerHook; |
| import org.osgi.framework.hooks.service.FindHook; |
| import org.osgi.framework.hooks.service.ListenerHook; |
| import org.osgi.framework.hooks.service.ListenerHook.ListenerInfo; |
| |
| /** |
| * The Service Registry. This class is the main control point for service |
| * layer operations in the framework. |
| * |
| * @ThreadSafe |
| */ |
| public class ServiceRegistry { |
| public static final int SERVICEEVENT = 3; |
| |
| static final String findHookName = FindHook.class.getName(); |
| @SuppressWarnings("deprecation") |
| static final String eventHookName = EventHook.class.getName(); |
| static final String eventListenerHookName = EventListenerHook.class.getName(); |
| static final String listenerHookName = ListenerHook.class.getName(); |
| |
| |
| /** Published services by class name. |
| * The {@literal List<ServiceRegistrationImpl<?>>}s are both sorted |
| * in the natural order of ServiceRegistrationImpl and also are sets in that |
| * there must be no two entries in a List which are equal. |
| */ |
| /* @GuardedBy("this") */ |
| private final Map<String, List<ServiceRegistrationImpl<?>>> publishedServicesByClass; |
| |
| /** All published services. |
| * The List is both sorted in the natural order of ServiceRegistrationImpl and also is a |
| * set in that there must be no two entries in the List which are equal. |
| */ |
| /* @GuardedBy("this") */ |
| private final List<ServiceRegistrationImpl<?>> allPublishedServices; |
| |
| /** Published services by BundleContextImpl. |
| * The {@literal List<ServiceRegistrationImpl<?>>}s are NOT sorted |
| * and also are sets in that |
| * there must be no two entries in a List which are equal. |
| */ |
| /* @GuardedBy("this") */ |
| private final Map<BundleContextImpl, List<ServiceRegistrationImpl<?>>> publishedServicesByContext; |
| |
| /** next free service id. */ |
| /* @GuardedBy("this") */ |
| private long serviceid; |
| |
| /** Active Service Listeners. |
| * {@literal Map<BundleContextImpl,CopyOnWriteIdentityMap<ServiceListener,FilteredServiceListener>>}. |
| */ |
| /* @GuardedBy("serviceEventListeners") */ |
| private final Map<BundleContextImpl, CopyOnWriteIdentityMap<ServiceListener, FilteredServiceListener>> serviceEventListeners; |
| |
| /** initial capacity of the main data structure */ |
| private static final int initialCapacity = 50; |
| /** initial capacity of the nested data structure */ |
| private static final int initialSubCapacity = 10; |
| /** container which created this service registry */ |
| private final EquinoxContainer container; |
| private final BundleContextImpl systemBundleContext; |
| final Debug debug; |
| |
| /** |
| * Initializes the internal data structures of this ServiceRegistry. |
| * |
| */ |
| public ServiceRegistry(EquinoxContainer container) { |
| this.container = container; |
| this.debug = container.getConfiguration().getDebug(); |
| serviceid = 1; |
| publishedServicesByClass = new HashMap<>(initialCapacity); |
| publishedServicesByContext = new HashMap<>(initialCapacity); |
| allPublishedServices = new ArrayList<>(initialCapacity); |
| serviceEventListeners = new LinkedHashMap<>(initialCapacity); |
| Module systemModule = container.getStorage().getModuleContainer().getModule(0); |
| systemBundleContext = (BundleContextImpl) systemModule.getBundle().getBundleContext(); |
| systemBundleContext.provisionServicesInUseMap(); |
| } |
| |
| /** |
| * Registers the specified service object with the specified properties |
| * under the specified class names into the Framework. A |
| * <code>ServiceRegistrationImpl</code> object is returned. The |
| * <code>ServiceRegistrationImpl</code> object is for the private use of the |
| * bundle registering the service and should not be shared with other |
| * bundles. The registering bundle is defined to be the context bundle. |
| * Other bundles can locate the service by using either the |
| * {@link #getServiceReferences} or {@link #getServiceReference} method. |
| * |
| * <p> |
| * A bundle can register a service object that implements the |
| * {@link ServiceFactory} interface to have more flexibility in providing |
| * service objects to other bundles. |
| * |
| * <p> |
| * The following steps are required to register a service: |
| * <ol> |
| * <li>If <code>service</code> is not a <code>ServiceFactory</code>, |
| * an <code>IllegalArgumentException</code> is thrown if |
| * <code>service</code> is not an <code>instanceof</code> all the |
| * classes named. |
| * <li>The Framework adds these service properties to the specified |
| * <code>Dictionary</code> (which may be <code>null</code>): a property |
| * named {@link Constants#SERVICE_ID} identifying the registration number of |
| * the service and a property named {@link Constants#OBJECTCLASS} containing |
| * all the specified classes. If any of these properties have already been |
| * specified by the registering bundle, their values will be overwritten by |
| * the Framework. |
| * <li>The service is added to the Framework service registry and may now |
| * be used by other bundles. |
| * <li>A service event of type {@link ServiceEvent#REGISTERED} is fired. |
| * <li>A <code>ServiceRegistration</code> object for this registration is |
| * returned. |
| * </ol> |
| * |
| * @param context The BundleContext of the registering bundle. |
| * @param clazzes The class names under which the service can be located. |
| * The class names in this array will be stored in the service's |
| * properties under the key {@link Constants#OBJECTCLASS}. |
| * @param service The service object or a <code>ServiceFactory</code> |
| * object. |
| * @param properties The properties for this service. The keys in the |
| * properties object must all be <code>String</code> objects. See |
| * {@link Constants} for a list of standard service property keys. |
| * Changes should not be made to this object after calling this |
| * method. To update the service's properties the |
| * {@link ServiceRegistration#setProperties} method must be called. |
| * The set of properties may be <code>null</code> if the service |
| * has no properties. |
| * |
| * @return A <code>ServiceRegistrationImpl</code> object for use by the bundle |
| * registering the service to update the service's properties or to |
| * unregister the service. |
| * |
| * @throws java.lang.IllegalArgumentException If one of the following is |
| * true: |
| * <ul> |
| * <li><code>service</code> is <code>null</code>. |
| * <li><code>service</code> is not a <code>ServiceFactory</code> |
| * object and is not an instance of all the named classes in |
| * <code>clazzes</code>. |
| * <li><code>properties</code> contains case variants of the same |
| * key name. |
| * </ul> |
| * |
| * @throws java.lang.SecurityException If the caller does not have the |
| * <code>ServicePermission</code> to register the service for all |
| * the named classes and the Java Runtime Environment supports |
| * permissions. |
| * |
| * @throws java.lang.IllegalStateException If this BundleContext is no |
| * longer valid. |
| * |
| * @see ServiceRegistration |
| * @see ServiceFactory |
| */ |
| public ServiceRegistrationImpl<?> registerService(BundleContextImpl context, String[] clazzes, Object service, Dictionary<String, ?> properties) { |
| if (service == null) { |
| if (debug.DEBUG_SERVICES) { |
| Debug.println("Service object is null"); //$NON-NLS-1$ |
| } |
| |
| throw new IllegalArgumentException(Msg.SERVICE_ARGUMENT_NULL_EXCEPTION); |
| } |
| |
| int size = clazzes.length; |
| |
| if (size == 0) { |
| if (debug.DEBUG_SERVICES) { |
| Debug.println("Classes array is empty"); //$NON-NLS-1$ |
| } |
| |
| throw new IllegalArgumentException(Msg.SERVICE_EMPTY_CLASS_LIST_EXCEPTION); |
| } |
| |
| boolean isListenerHook = false; |
| boolean isFrameworkHook = false; |
| /* copy the array so that changes to the original will not affect us. */ |
| List<String> copy = new ArrayList<>(size); |
| // intern the strings and remove duplicates |
| for (int i = 0; i < size; i++) { |
| String clazz = clazzes[i].intern(); |
| if (!copy.contains(clazz)) { |
| isListenerHook = isListenerHook || listenerHookName.equals(clazz); |
| isFrameworkHook = isFrameworkHook || isFrameworkHook(clazz); |
| copy.add(clazz); |
| } |
| } |
| size = copy.size(); |
| clazzes = copy.toArray(new String[size]); |
| |
| /* check for ServicePermissions. */ |
| checkRegisterServicePermission(clazzes); |
| |
| if (!(service instanceof ServiceFactory<?>)) { |
| String invalidService = checkServiceClass(clazzes, service); |
| if (invalidService != null) { |
| if (debug.DEBUG_SERVICES) { |
| Debug.println("Service object is not an instanceof " + invalidService); //$NON-NLS-1$ |
| } |
| throw new IllegalArgumentException(NLS.bind(Msg.SERVICE_NOT_INSTANCEOF_CLASS_EXCEPTION, invalidService)); |
| } |
| } |
| |
| ServiceRegistrationImpl<?> registration = isFrameworkHook |
| ? new ServiceRegistrationImpl.FrameworkHookRegistration<>(this, context, clazzes, service, |
| systemBundleContext) |
| : new ServiceRegistrationImpl<>(this, context, clazzes, service); |
| registration.register(properties); |
| registration.initHookInstance(); |
| |
| if (isListenerHook) { |
| notifyNewListenerHook(registration); |
| } |
| return registration; |
| } |
| |
| private boolean isFrameworkHook(String className) { |
| switch (className) { |
| case "org.osgi.framework.hooks.bundle.CollisionHook": //$NON-NLS-1$ |
| return true; |
| case "org.osgi.framework.hooks.bundle.EventHook": //$NON-NLS-1$ |
| return true; |
| case "org.osgi.framework.hooks.bundle.FindHook": //$NON-NLS-1$ |
| return true; |
| case "org.osgi.framework.hooks.service.EventHook": //$NON-NLS-1$ |
| return true; |
| case "org.osgi.framework.hooks.service.EventListenerHook": //$NON-NLS-1$ |
| return true; |
| case "org.osgi.framework.hooks.service.FindHook": //$NON-NLS-1$ |
| return true; |
| case "org.osgi.framework.hooks.service.ListenerHook": //$NON-NLS-1$ |
| return true; |
| case "org.osgi.framework.hooks.weaving.WeavingHook": //$NON-NLS-1$ |
| return true; |
| case "org.osgi.framework.hooks.weaving.WovenClassListener": //$NON-NLS-1$ |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| /** |
| * Returns an array of <code>ServiceReferenceImpl</code> objects. The returned |
| * array of <code>ServiceReferenceImpl</code> objects contains services that |
| * were registered under the specified class, match the specified filter |
| * criteria, and the packages for the class names under which the services |
| * were registered match the context bundle's packages as defined in |
| * {@link ServiceReference#isAssignableTo(Bundle, String)}. |
| * |
| * <p> |
| * The list is valid at the time of the call to this method, however since |
| * the Framework is a very dynamic environment, services can be modified or |
| * unregistered at anytime. |
| * |
| * <p> |
| * <code>filter</code> is used to select the registered service whose |
| * properties objects contain keys and values which satisfy the filter. See |
| * {@link Filter} for a description of the filter string syntax. |
| * |
| * <p> |
| * If <code>filter</code> is <code>null</code>, all registered services |
| * are considered to match the filter. If <code>filter</code> cannot be |
| * parsed, an {@link InvalidSyntaxException} will be thrown with a human |
| * readable message where the filter became unparsable. |
| * |
| * <p> |
| * The following steps are required to select a set of |
| * <code>ServiceReferenceImpl</code> objects: |
| * <ol> |
| * <li>If the filter string is not <code>null</code>, the filter string |
| * is parsed and the set <code>ServiceReferenceImpl</code> objects of |
| * registered services that satisfy the filter is produced. If the filter |
| * string is <code>null</code>, then all registered services are |
| * considered to satisfy the filter. |
| * <li>If the Java Runtime Environment supports permissions, the set of |
| * <code>ServiceReferenceImpl</code> objects produced by the previous step is |
| * reduced by checking that the caller has the |
| * <code>ServicePermission</code> to get at least one of the class names |
| * under which the service was registered. If the caller does not have the |
| * correct permission for a particular <code>ServiceReferenceImpl</code> |
| * object, then it is removed from the set. |
| * <li>If <code>clazz</code> is not <code>null</code>, the set is |
| * further reduced to those services that are an <code>instanceof</code> |
| * and were registered under the specified class. The complete list of |
| * classes of which a service is an instance and which were specified when |
| * the service was registered is available from the service's |
| * {@link Constants#OBJECTCLASS} property. |
| * <li>The set is reduced one final time by cycling through each |
| * <code>ServiceReference</code> object and calling |
| * {@link ServiceReference#isAssignableTo(Bundle, String)} with the context |
| * bundle and each class name under which the <code>ServiceReference</code> |
| * object was registered. For any given <code>ServiceReferenceImpl</code> |
| * object, if any call to |
| * {@link ServiceReference#isAssignableTo(Bundle, String)} returns |
| * <code>false</code>, then it is removed from the set of |
| * <code>ServiceReferenceImpl</code> objects. |
| * <li>An array of the remaining <code>ServiceReferenceImpl</code> objects is |
| * returned. |
| * </ol> |
| * |
| * @param context The BundleContext of the requesting bundle. |
| * @param clazz The class name with which the service was registered or |
| * <code>null</code> for all services. |
| * @param filterstring The filter criteria. |
| * @param allservices True if the bundle called getAllServiceReferences. |
| * @return An array of <code>ServiceReferenceImpl</code> objects or |
| * <code>null</code> if no services are registered which satisfy |
| * the search. |
| * @throws InvalidSyntaxException If <code>filter</code> contains an |
| * invalid filter string that cannot be parsed. |
| * @throws java.lang.IllegalStateException If this BundleContext is no |
| * longer valid. |
| */ |
| public ServiceReferenceImpl<?>[] getServiceReferences(final BundleContextImpl context, final String clazz, final String filterstring, final boolean allservices) throws InvalidSyntaxException { |
| if (debug.DEBUG_SERVICES) { |
| Debug.println((allservices ? "getAllServiceReferences(" : "getServiceReferences(") + clazz + ", \"" + filterstring + "\")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ |
| } |
| Filter filter = (filterstring == null) ? null : context.createFilter(filterstring); |
| List<ServiceRegistrationImpl<?>> registrations = lookupServiceRegistrations(clazz, filter); |
| List<ServiceReferenceImpl<?>> references = new ArrayList<>(registrations.size()); |
| for (ServiceRegistrationImpl<?> registration : registrations) { |
| ServiceReferenceImpl<?> reference; |
| try { |
| reference = registration.getReferenceImpl(); |
| } catch (IllegalStateException e) { |
| continue; // got unregistered, don't return reference |
| } |
| if (allservices || isAssignableTo(context, clazz, reference)) { |
| try { /* test for permission to get the service */ |
| checkGetServicePermission(reference); |
| } catch (SecurityException se) { |
| continue; // don't return reference |
| } |
| } else { |
| continue; // don't return reference |
| } |
| references.add(reference); |
| } |
| |
| Collection<ServiceReferenceImpl<?>> copyReferences = references; |
| if (context.getBundleImpl().getBundleId() == 0) { |
| // Make a copy for the purposes of calling the hooks; |
| // The the removals from the hooks are ignored for the system bundle |
| copyReferences = new ArrayList<>(references); |
| } |
| Collection<ServiceReference<?>> shrinkable = new ShrinkableCollection<>(copyReferences); |
| notifyFindHooks(context, clazz, filterstring, allservices, shrinkable); |
| |
| int size = references.size(); |
| if (size == 0) { |
| return null; |
| } |
| return references.toArray(new ServiceReferenceImpl[size]); |
| } |
| |
| /** |
| * Returns a <code>ServiceReference</code> object for a service that |
| * implements and was registered under the specified class. |
| * |
| * <p> |
| * This <code>ServiceReference</code> object is valid at the time of the |
| * call to this method, however as the Framework is a very dynamic |
| * environment, services can be modified or unregistered at anytime. |
| * |
| * <p> |
| * This method is the same as calling |
| * {@link BundleContext#getServiceReferences(String, String)} with a |
| * <code>null</code> filter string. It is provided as a convenience for |
| * when the caller is interested in any service that implements the |
| * specified class. |
| * <p> |
| * If multiple such services exist, the service with the highest ranking (as |
| * specified in its {@link Constants#SERVICE_RANKING} property) is returned. |
| * <p> |
| * If there is a tie in ranking, the service with the lowest service ID (as |
| * specified in its {@link Constants#SERVICE_ID} property); that is, the |
| * service that was registered first is returned. |
| * |
| * @param context The BundleContext of the requesting bundle. |
| * @param clazz The class name with which the service was registered. |
| * @return A <code>ServiceReference</code> object, or <code>null</code> |
| * if no services are registered which implement the named class. |
| * @throws java.lang.IllegalStateException If this BundleContext is no |
| * longer valid. |
| */ |
| public ServiceReferenceImpl<?> getServiceReference(BundleContextImpl context, String clazz) { |
| if (debug.DEBUG_SERVICES) { |
| Debug.println("getServiceReference(" + clazz + ")"); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| |
| try { |
| ServiceReferenceImpl<?>[] references = getServiceReferences(context, clazz, null, false); |
| |
| if (references != null) { |
| // Since we maintain the registrations in a sorted List, the first element is always the |
| // correct one to return. |
| return references[0]; |
| } |
| } catch (InvalidSyntaxException e) { |
| if (debug.DEBUG_GENERAL) { |
| Debug.println("InvalidSyntaxException w/ null filter" + e.getMessage()); //$NON-NLS-1$ |
| Debug.printStackTrace(e); |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Returns the specified service object for a service. |
| * <p> |
| * A bundle's use of a service is tracked by the bundle's use count of that |
| * service. Each time a service's service object is returned by |
| * {@link #getService(BundleContextImpl, ServiceReferenceImpl)} the context bundle's use count for |
| * that service is incremented by one. Each time the service is released by |
| * {@link #ungetService(BundleContextImpl, ServiceReferenceImpl)} the context bundle's use count |
| * for that service is decremented by one. |
| * <p> |
| * When a bundle's use count for a service drops to zero, the bundle should |
| * no longer use that service. |
| * |
| * <p> |
| * This method will always return <code>null</code> when the service |
| * associated with this <code>reference</code> has been unregistered. |
| * |
| * <p> |
| * The following steps are required to get the service object: |
| * <ol> |
| * <li>If the service has been unregistered, <code>null</code> is |
| * returned. |
| * <li>The context bundle's use count for this service is incremented by |
| * one. |
| * <li>If the context bundle's use count for the service is currently one |
| * and the service was registered with an object implementing the |
| * <code>ServiceFactory</code> interface, the |
| * {@link ServiceFactory#getService(Bundle, ServiceRegistration)} method is |
| * called to create a service object for the context bundle. This service |
| * object is cached by the Framework. While the context bundle's use count |
| * for the service is greater than zero, subsequent calls to get the |
| * services's service object for the context bundle will return the cached |
| * service object. <br> |
| * If the service object returned by the <code>ServiceFactory</code> |
| * object is not an <code>instanceof</code> all the classes named when the |
| * service was registered or the <code>ServiceFactory</code> object throws |
| * an exception, <code>null</code> is returned and a Framework event of |
| * type {@link FrameworkEvent#ERROR} containing a {@link ServiceException} |
| * describing the error is fired. |
| * <li>The service object for the service is returned. |
| * </ol> |
| * |
| * @param context The BundleContext of the requesting bundle. |
| * @param reference A reference to the service. |
| * @return A service object for the service associated with |
| * <code>reference</code> or <code>null</code> if the service is |
| * not registered, the service object returned by a |
| * <code>ServiceFactory</code> does not implement the classes |
| * under which it was registered or the <code>ServiceFactory</code> |
| * threw an exception. |
| * @throws java.lang.SecurityException If the caller does not have the |
| * <code>ServicePermission</code> to get the service using at |
| * least one of the named classes the service was registered under |
| * and the Java Runtime Environment supports permissions. |
| * @throws java.lang.IllegalStateException If this BundleContext is no |
| * longer valid. |
| * @see #ungetService(BundleContextImpl, ServiceReferenceImpl) |
| * @see ServiceFactory |
| */ |
| public <S> S getService(BundleContextImpl context, ServiceReferenceImpl<S> reference) { |
| /* test for permission to get the service */ |
| checkGetServicePermission(reference); |
| return reference.getRegistration().getService(context, ServiceConsumer.singletonConsumer); |
| } |
| |
| /** |
| * Returns the {@link ServiceObjects} object for the service referenced by |
| * the specified {@code ServiceReference} object. |
| * |
| * <p> |
| * The {@link ServiceObjects} object can be used to obtain multiple |
| * service objects for services with {@link Constants#SCOPE_PROTOTYPE |
| * prototype} scope. For services with {@link Constants#SCOPE_SINGLETON |
| * singleton} or {@link Constants#SCOPE_BUNDLE bundle} scope, the |
| * {@link ServiceObjects#getService()} method behaves the same as the |
| * {@link BundleContext#getService(ServiceReference)} method and the |
| * {@link ServiceObjects#ungetService(Object)} method behaves the same as |
| * the {@link BundleContext#ungetService(ServiceReference)} method. That is, only one, |
| * use-counted service object is available from the {@link ServiceObjects} |
| * object. |
| * |
| * <p> |
| * This method will always return {@code null} when the service associated |
| * with the specified {@code reference} has been unregistered. |
| * |
| * @param <S> Type of Service. |
| * @param context The BundleContext of the requesting bundle. |
| * @param reference A reference to the service. |
| * @return A {@link ServiceObjects} object for the service associated with |
| * the specified {@code reference} or {@code null} if the service is |
| * not registered. |
| * @throws SecurityException If the caller does not have the |
| * {@code ServicePermission} to get the service using at least one |
| * of the named classes the service was registered under and the |
| * Java Runtime Environment supports permissions. |
| */ |
| public <S> ServiceObjectsImpl<S> getServiceObjects(BundleContextImpl context, ServiceReferenceImpl<S> reference) { |
| checkGetServicePermission(reference); |
| return reference.getRegistration().getServiceObjects(context); |
| } |
| |
| /** |
| * Releases the service object referenced by the specified |
| * <code>ServiceReference</code> object. If the context bundle's use count |
| * for the service is zero, this method returns <code>false</code>. |
| * Otherwise, the context bundle's use count for the service is decremented |
| * by one. |
| * |
| * <p> |
| * The service's service object should no longer be used and all references |
| * to it should be destroyed when a bundle's use count for the service drops |
| * to zero. |
| * |
| * <p> |
| * The following steps are required to unget the service object: |
| * <ol> |
| * <li>If the context bundle's use count for the service is zero or the |
| * service has been unregistered, <code>false</code> is returned. |
| * <li>The context bundle's use count for this service is decremented by |
| * one. |
| * <li>If the context bundle's use count for the service is currently zero |
| * and the service was registered with a <code>ServiceFactory</code> |
| * object, the |
| * {@link ServiceFactory#ungetService(Bundle, ServiceRegistration, Object)} |
| * method is called to release the service object for the context bundle. |
| * <li><code>true</code> is returned. |
| * </ol> |
| * |
| * @param context The BundleContext of the requesting bundle. |
| * @param reference A reference to the service to be released. |
| * @return <code>false</code> if the context bundle's use count for the |
| * service is zero or if the service has been unregistered; |
| * <code>true</code> otherwise. |
| * @throws java.lang.IllegalStateException If this BundleContext is no |
| * longer valid. |
| * @see #getService |
| * @see ServiceFactory |
| */ |
| public boolean ungetService(BundleContextImpl context, ServiceReferenceImpl<?> reference) { |
| ServiceRegistrationImpl<?> registration = reference.getRegistration(); |
| |
| return registration.ungetService(context, ServiceConsumer.singletonConsumer, null); |
| } |
| |
| /** |
| * Returns this bundle's <code>ServiceReference</code> list for all |
| * services it has registered or <code>null</code> if this bundle has no |
| * registered services. |
| * |
| * <p> |
| * If the Java runtime supports permissions, a <code>ServiceReference</code> |
| * object to a service is included in the returned list only if the caller |
| * has the <code>ServicePermission</code> to get the service using at |
| * least one of the named classes the service was registered under. |
| * |
| * <p> |
| * The list is valid at the time of the call to this method, however, as the |
| * Framework is a very dynamic environment, services can be modified or |
| * unregistered at anytime. |
| * |
| * @param context The BundleContext of the requesting bundle. |
| * @return An array of <code>ServiceReference</code> objects or |
| * <code>null</code>. |
| * @throws java.lang.IllegalStateException If this bundle has been |
| * uninstalled. |
| * @see ServiceRegistration |
| * @see ServiceReference |
| * @see ServicePermission |
| */ |
| public ServiceReferenceImpl<?>[] getRegisteredServices(BundleContextImpl context) { |
| List<ServiceRegistrationImpl<?>> registrations = lookupServiceRegistrations(context); |
| List<ServiceReferenceImpl<?>> references = new ArrayList<>(registrations.size()); |
| for (ServiceRegistrationImpl<?> registration : registrations) { |
| ServiceReferenceImpl<?> reference; |
| try { |
| reference = registration.getReferenceImpl(); |
| } catch (IllegalStateException e) { |
| continue; // got unregistered, don't return reference |
| } |
| try { |
| /* test for permission to get the service */ |
| checkGetServicePermission(reference); |
| } catch (SecurityException se) { |
| continue; // don't return reference |
| } |
| references.add(reference); |
| } |
| |
| int size = references.size(); |
| if (size == 0) { |
| return null; |
| } |
| return references.toArray(new ServiceReferenceImpl[size]); |
| } |
| |
| /** |
| * Returns this bundle's <code>ServiceReference</code> list for all |
| * services it is using or returns <code>null</code> if this bundle is not |
| * using any services. A bundle is considered to be using a service if its |
| * use count for that service is greater than zero. |
| * |
| * <p> |
| * If the Java Runtime Environment supports permissions, a |
| * <code>ServiceReference</code> object to a service is included in the |
| * returned list only if the caller has the <code>ServicePermission</code> |
| * to get the service using at least one of the named classes the service |
| * was registered under. |
| * <p> |
| * The list is valid at the time of the call to this method, however, as the |
| * Framework is a very dynamic environment, services can be modified or |
| * unregistered at anytime. |
| * |
| * @param context The BundleContext of the requesting bundle. |
| * @return An array of <code>ServiceReference</code> objects or |
| * <code>null</code>. |
| * @throws java.lang.IllegalStateException If this bundle has been |
| * uninstalled. |
| * @see ServiceReference |
| * @see ServicePermission |
| */ |
| public ServiceReferenceImpl<?>[] getServicesInUse(BundleContextImpl context) { |
| Map<ServiceRegistrationImpl<?>, ServiceUse<?>> servicesInUse = context.getServicesInUseMap(); |
| if (servicesInUse == null) { |
| return null; |
| } |
| |
| List<ServiceRegistrationImpl<?>> registrations; |
| synchronized (servicesInUse) { |
| if (servicesInUse.isEmpty()) { |
| return null; |
| } |
| registrations = new ArrayList<>(servicesInUse.keySet()); |
| } |
| List<ServiceReferenceImpl<?>> references = new ArrayList<>(registrations.size()); |
| for (ServiceRegistrationImpl<?> registration : registrations) { |
| ServiceReferenceImpl<?> reference; |
| try { |
| reference = registration.getReferenceImpl(); |
| } catch (IllegalStateException e) { |
| continue; // got unregistered, don't return reference |
| } |
| try { |
| /* test for permission to get the service */ |
| checkGetServicePermission(reference); |
| } catch (SecurityException se) { |
| continue; // don't return reference |
| } |
| references.add(reference); |
| } |
| |
| int size = references.size(); |
| if (size == 0) { |
| return null; |
| } |
| return references.toArray(new ServiceReferenceImpl[size]); |
| } |
| |
| /** |
| * Called when the BundleContext is closing to unregister all services |
| * currently registered by the bundle. |
| * |
| * @param context The BundleContext of the closing bundle. |
| */ |
| public void unregisterServices(BundleContextImpl context) { |
| for (ServiceRegistrationImpl<?> registration : lookupServiceRegistrations(context)) { |
| try { |
| registration.unregister(); |
| } catch (IllegalStateException e) { |
| /* already unregistered */ |
| } |
| } |
| removeServiceRegistrations(context); // remove empty list |
| } |
| |
| /** |
| * Called when the BundleContext is closing to unget all services |
| * currently used by the bundle. |
| * |
| * @param context The BundleContext of the closing bundle. |
| */ |
| public void releaseServicesInUse(BundleContextImpl context) { |
| Map<ServiceRegistrationImpl<?>, ServiceUse<?>> servicesInUse = context.getServicesInUseMap(); |
| if (servicesInUse == null) { |
| return; |
| } |
| List<ServiceRegistrationImpl<?>> registrations; |
| synchronized (servicesInUse) { |
| if (servicesInUse.isEmpty()) { |
| return; |
| } |
| registrations = new ArrayList<>(servicesInUse.keySet()); |
| } |
| if (debug.DEBUG_SERVICES) { |
| Debug.println("Releasing services"); //$NON-NLS-1$ |
| } |
| for (ServiceRegistrationImpl<?> registration : registrations) { |
| registration.releaseService(context); |
| } |
| } |
| |
| /** |
| * Add a new Service Listener for a bundle. |
| * |
| * @param context Context of bundle adding listener. |
| * @param listener Service Listener to be added. |
| * @param filter Filter string for listener or null. |
| * @throws InvalidSyntaxException If the filter string is invalid. |
| */ |
| public void addServiceListener(BundleContextImpl context, ServiceListener listener, String filter) throws InvalidSyntaxException { |
| if (debug.DEBUG_EVENTS) { |
| String listenerName = listener.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(listener)); //$NON-NLS-1$ |
| Debug.println("addServiceListener[" + context.getBundleImpl() + "](" + listenerName + ", \"" + filter + "\")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ |
| } |
| |
| FilteredServiceListener filteredListener = new FilteredServiceListener(context, listener, filter); |
| FilteredServiceListener oldFilteredListener; |
| synchronized (serviceEventListeners) { |
| CopyOnWriteIdentityMap<ServiceListener, FilteredServiceListener> listeners = serviceEventListeners.get(context); |
| if (listeners == null) { |
| listeners = new CopyOnWriteIdentityMap<>(); |
| serviceEventListeners.put(context, listeners); |
| } |
| oldFilteredListener = listeners.put(listener, filteredListener); |
| } |
| |
| if (oldFilteredListener != null) { |
| oldFilteredListener.markRemoved(); |
| Collection<ListenerInfo> removedListeners = Collections.<ListenerInfo> singletonList(oldFilteredListener); |
| notifyListenerHooks(removedListeners, false); |
| } |
| |
| Collection<ListenerInfo> addedListeners = Collections.<ListenerInfo> singletonList(filteredListener); |
| notifyListenerHooks(addedListeners, true); |
| } |
| |
| /** |
| * Remove a Service Listener for a bundle. |
| * |
| * @param context Context of bundle removing listener. |
| * @param listener Service Listener to be removed. |
| */ |
| public void removeServiceListener(BundleContextImpl context, ServiceListener listener) { |
| if (debug.DEBUG_EVENTS) { |
| String listenerName = listener.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(listener)); //$NON-NLS-1$ |
| Debug.println("removeServiceListener[" + context.getBundleImpl() + "](" + listenerName + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| } |
| |
| FilteredServiceListener oldFilteredListener; |
| synchronized (serviceEventListeners) { |
| Map<ServiceListener, FilteredServiceListener> listeners = serviceEventListeners.get(context); |
| if (listeners == null) { |
| return; // this context has no listeners to begin with |
| } |
| oldFilteredListener = listeners.remove(listener); |
| } |
| |
| if (oldFilteredListener == null) { |
| return; |
| } |
| oldFilteredListener.markRemoved(); |
| Collection<ListenerInfo> removedListeners = Collections.<ListenerInfo> singletonList(oldFilteredListener); |
| notifyListenerHooks(removedListeners, false); |
| } |
| |
| /** |
| * Remove all Service Listener for a bundle. |
| * |
| * @param context Context of bundle removing all listeners. |
| */ |
| public void removeAllServiceListeners(BundleContextImpl context) { |
| Map<ServiceListener, FilteredServiceListener> removedListenersMap; |
| synchronized (serviceEventListeners) { |
| removedListenersMap = serviceEventListeners.remove(context); |
| } |
| if ((removedListenersMap == null) || removedListenersMap.isEmpty()) { |
| return; |
| } |
| Collection<FilteredServiceListener> removedListeners = removedListenersMap.values(); |
| for (FilteredServiceListener oldFilteredListener : removedListeners) { |
| oldFilteredListener.markRemoved(); |
| } |
| notifyListenerHooks(asListenerInfos(removedListeners), false); |
| } |
| |
| /** |
| * Coerce the generic type of a collection from Collection<FilteredServiceListener> |
| * to Collection<ListenerInfo> |
| * @param c Collection to be coerced. |
| * @return c coerced to Collection<ListenerInfo> |
| */ |
| @SuppressWarnings("unchecked") |
| private static Collection<ListenerInfo> asListenerInfos(Collection<? extends ListenerInfo> c) { |
| return (Collection<ListenerInfo>) c; |
| } |
| |
| /** |
| * Deliver a ServiceEvent. |
| * |
| * @param event The ServiceEvent to deliver. |
| */ |
| public void publishServiceEvent(final ServiceEvent event) { |
| if (System.getSecurityManager() == null) { |
| publishServiceEventPrivileged(event); |
| } else { |
| AccessController.doPrivileged(new PrivilegedAction<Void>() { |
| @Override |
| public Void run() { |
| publishServiceEventPrivileged(event); |
| return null; |
| } |
| }); |
| } |
| } |
| |
| void publishServiceEventPrivileged(final ServiceEvent event) { |
| /* Build the listener snapshot */ |
| Map<BundleContextImpl, Set<Map.Entry<ServiceListener, FilteredServiceListener>>> listenerSnapshot; |
| Set<Map.Entry<ServiceListener, FilteredServiceListener>> systemServiceListenersOrig = null; |
| BundleContextImpl systemContext = null; |
| synchronized (serviceEventListeners) { |
| listenerSnapshot = new LinkedHashMap<>(serviceEventListeners.size()); |
| for (Map.Entry<BundleContextImpl, CopyOnWriteIdentityMap<ServiceListener, FilteredServiceListener>> entry : serviceEventListeners.entrySet()) { |
| Map<ServiceListener, FilteredServiceListener> listeners = entry.getValue(); |
| if (!listeners.isEmpty()) { |
| if (entry.getKey().getBundleImpl().getBundleId() == 0) { |
| systemContext = entry.getKey(); |
| // make a copy that we can use to discard hook removals later |
| systemServiceListenersOrig = listeners.entrySet(); |
| } |
| listenerSnapshot.put(entry.getKey(), listeners.entrySet()); |
| } |
| } |
| } |
| |
| /* shrink the snapshot. |
| * keySet returns a Collection which cannot be added to and |
| * removals from that collection will result in removals of the |
| * entry from the snapshot. |
| */ |
| Collection<BundleContext> contexts = asBundleContexts(listenerSnapshot.keySet()); |
| notifyEventHooksPrivileged(event, contexts); |
| if (!listenerSnapshot.isEmpty()) { |
| Map<BundleContext, Collection<ListenerInfo>> listeners = new ShrinkableValueCollectionMap<>( |
| listenerSnapshot); |
| notifyEventListenerHooksPrivileged(event, listeners); |
| } |
| // always add back the system service listeners if they were removed |
| if (systemServiceListenersOrig != null) { |
| // No contains key check is done because hooks may have removed |
| // a single listener from the value instead of the whole context key. |
| // It is more simple to just replace with the original snapshot. |
| listenerSnapshot.put(systemContext, systemServiceListenersOrig); |
| } |
| if (listenerSnapshot.isEmpty()) { |
| return; |
| } |
| |
| /* deliver the event to the snapshot */ |
| ListenerQueue<ServiceListener, FilteredServiceListener, ServiceEvent> queue = container.newListenerQueue(); |
| for (Map.Entry<BundleContextImpl, Set<Map.Entry<ServiceListener, FilteredServiceListener>>> entry : listenerSnapshot.entrySet()) { |
| @SuppressWarnings({"unchecked", "rawtypes"}) |
| EventDispatcher<ServiceListener, FilteredServiceListener, ServiceEvent> dispatcher = (EventDispatcher) entry.getKey(); |
| Set<Map.Entry<ServiceListener, FilteredServiceListener>> listenerSet = entry.getValue(); |
| queue.queueListeners(listenerSet, dispatcher); |
| } |
| queue.dispatchEventSynchronous(SERVICEEVENT, event); |
| } |
| |
| /** |
| * Coerce the generic type of a collection from Collection<BundleContextImpl> |
| * to Collection<BundleContext> |
| * @param c Collection to be coerced. |
| * @return c coerced to Collection<BundleContext> |
| */ |
| @SuppressWarnings("unchecked") |
| private static Collection<BundleContext> asBundleContexts(Collection<? extends BundleContext> c) { |
| return (Collection<BundleContext>) c; |
| } |
| |
| /** |
| * Return the next available service id. |
| * |
| * @return next service id. |
| */ |
| synchronized long getNextServiceId() { |
| long id = serviceid; |
| serviceid = id + 1; |
| return id; |
| } |
| |
| /** |
| * Add the ServiceRegistrationImpl to the data structure. |
| * |
| * @param context The BundleContext of the bundle registering the service. |
| * @param registration The new ServiceRegistration. |
| */ |
| /* @GuardedBy("this") */ |
| void addServiceRegistration(BundleContextImpl context, ServiceRegistrationImpl<?> registration) { |
| assert Thread.holdsLock(this); |
| // Add the ServiceRegistrationImpl to the list of Services published by BundleContextImpl. |
| List<ServiceRegistrationImpl<?>> contextServices = publishedServicesByContext.get(context); |
| if (contextServices == null) { |
| contextServices = new ArrayList<>(initialSubCapacity); |
| publishedServicesByContext.put(context, contextServices); |
| } |
| // The list is NOT sorted, so we just add |
| contextServices.add(registration); |
| |
| // Add the ServiceRegistrationImpl to the list of Services published by Class Name. |
| int insertIndex; |
| for (String clazz : registration.getClasses()) { |
| List<ServiceRegistrationImpl<?>> services = publishedServicesByClass.get(clazz); |
| |
| if (services == null) { |
| services = new ArrayList<>(initialSubCapacity); |
| publishedServicesByClass.put(clazz, services); |
| } |
| |
| // The list is sorted, so we must find the proper location to insert |
| insertIndex = -Collections.binarySearch(services, registration) - 1; |
| services.add(insertIndex, registration); |
| } |
| |
| // Add the ServiceRegistrationImpl to the list of all published Services. |
| // The list is sorted, so we must find the proper location to insert |
| insertIndex = -Collections.binarySearch(allPublishedServices, registration) - 1; |
| allPublishedServices.add(insertIndex, registration); |
| } |
| |
| /** |
| * Modify the ServiceRegistrationImpl in the data structure. |
| * |
| * @param context The BundleContext of the bundle registering the service. |
| * @param registration The modified ServiceRegistration. |
| */ |
| /* @GuardedBy("this") */ |
| void modifyServiceRegistration(BundleContextImpl context, ServiceRegistrationImpl<?> registration) { |
| assert Thread.holdsLock(this); |
| // The list of Services published by BundleContextImpl is not sorted, so |
| // we do not need to modify it. |
| |
| // Remove the ServiceRegistrationImpl from the list of Services published by Class Name |
| // and then add at the correct index. |
| int insertIndex; |
| for (String clazz : registration.getClasses()) { |
| List<ServiceRegistrationImpl<?>> services = publishedServicesByClass.get(clazz); |
| services.remove(registration); |
| // The list is sorted, so we must find the proper location to insert |
| insertIndex = -Collections.binarySearch(services, registration) - 1; |
| services.add(insertIndex, registration); |
| } |
| |
| // Remove the ServiceRegistrationImpl from the list of all published Services |
| // and then add at the correct index. |
| allPublishedServices.remove(registration); |
| // The list is sorted, so we must find the proper location to insert |
| insertIndex = -Collections.binarySearch(allPublishedServices, registration) - 1; |
| allPublishedServices.add(insertIndex, registration); |
| } |
| |
| /** |
| * Remove the ServiceRegistrationImpl from the data structure. |
| * |
| * @param context The BundleContext of the bundle registering the service. |
| * @param registration The ServiceRegistration to remove. |
| */ |
| /* @GuardedBy("this") */ |
| void removeServiceRegistration(BundleContextImpl context, ServiceRegistrationImpl<?> registration) { |
| assert Thread.holdsLock(this); |
| // Remove the ServiceRegistrationImpl from the list of Services published by BundleContextImpl. |
| List<ServiceRegistrationImpl<?>> contextServices = publishedServicesByContext.get(context); |
| if (contextServices != null) { |
| contextServices.remove(registration); |
| } |
| |
| // Remove the ServiceRegistrationImpl from the list of Services published by Class Name. |
| for (String clazz : registration.getClasses()) { |
| List<ServiceRegistrationImpl<?>> services = publishedServicesByClass.get(clazz); |
| services.remove(registration); |
| if (services.isEmpty()) { // remove empty list |
| publishedServicesByClass.remove(clazz); |
| } |
| } |
| |
| // Remove the ServiceRegistrationImpl from the list of all published Services. |
| allPublishedServices.remove(registration); |
| } |
| |
| /** |
| * Lookup Service Registrations in the data structure by class name and filter. |
| * |
| * @param clazz The class name with which the service was registered or |
| * <code>null</code> for all services. |
| * @param filter The filter criteria. |
| * @return List<ServiceRegistrationImpl> |
| */ |
| private List<ServiceRegistrationImpl<?>> lookupServiceRegistrations(String clazz, Filter filter) { |
| List<ServiceRegistrationImpl<?>> result; |
| synchronized (this) { |
| if (clazz == null) { /* all services */ |
| result = allPublishedServices; |
| } else { |
| /* services registered under the class name */ |
| result = publishedServicesByClass.get(clazz); |
| } |
| |
| if ((result == null) || result.isEmpty()) { |
| List<ServiceRegistrationImpl<?>> empty = Collections.<ServiceRegistrationImpl<?>> emptyList(); |
| return empty; |
| } |
| |
| result = new LinkedList<>(result); /* make a new list since we don't want to change the real list */ |
| } |
| |
| if (filter == null) { |
| return result; |
| } |
| |
| for (Iterator<ServiceRegistrationImpl<?>> iter = result.iterator(); iter.hasNext();) { |
| ServiceRegistrationImpl<?> registration = iter.next(); |
| ServiceReferenceImpl<?> reference; |
| try { |
| reference = registration.getReferenceImpl(); |
| } catch (IllegalStateException e) { |
| iter.remove(); /* service was unregistered after we left the synchronized block above */ |
| continue; |
| } |
| if (!filter.match(reference)) { |
| iter.remove(); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Lookup Service Registrations in the data structure by BundleContext. |
| * |
| * @param context The BundleContext for which to return Service Registrations. |
| * @return List<ServiceRegistrationImpl> |
| */ |
| private synchronized List<ServiceRegistrationImpl<?>> lookupServiceRegistrations(BundleContextImpl context) { |
| List<ServiceRegistrationImpl<?>> result = publishedServicesByContext.get(context); |
| |
| if ((result == null) || result.isEmpty()) { |
| List<ServiceRegistrationImpl<?>> empty = Collections.<ServiceRegistrationImpl<?>> emptyList(); |
| return empty; |
| } |
| |
| return new ArrayList<>(result); /* make a new list since we don't want to change the real list */ |
| } |
| |
| /** |
| * Remove Service Registrations in the data structure by BundleContext. |
| * |
| * @param context The BundleContext for which to remove Service Registrations. |
| */ |
| private synchronized void removeServiceRegistrations(BundleContextImpl context) { |
| publishedServicesByContext.remove(context); |
| } |
| |
| /** |
| * Check for permission to register a service. |
| * |
| * The caller must have permission for ALL names. |
| */ |
| private static void checkRegisterServicePermission(String[] names) { |
| SecurityManager sm = System.getSecurityManager(); |
| if (sm == null) { |
| return; |
| } |
| for (int i = 0, len = names.length; i < len; i++) { |
| sm.checkPermission(new ServicePermission(names[i], ServicePermission.REGISTER)); |
| } |
| } |
| |
| /** |
| * Check for permission to get a service. |
| */ |
| private static void checkGetServicePermission(ServiceReference<?> reference) { |
| SecurityManager sm = System.getSecurityManager(); |
| if (sm == null) { |
| return; |
| } |
| sm.checkPermission(new ServicePermission(reference, ServicePermission.GET)); |
| } |
| |
| /** |
| * Check for permission to listen to a service. |
| */ |
| static boolean hasListenServicePermission(ServiceEvent event, BundleContextImpl context) { |
| ModuleRevision revision = context.getBundleImpl().getModule().getCurrentRevision(); |
| if (revision == null) { |
| return false; |
| } |
| ProtectionDomain domain = ((Generation) revision.getRevisionInfo()).getDomain(); |
| if (domain == null) { |
| return true; |
| } |
| |
| return domain.implies(new ServicePermission(event.getServiceReference(), ServicePermission.GET)); |
| } |
| |
| /** |
| * Return the name of the class that is not satisfied by the service object. |
| * @param clazzes Array of class names. |
| * @param serviceObject Service object. |
| * @return The name of the class that is not satisfied by the service object. |
| */ |
| static String checkServiceClass(final String[] clazzes, final Object serviceObject) { |
| ClassLoader cl = AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() { |
| @Override |
| public ClassLoader run() { |
| return serviceObject.getClass().getClassLoader(); |
| } |
| }); |
| for (int i = 0, len = clazzes.length; i < len; i++) { |
| try { |
| Class<?> serviceClazz = cl == null ? Class.forName(clazzes[i]) : cl.loadClass(clazzes[i]); |
| if (!serviceClazz.isInstance(serviceObject)) |
| return clazzes[i]; |
| } catch (ClassNotFoundException e) { |
| //This check is rarely done |
| if (extensiveCheckServiceClass(clazzes[i], serviceObject.getClass())) |
| return clazzes[i]; |
| } |
| } |
| return null; |
| } |
| |
| private static boolean extensiveCheckServiceClass(String clazz, Class<?> serviceClazz) { |
| if (clazz.equals(serviceClazz.getName())) |
| return false; |
| Class<?>[] interfaces = serviceClazz.getInterfaces(); |
| for (int i = 0, len = interfaces.length; i < len; i++) |
| if (!extensiveCheckServiceClass(clazz, interfaces[i])) |
| return false; |
| Class<?> superClazz = serviceClazz.getSuperclass(); |
| if (superClazz != null) |
| if (!extensiveCheckServiceClass(clazz, superClazz)) |
| return false; |
| return true; |
| } |
| |
| static boolean isAssignableTo(BundleContextImpl context, String clazz, ServiceReferenceImpl<?> reference) { |
| Bundle bundle = context.getBundleImpl(); |
| String[] clazzes = reference.getClasses(); |
| for (int i = 0, len = clazzes.length; i < len; i++) |
| if (!reference.getRegistration().isAssignableTo(bundle, clazzes[i], clazzes[i] == clazz)) |
| return false; |
| return true; |
| } |
| |
| /** |
| * Call the registered FindHook services to allow them to inspect and possibly shrink the result. |
| * The FindHook must be called in order: descending by service.ranking, then ascending by service.id. |
| * This is the natural order for ServiceReference. |
| * |
| * @param context The context of the bundle getting the service references. |
| * @param clazz The class name used to search for the service references. |
| * @param filterstring The filter used to search for the service references. |
| * @param allservices True if getAllServiceReferences called. |
| * @param result The result to return to the caller which may have been shrunk by the FindHooks. |
| */ |
| private void notifyFindHooks(final BundleContextImpl context, final String clazz, final String filterstring, final boolean allservices, final Collection<ServiceReference<?>> result) { |
| if (System.getSecurityManager() == null) { |
| notifyFindHooksPrivileged(context, clazz, filterstring, allservices, result); |
| } else { |
| AccessController.doPrivileged(new PrivilegedAction<Void>() { |
| @Override |
| public Void run() { |
| notifyFindHooksPrivileged(context, clazz, filterstring, allservices, result); |
| return null; |
| } |
| }); |
| } |
| } |
| |
| void notifyFindHooksPrivileged(final BundleContextImpl context, final String clazz, final String filterstring, final boolean allservices, final Collection<ServiceReference<?>> result) { |
| if (debug.DEBUG_HOOKS) { |
| Debug.println("notifyServiceFindHooks(" + context.getBundleImpl() + "," + clazz + "," + filterstring + "," + allservices + "," + result + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ |
| } |
| notifyHooksPrivileged(findHookName, "find", (hook, hookRegistration) -> { //$NON-NLS-1$ |
| if (hook instanceof FindHook) { |
| ((FindHook) hook).find(context, clazz, filterstring, allservices, result); |
| } |
| }); |
| } |
| |
| /** |
| * Call the registered EventHook services to allow them to inspect and possibly shrink the result. |
| * The EventHooks must be called in order: descending by service.ranking, then ascending by service.id. |
| * This is the natural order for ServiceReference. |
| * |
| * @param event The service event to be delivered. |
| * @param result The result to return to the caller which may have been shrunk by the EventHooks. |
| */ |
| @SuppressWarnings("deprecation") |
| private void notifyEventHooksPrivileged(final ServiceEvent event, final Collection<BundleContext> result) { |
| if (debug.DEBUG_HOOKS) { |
| Debug.println("notifyServiceEventHooks(" + event.getType() + ":" + event.getServiceReference() + "," + result + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ |
| } |
| notifyHooksPrivileged(eventHookName, "event", (hook, hookRegistration) -> { //$NON-NLS-1$ |
| if (hook instanceof EventHook) { |
| ((EventHook) hook).event(event, result); |
| } |
| }); |
| } |
| |
| /** |
| * Call the registered EventListenerHook services to allow them to inspect and possibly shrink the result. |
| * The EventListenerHooks must be called in order: descending by service.ranking, then ascending by service.id. |
| * This is the natural order for ServiceReference. |
| * |
| * @param event The service event to be delivered. |
| * @param result The result to return to the caller which may have been shrunk by the EventListenerHooks. |
| */ |
| private void notifyEventListenerHooksPrivileged(final ServiceEvent event, final Map<BundleContext, Collection<ListenerInfo>> result) { |
| if (debug.DEBUG_HOOKS) { |
| Debug.println("notifyServiceEventListenerHooks(" + event.getType() + ":" + event.getServiceReference() + "," + result + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ |
| } |
| notifyHooksPrivileged(eventListenerHookName, "event", (hook, hookRegistration) -> { //$NON-NLS-1$ |
| if (hook instanceof EventListenerHook) { |
| ((EventListenerHook) hook).event(event, result); |
| } |
| }); |
| } |
| |
| /** |
| * Calls all hook services of the type specified by the hook context. |
| * |
| * @param hookContext Context to use when calling the hook services. |
| */ |
| public void notifyHooksPrivileged(String serviceName, String serviceMethod, HookContext hookContext) { |
| List<ServiceRegistrationImpl<?>> hooks = lookupServiceRegistrations(serviceName, null); |
| // Since the list is already sorted, we don't need to sort the list to call the hooks |
| // in the proper order. |
| |
| for (ServiceRegistrationImpl<?> registration : hooks) { |
| notifyHookPrivileged(systemBundleContext, registration, serviceMethod, hookContext); |
| } |
| } |
| |
| /** |
| * Call a hook service via a hook context. |
| * |
| * @param context Context of the bundle to get the hook service. |
| * @param registration Hook service to call. |
| * @param hookContext Context to use when calling the hook service. |
| */ |
| private void notifyHookPrivileged(BundleContextImpl context, ServiceRegistrationImpl<?> registration, |
| String serviceMethod, HookContext hookContext) { |
| if (hookContext.skipRegistration(registration)) { |
| return; |
| } |
| Object hook = registration.getHookInstance(); |
| if (hook == null) { |
| // The hook may not be initialized yet |
| // We do not call the hook until after it has been registered |
| // This means we could miss calls to a hook during the registered event. |
| return; |
| } |
| try { |
| hookContext.call(hook, registration); |
| } catch (Throwable t) { |
| if (debug.DEBUG_HOOKS) { |
| Debug.println(hook.getClass().getName() + "." + serviceMethod + "() exception: " + t.getMessage()); //$NON-NLS-1$ //$NON-NLS-2$ |
| Debug.printStackTrace(t); |
| } |
| // allow the adaptor to handle this unexpected error |
| container.handleRuntimeError(t); |
| ServiceException se = new ServiceException( |
| NLS.bind(Msg.SERVICE_FACTORY_EXCEPTION, hook.getClass().getName(), serviceMethod), t); |
| container.getEventPublisher().publishFrameworkEvent(FrameworkEvent.ERROR, registration.getBundle(), se); |
| } |
| } |
| |
| /** |
| * Call a newly registered ListenerHook service to provide the current collection of |
| * service listeners. |
| * |
| * @param registration The newly registered ListenerHook service. |
| */ |
| private void notifyNewListenerHook(final ServiceRegistrationImpl<?> registration) { |
| if (System.getSecurityManager() == null) { |
| notifyNewListenerHookPrivileged(registration); |
| } else { |
| AccessController.doPrivileged(new PrivilegedAction<Void>() { |
| @Override |
| public Void run() { |
| notifyNewListenerHookPrivileged(registration); |
| return null; |
| } |
| }); |
| } |
| |
| } |
| |
| void notifyNewListenerHookPrivileged(ServiceRegistrationImpl<?> registration) { |
| if (debug.DEBUG_HOOKS) { |
| Debug.println("notifyServiceNewListenerHook(" + registration + ")"); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| |
| // snapshot the listeners |
| Collection<ListenerInfo> addedListeners = new ArrayList<>(initialCapacity); |
| synchronized (serviceEventListeners) { |
| for (CopyOnWriteIdentityMap<ServiceListener, FilteredServiceListener> listeners : serviceEventListeners.values()) { |
| if (!listeners.isEmpty()) { |
| addedListeners.addAll(listeners.values()); |
| } |
| } |
| } |
| |
| final Collection<ListenerInfo> listeners = Collections.unmodifiableCollection(addedListeners); |
| notifyHookPrivileged(systemBundleContext, registration, "added", (hook, hookRegistration) -> { //$NON-NLS-1$ |
| if (hook instanceof ListenerHook) { |
| ((ListenerHook) hook).added(listeners); |
| } |
| }); |
| } |
| |
| /** |
| * Call the registered ListenerHook services to notify them of newly added or removed service listeners. |
| * The ListenerHook must be called in order: descending by service.ranking, then ascending by service.id. |
| * This is the natural order for ServiceReference. |
| * |
| * @param listeners A non-empty, unmodifiable collection of ListenerInfo objects. |
| * All elements in the list must be for the same bundle. |
| * @param added <code>true</code> if the specified listeners are being added. <code>false</code> |
| * if they are being removed. |
| */ |
| private void notifyListenerHooks(final Collection<ListenerInfo> listeners, final boolean added) { |
| if (System.getSecurityManager() == null) { |
| notifyListenerHooksPrivileged(listeners, added); |
| } else { |
| AccessController.doPrivileged(new PrivilegedAction<Void>() { |
| @Override |
| public Void run() { |
| notifyListenerHooksPrivileged(listeners, added); |
| return null; |
| } |
| }); |
| } |
| |
| } |
| |
| void notifyListenerHooksPrivileged(final Collection<ListenerInfo> listeners, final boolean added) { |
| assert !listeners.isEmpty(); |
| if (debug.DEBUG_HOOKS) { |
| Debug.println("notifyServiceListenerHooks(" + listeners + "," + (added ? "added" : "removed") + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ |
| } |
| |
| notifyHooksPrivileged(listenerHookName, added ? "added" : "removed", (hook, hookRegistration) -> { //$NON-NLS-1$ //$NON-NLS-2$ |
| if (hook instanceof ListenerHook) { |
| if (added) { |
| ((ListenerHook) hook).added(listeners); |
| } else { |
| ((ListenerHook) hook).removed(listeners); |
| } |
| } |
| }); |
| } |
| |
| final EquinoxContainer getContainer() { |
| return container; |
| } |
| } |