/*
* 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.connect.felix.framework;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.WeakHashMap;
import org.osgi.framework.Bundle;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceException;
import org.osgi.framework.ServiceFactory;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.framework.wiring.BundleCapability;
import org.apache.felix.connect.felix.framework.capabilityset.CapabilitySet;
import org.apache.felix.connect.felix.framework.capabilityset.SimpleFilter;
public class ServiceRegistry
{
private long m_currentServiceId = 1L;
// Maps bundle to an array of service registrations.
private final Map<Bundle, ServiceRegistration[]> m_regsMap = Collections.synchronizedMap(new HashMap<Bundle, ServiceRegistration[]>());
// Capability set for all service registrations.
private final CapabilitySet<ServiceRegistrationImpl.ServiceReferenceImpl> 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<ServiceRegistrationImpl, Thread> m_lockedRegsMap = new HashMap<ServiceRegistrationImpl, Thread>();
// Maps bundle to an array of usage counts.
private final Map<Bundle, UsageCount[]> m_inUseMap = new HashMap<Bundle, UsageCount[]>();
private final ServiceRegistryCallbacks m_callbacks;
private final WeakHashMap<ServiceReference, ServiceReference> m_blackList =
new WeakHashMap<ServiceReference, ServiceReference>();
private final static Class<?>[] m_hookClasses =
{
org.osgi.framework.hooks.bundle.FindHook.class,
org.osgi.framework.hooks.bundle.EventHook.class,
org.osgi.framework.hooks.service.EventHook.class,
org.osgi.framework.hooks.service.EventListenerHook.class,
org.osgi.framework.hooks.service.FindHook.class,
org.osgi.framework.hooks.service.ListenerHook.class,
org.osgi.framework.hooks.weaving.WeavingHook.class,
org.osgi.framework.hooks.resolver.ResolverHookFactory.class,
org.osgi.service.url.URLStreamHandlerService.class,
java.net.ContentHandler.class
};
private final Map<Class<?>, Set<ServiceReference<?>>> m_allHooks =
new HashMap<Class<?>, Set<ServiceReference<?>>>();
public ServiceRegistry(ServiceRegistryCallbacks callbacks)
{
m_callbacks = callbacks;
m_regCapSet = new CapabilitySet(Collections.singletonList(Constants.OBJECTCLASS), false);
}
public ServiceReference[] getRegisteredServices(Bundle bundle)
{
ServiceRegistration[] regs = m_regsMap.get(bundle);
if (regs != null)
{
List<ServiceReference> refs = new ArrayList<ServiceReference>(regs.length);
for (ServiceRegistration reg : regs)
{
try
{
refs.add(reg.getReference());
}
catch (IllegalStateException ex)
{
// Don't include the reference as it is not valid anymore
}
}
return refs.toArray(new ServiceReference[refs.size()]);
}
return null;
}
// Caller is expected to fire REGISTERED event.
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, 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(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, ServiceRegistrationImpl 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 = m_regsMap.get(bundle);
m_regsMap.put(bundle, removeServiceRegistration(regs, reg));
m_regCapSet.removeCapability(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 = 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 Collection<ServiceReference<?>> 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<Object> filters = new ArrayList<Object>(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<ServiceRegistrationImpl.ServiceReferenceImpl> matches = m_regCapSet.match(filter, false);
return (Collection) 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 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 = 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 (UsageCount usage : usages)
{
// Keep ungetting until all usage count is zero.
while (ungetService(bundle, usage.m_ref))
{
// Empty loop body.
}
}
}
public synchronized Bundle[] getUsingBundles(ServiceReference ref)
{
Bundle[] bundles = null;
for (Map.Entry<Bundle, UsageCount[]> entry : m_inUseMap.entrySet())
{
Bundle bundle = entry.getKey();
UsageCount[] usages = entry.getValue();
for (UsageCount usage : usages)
{
if (usage.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)
{
updateHook(reg.getReference());
if (m_callbacks != null)
{
m_callbacks.serviceChanged(
new ServiceEvent(ServiceEvent.MODIFIED, reg.getReference()), oldProps);
}
}
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.
*/
private UsageCount addUsageCount(Bundle bundle, ServiceReference ref)
{
UsageCount[] usages = 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 TreeSet<ServiceReference<?>>(Collections.reverseOrder());
m_allHooks.put(hookClass, hooks);
}
hooks.add(ref);
}
}
}
}
private void updateHook(ServiceReference ref)
{
// We maintain the hooks sorted, so if ranking has changed for example,
// we need to ensure the order remains correct by resorting the hooks.
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)
{
List<ServiceReference<?>> refs = new ArrayList<ServiceReference<?>>(hooks);
hooks.clear();
hooks.addAll(refs);
}
}
}
}
}
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)
{
@SuppressWarnings("unchecked")
Set<ServiceReference<S>> hooks = (Set) m_allHooks.get(hookClass);
if (hooks != null)
{
SortedSet<ServiceReference<S>> sorted = new TreeSet<ServiceReference<S>>(Collections.reverseOrder());
sorted.addAll(hooks);
return sorted;
}
return Collections.emptySet();
}
}
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<String, ?> oldProps);
}
}