| /* |
| * 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.net.ContentHandler; |
| import java.util.*; |
| import org.apache.felix.framework.capabilityset.CapabilitySet; |
| import org.apache.felix.framework.capabilityset.SimpleFilter; |
| import org.apache.felix.framework.wiring.BundleCapabilityImpl; |
| |
| import org.osgi.framework.*; |
| import org.osgi.framework.hooks.service.*; |
| import org.osgi.framework.hooks.weaving.WeavingHook; |
| import org.osgi.framework.wiring.BundleCapability; |
| import org.osgi.service.url.URLStreamHandlerService; |
| |
| public class ServiceRegistry |
| { |
| private final Logger m_logger; |
| private long m_currentServiceId = 1L; |
| // Maps bundle to an array of service registrations. |
| private final Map m_regsMap = Collections.synchronizedMap(new HashMap()); |
| // Capability set for all service registrations. |
| private final CapabilitySet m_regCapSet; |
| |
| // Maps registration to thread to keep track when a |
| // registration is in use, which will cause other |
| // threads to wait. |
| private final Map m_lockedRegsMap = new HashMap(); |
| // Maps bundle to an array of usage counts. |
| private final Map m_inUseMap = new HashMap(); |
| |
| private final ServiceRegistryCallbacks m_callbacks; |
| |
| private final WeakHashMap<ServiceReference, ServiceReference> m_blackList = |
| new WeakHashMap<ServiceReference, ServiceReference>(); |
| |
| private final static Class<?>[] m_hookClasses = { |
| EventHook.class, |
| FindHook.class, |
| ListenerHook.class, |
| WeavingHook.class, |
| URLStreamHandlerService.class, |
| ContentHandler.class |
| }; |
| private final Map<Class<?>, Set<ServiceReference<?>>> m_allHooks = |
| new HashMap<Class<?>, Set<ServiceReference<?>>>(); |
| |
| public ServiceRegistry(Logger logger, ServiceRegistryCallbacks callbacks) |
| { |
| m_logger = logger; |
| m_callbacks = callbacks; |
| |
| List indices = new ArrayList(); |
| indices.add(Constants.OBJECTCLASS); |
| m_regCapSet = new CapabilitySet(indices, false); |
| } |
| |
| public ServiceReference[] getRegisteredServices(Bundle bundle) |
| { |
| ServiceRegistration[] regs = (ServiceRegistration[]) m_regsMap.get(bundle); |
| if (regs != null) |
| { |
| List refs = new ArrayList(regs.length); |
| for (int i = 0; i < regs.length; i++) |
| { |
| try |
| { |
| refs.add(regs[i].getReference()); |
| } |
| catch (IllegalStateException ex) |
| { |
| // Don't include the reference as it is not valid anymore |
| } |
| } |
| return (ServiceReference[]) refs.toArray(new ServiceReference[refs.size()]); |
| } |
| return null; |
| } |
| |
| public ServiceRegistration registerService( |
| Bundle bundle, String[] classNames, Object svcObj, Dictionary dict) |
| { |
| ServiceRegistrationImpl reg = null; |
| |
| synchronized (this) |
| { |
| // Create the service registration. |
| reg = new ServiceRegistrationImpl( |
| this, bundle, classNames, new Long(m_currentServiceId++), svcObj, dict); |
| |
| // Keep track of registered hooks. |
| addHooks(classNames, svcObj, reg.getReference()); |
| |
| // Get the bundles current registered services. |
| ServiceRegistration[] regs = (ServiceRegistration[]) m_regsMap.get(bundle); |
| m_regsMap.put(bundle, addServiceRegistration(regs, reg)); |
| m_regCapSet.addCapability((BundleCapabilityImpl) reg.getReference()); |
| } |
| |
| // Notify callback objects about registered service. |
| if (m_callbacks != null) |
| { |
| m_callbacks.serviceChanged( |
| new ServiceEvent(ServiceEvent.REGISTERED, reg.getReference()), null); |
| } |
| return reg; |
| } |
| |
| public void unregisterService(Bundle bundle, ServiceRegistration reg) |
| { |
| // If this is a hook, it should be removed. |
| removeHook(reg.getReference()); |
| |
| synchronized (this) |
| { |
| // Note that we don't lock the service registration here using |
| // the m_lockedRegsMap because we want to allow bundles to get |
| // the service during the unregistration process. However, since |
| // we do remove the registration from the service registry, no |
| // new bundles will be able to look up the service. |
| |
| // Now remove the registered service. |
| ServiceRegistration[] regs = (ServiceRegistration[]) m_regsMap.get(bundle); |
| m_regsMap.put(bundle, removeServiceRegistration(regs, reg)); |
| m_regCapSet.removeCapability((BundleCapabilityImpl) reg.getReference()); |
| } |
| |
| // Notify callback objects about unregistering service. |
| if (m_callbacks != null) |
| { |
| m_callbacks.serviceChanged( |
| new ServiceEvent(ServiceEvent.UNREGISTERING, reg.getReference()), null); |
| } |
| |
| // Now forcibly unget the service object for all stubborn clients. |
| synchronized (this) |
| { |
| Bundle[] clients = getUsingBundles(reg.getReference()); |
| for (int i = 0; (clients != null) && (i < clients.length); i++) |
| { |
| while (ungetService(clients[i], reg.getReference())) |
| ; // Keep removing until it is no longer possible |
| } |
| ((ServiceRegistrationImpl) reg).invalidate(); |
| } |
| } |
| |
| /** |
| * This method retrieves all services registrations for the specified |
| * bundle and invokes <tt>ServiceRegistration.unregister()</tt> on each |
| * one. This method is only called be the framework to clean up after |
| * a stopped bundle. |
| * @param bundle the bundle whose services should be unregistered. |
| **/ |
| public void unregisterServices(Bundle bundle) |
| { |
| // Simply remove all service registrations for the bundle. |
| ServiceRegistration[] regs = null; |
| synchronized (this) |
| { |
| regs = (ServiceRegistration[]) m_regsMap.get(bundle); |
| } |
| |
| // Note, there is no race condition here with respect to the |
| // bundle registering more services, because its bundle context |
| // has already been invalidated by this point, so it would not |
| // be able to register more services. |
| |
| // Unregister each service. |
| for (int i = 0; (regs != null) && (i < regs.length); i++) |
| { |
| if (((ServiceRegistrationImpl) regs[i]).isValid()) |
| { |
| regs[i].unregister(); |
| } |
| } |
| |
| // Now remove the bundle itself. |
| synchronized (this) |
| { |
| m_regsMap.remove(bundle); |
| } |
| } |
| |
| public synchronized List getServiceReferences(String className, SimpleFilter filter) |
| { |
| if ((className == null) && (filter == null)) |
| { |
| // Return all services. |
| filter = new SimpleFilter(Constants.OBJECTCLASS, "*", SimpleFilter.PRESENT); |
| } |
| else if ((className != null) && (filter == null)) |
| { |
| // Return services matching the class name. |
| filter = new SimpleFilter(Constants.OBJECTCLASS, className, SimpleFilter.EQ); |
| } |
| else if ((className != null) && (filter != null)) |
| { |
| // Return services matching the class name and filter. |
| List filters = new ArrayList(2); |
| filters.add(new SimpleFilter(Constants.OBJECTCLASS, className, SimpleFilter.EQ)); |
| filters.add(filter); |
| filter = new SimpleFilter(null, filters, SimpleFilter.AND); |
| } |
| // else just use the specified filter. |
| |
| Set<BundleCapability> matches = m_regCapSet.match(filter, false); |
| |
| return new ArrayList(matches); |
| } |
| |
| public synchronized ServiceReference[] getServicesInUse(Bundle bundle) |
| { |
| UsageCount[] usages = (UsageCount[]) m_inUseMap.get(bundle); |
| if (usages != null) |
| { |
| ServiceReference[] refs = new ServiceReference[usages.length]; |
| for (int i = 0; i < refs.length; i++) |
| { |
| refs[i] = usages[i].m_ref; |
| } |
| return refs; |
| } |
| return null; |
| } |
| |
| public <S> S getServiceSafely(Bundle bundle, ServiceReference<S> ref) |
| { |
| try |
| { |
| return getService(bundle, ref); |
| } |
| catch (ServiceException ex) |
| { |
| // Just ignore and return null. |
| } |
| return null; |
| } |
| |
| public <S> S getService(Bundle bundle, ServiceReference<S> ref) |
| { |
| UsageCount usage = null; |
| Object svcObj = null; |
| |
| // Get the service registration. |
| ServiceRegistrationImpl reg = |
| ((ServiceRegistrationImpl.ServiceReferenceImpl) ref).getRegistration(); |
| |
| synchronized (this) |
| { |
| // First make sure that no existing operation is currently |
| // being performed by another thread on the service registration. |
| for (Object o = m_lockedRegsMap.get(reg); (o != null); o = m_lockedRegsMap.get(reg)) |
| { |
| // We don't allow cycles when we call out to the service factory. |
| if (o.equals(Thread.currentThread())) |
| { |
| throw new ServiceException( |
| "ServiceFactory.getService() resulted in a cycle.", |
| ServiceException.FACTORY_ERROR, |
| null); |
| } |
| |
| // Otherwise, wait for it to be freed. |
| try |
| { |
| wait(); |
| } |
| catch (InterruptedException ex) |
| { |
| } |
| } |
| |
| // Lock the service registration. |
| m_lockedRegsMap.put(reg, Thread.currentThread()); |
| |
| // Make sure the service registration is still valid. |
| if (reg.isValid()) |
| { |
| // Get the usage count, if any. |
| usage = getUsageCount(bundle, ref); |
| |
| // If we don't have a usage count, then create one and |
| // since the spec says we increment usage count before |
| // actually getting the service object. |
| if (usage == null) |
| { |
| usage = addUsageCount(bundle, ref); |
| } |
| |
| // Increment the usage count and grab the already retrieved |
| // service object, if one exists. |
| usage.m_count++; |
| svcObj = usage.m_svcObj; |
| } |
| } |
| |
| // If we have a usage count, but no service object, then we haven't |
| // cached the service object yet, so we need to create one now without |
| // holding the lock, since we will potentially call out to a service |
| // factory. |
| try |
| { |
| if ((usage != null) && (svcObj == null)) |
| { |
| svcObj = reg.getService(bundle); |
| } |
| } |
| finally |
| { |
| // If we successfully retrieved a service object, then we should |
| // cache it in the usage count. If not, we should flush the usage |
| // count. Either way, we need to unlock the service registration |
| // so that any threads waiting for it can continue. |
| synchronized (this) |
| { |
| // Before caching the service object, double check to see if |
| // the registration is still valid, since it may have been |
| // unregistered while we didn't hold the lock. |
| if (!reg.isValid() || (svcObj == null)) |
| { |
| flushUsageCount(bundle, ref); |
| } |
| else |
| { |
| usage.m_svcObj = svcObj; |
| } |
| m_lockedRegsMap.remove(reg); |
| notifyAll(); |
| } |
| } |
| |
| return (S) svcObj; |
| } |
| |
| public boolean ungetService(Bundle bundle, ServiceReference ref) |
| { |
| UsageCount usage = null; |
| ServiceRegistrationImpl reg = |
| ((ServiceRegistrationImpl.ServiceReferenceImpl) ref).getRegistration(); |
| |
| synchronized (this) |
| { |
| // First make sure that no existing operation is currently |
| // being performed by another thread on the service registration. |
| for (Object o = m_lockedRegsMap.get(reg); (o != null); o = m_lockedRegsMap.get(reg)) |
| { |
| // We don't allow cycles when we call out to the service factory. |
| if (o.equals(Thread.currentThread())) |
| { |
| throw new IllegalStateException( |
| "ServiceFactory.ungetService() resulted in a cycle."); |
| } |
| |
| // Otherwise, wait for it to be freed. |
| try |
| { |
| wait(); |
| } |
| catch (InterruptedException ex) |
| { |
| } |
| } |
| |
| // Get the usage count. |
| usage = getUsageCount(bundle, ref); |
| // If there is no cached services, then just return immediately. |
| if (usage == null) |
| { |
| return false; |
| } |
| |
| // Lock the service registration. |
| m_lockedRegsMap.put(reg, Thread.currentThread()); |
| } |
| |
| // If usage count will go to zero, then unget the service |
| // from the registration; we do this outside the lock |
| // since this might call out to the service factory. |
| try |
| { |
| if (usage.m_count == 1) |
| { |
| // Remove reference from usages array. |
| ((ServiceRegistrationImpl.ServiceReferenceImpl) ref) |
| .getRegistration().ungetService(bundle, usage.m_svcObj); |
| } |
| } |
| finally |
| { |
| // Finally, decrement usage count and flush if it goes to zero or |
| // the registration became invalid while we were not holding the |
| // lock. Either way, unlock the service registration so that any |
| // threads waiting for it can continue. |
| synchronized (this) |
| { |
| // Decrement usage count, which spec says should happen after |
| // ungetting the service object. |
| usage.m_count--; |
| |
| // If the registration is invalid or the usage count has reached |
| // zero, then flush it. |
| if (!reg.isValid() || (usage.m_count <= 0)) |
| { |
| usage.m_svcObj = null; |
| flushUsageCount(bundle, ref); |
| } |
| |
| // Release the registration lock so any waiting threads can |
| // continue. |
| m_lockedRegsMap.remove(reg); |
| notifyAll(); |
| } |
| } |
| |
| return true; |
| } |
| |
| |
| /** |
| * This is a utility method to release all services being |
| * used by the specified bundle. |
| * @param bundle the bundle whose services are to be released. |
| **/ |
| public void ungetServices(Bundle bundle) |
| { |
| UsageCount[] usages; |
| synchronized (this) |
| { |
| usages = (UsageCount[]) m_inUseMap.get(bundle); |
| } |
| |
| if (usages == null) |
| { |
| return; |
| } |
| |
| // Note, there is no race condition here with respect to the |
| // bundle using more services, because its bundle context |
| // has already been invalidated by this point, so it would not |
| // be able to look up more services. |
| |
| // Remove each service object from the |
| // service cache. |
| for (int i = 0; i < usages.length; i++) |
| { |
| // Keep ungetting until all usage count is zero. |
| while (ungetService(bundle, usages[i].m_ref)) |
| { |
| // Empty loop body. |
| } |
| } |
| } |
| |
| public synchronized Bundle[] getUsingBundles(ServiceReference ref) |
| { |
| Bundle[] bundles = null; |
| for (Iterator iter = m_inUseMap.entrySet().iterator(); iter.hasNext(); ) |
| { |
| Map.Entry entry = (Map.Entry) iter.next(); |
| Bundle bundle = (Bundle) entry.getKey(); |
| UsageCount[] usages = (UsageCount[]) entry.getValue(); |
| for (int useIdx = 0; useIdx < usages.length; useIdx++) |
| { |
| if (usages[useIdx].m_ref.equals(ref)) |
| { |
| // Add the bundle to the array to be returned. |
| if (bundles == null) |
| { |
| bundles = new Bundle[] { bundle }; |
| } |
| else |
| { |
| Bundle[] nbs = new Bundle[bundles.length + 1]; |
| System.arraycopy(bundles, 0, nbs, 0, bundles.length); |
| nbs[bundles.length] = bundle; |
| bundles = nbs; |
| } |
| } |
| } |
| } |
| return bundles; |
| } |
| |
| void servicePropertiesModified(ServiceRegistration reg, Dictionary oldProps) |
| { |
| if (m_callbacks != null) |
| { |
| m_callbacks.serviceChanged( |
| new ServiceEvent(ServiceEvent.MODIFIED, reg.getReference()), oldProps); |
| } |
| } |
| |
| public Logger getLogger() |
| { |
| return m_logger; |
| } |
| |
| private static ServiceRegistration[] addServiceRegistration( |
| ServiceRegistration[] regs, ServiceRegistration reg) |
| { |
| if (regs == null) |
| { |
| regs = new ServiceRegistration[] { reg }; |
| } |
| else |
| { |
| ServiceRegistration[] newRegs = new ServiceRegistration[regs.length + 1]; |
| System.arraycopy(regs, 0, newRegs, 0, regs.length); |
| newRegs[regs.length] = reg; |
| regs = newRegs; |
| } |
| return regs; |
| } |
| |
| private static ServiceRegistration[] removeServiceRegistration( |
| ServiceRegistration[] regs, ServiceRegistration reg) |
| { |
| for (int i = 0; (regs != null) && (i < regs.length); i++) |
| { |
| if (regs[i].equals(reg)) |
| { |
| // If this is the only usage, then point to empty list. |
| if ((regs.length - 1) == 0) |
| { |
| regs = new ServiceRegistration[0]; |
| } |
| // Otherwise, we need to do some array copying. |
| else |
| { |
| ServiceRegistration[] newRegs = new ServiceRegistration[regs.length - 1]; |
| System.arraycopy(regs, 0, newRegs, 0, i); |
| if (i < newRegs.length) |
| { |
| System.arraycopy( |
| regs, i + 1, newRegs, i, newRegs.length - i); |
| } |
| regs = newRegs; |
| } |
| } |
| } |
| return regs; |
| } |
| |
| /** |
| * Utility method to retrieve the specified bundle's usage count for the |
| * specified service reference. |
| * @param bundle The bundle whose usage counts are being searched. |
| * @param ref The service reference to find in the bundle's usage counts. |
| * @return The associated usage count or null if not found. |
| **/ |
| private UsageCount getUsageCount(Bundle bundle, ServiceReference ref) |
| { |
| UsageCount[] usages = (UsageCount[]) m_inUseMap.get(bundle); |
| for (int i = 0; (usages != null) && (i < usages.length); i++) |
| { |
| if (usages[i].m_ref.equals(ref)) |
| { |
| return usages[i]; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Utility method to update the specified bundle's usage count array to |
| * include the specified service. This method should only be called |
| * to add a usage count for a previously unreferenced service. If the |
| * service already has a usage count, then the existing usage count |
| * counter simply needs to be incremented. |
| * @param bundle The bundle acquiring the service. |
| * @param ref The service reference of the acquired service. |
| * @param svcObj The service object of the acquired service. |
| **/ |
| private UsageCount addUsageCount(Bundle bundle, ServiceReference ref) |
| { |
| UsageCount[] usages = (UsageCount[]) m_inUseMap.get(bundle); |
| |
| UsageCount usage = new UsageCount(); |
| usage.m_ref = ref; |
| |
| if (usages == null) |
| { |
| usages = new UsageCount[] { usage }; |
| } |
| else |
| { |
| UsageCount[] newUsages = new UsageCount[usages.length + 1]; |
| System.arraycopy(usages, 0, newUsages, 0, usages.length); |
| newUsages[usages.length] = usage; |
| usages = newUsages; |
| } |
| |
| m_inUseMap.put(bundle, usages); |
| |
| return usage; |
| } |
| |
| /** |
| * Utility method to flush the specified bundle's usage count for the |
| * specified service reference. This should be called to completely |
| * remove the associated usage count object for the specified service |
| * reference. If the goal is to simply decrement the usage, then get |
| * the usage count and decrement its counter. This method will also |
| * remove the specified bundle from the "in use" map if it has no more |
| * usage counts after removing the usage count for the specified service |
| * reference. |
| * @param bundle The bundle whose usage count should be removed. |
| * @param ref The service reference whose usage count should be removed. |
| **/ |
| private void flushUsageCount(Bundle bundle, ServiceReference ref) |
| { |
| UsageCount[] usages = (UsageCount[]) m_inUseMap.get(bundle); |
| for (int i = 0; (usages != null) && (i < usages.length); i++) |
| { |
| if (usages[i].m_ref.equals(ref)) |
| { |
| // If this is the only usage, then point to empty list. |
| if ((usages.length - 1) == 0) |
| { |
| usages = null; |
| } |
| // Otherwise, we need to do some array copying. |
| else |
| { |
| UsageCount[] newUsages = new UsageCount[usages.length - 1]; |
| System.arraycopy(usages, 0, newUsages, 0, i); |
| if (i < newUsages.length) |
| { |
| System.arraycopy( |
| usages, i + 1, newUsages, i, newUsages.length - i); |
| } |
| usages = newUsages; |
| } |
| } |
| } |
| |
| if (usages != null) |
| { |
| m_inUseMap.put(bundle, usages); |
| } |
| else |
| { |
| m_inUseMap.remove(bundle); |
| } |
| } |
| |
| // |
| // Hook-related methods. |
| // |
| |
| boolean isHookBlackListed(ServiceReference sr) |
| { |
| return m_blackList.containsKey(sr); |
| } |
| |
| void blackListHook(ServiceReference sr) |
| { |
| m_blackList.put(sr, sr); |
| } |
| |
| static boolean isHook(String[] classNames, Class<?> hookClass, Object svcObj) |
| { |
| // For a service factory, we can only match names. |
| if (svcObj instanceof ServiceFactory) |
| { |
| for (String className : classNames) |
| { |
| if (className.equals(hookClass.getName())) |
| { |
| return true; |
| } |
| } |
| } |
| |
| // For a service object, check if its class matches. |
| if (hookClass.isAssignableFrom(svcObj.getClass())) |
| { |
| // But still only if it is registered under that interface. |
| String hookName = hookClass.getName(); |
| for (String className : classNames) |
| { |
| if (className.equals(hookName)) |
| { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| private void addHooks(String[] classNames, Object svcObj, ServiceReference<?> ref) |
| { |
| for (Class<?> hookClass : m_hookClasses) |
| { |
| if (isHook(classNames, hookClass, svcObj)) |
| { |
| synchronized (m_allHooks) |
| { |
| Set<ServiceReference<?>> hooks = m_allHooks.get(hookClass); |
| if (hooks == null) |
| { |
| hooks = new HashSet<ServiceReference<?>>(); |
| m_allHooks.put(hookClass, hooks); |
| } |
| hooks.add(ref); |
| } |
| } |
| } |
| } |
| |
| private void removeHook(ServiceReference ref) |
| { |
| Object svcObj = ((ServiceRegistrationImpl.ServiceReferenceImpl) ref) |
| .getRegistration().getService(); |
| String [] classNames = (String[]) ref.getProperty(Constants.OBJECTCLASS); |
| |
| for (Class<?> hookClass : m_hookClasses) |
| { |
| if (isHook(classNames, hookClass, svcObj)) |
| { |
| synchronized (m_allHooks) |
| { |
| Set<ServiceReference<?>> hooks = m_allHooks.get(hookClass); |
| if (hooks != null) |
| { |
| hooks.remove(ref); |
| if (hooks.isEmpty()) |
| { |
| m_allHooks.remove(hookClass); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| public <S> Set<ServiceReference<S>> getHooks(Class<S> hookClass) |
| { |
| synchronized (m_allHooks) |
| { |
| Set<ServiceReference<?>> hooks = m_allHooks.get(hookClass); |
| if (hooks != null) |
| { |
| SortedSet sorted = new TreeSet<ServiceReference<?>>(Collections.reverseOrder()); |
| sorted.addAll(hooks); |
| return asTypedSortedSet(sorted); |
| } |
| return Collections.EMPTY_SET; |
| } |
| } |
| |
| private static <S> SortedSet<ServiceReference<S>> asTypedSortedSet( |
| SortedSet<ServiceReference<?>> ss) |
| { |
| return (SortedSet<ServiceReference<S>>) (SortedSet) ss; |
| } |
| |
| private static class UsageCount |
| { |
| public int m_count = 0; |
| public ServiceReference m_ref = null; |
| public Object m_svcObj = null; |
| } |
| |
| public interface ServiceRegistryCallbacks |
| { |
| void serviceChanged(ServiceEvent event, Dictionary oldProps); |
| } |
| } |