blob: ebd42f6631e831e7b4c8395a942886f186e21cda [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.felix.framework;
import java.util.*;
import org.apache.felix.framework.util.FelixConstants;
import org.osgi.framework.*;
import org.osgi.framework.hooks.service.*;
import org.osgi.framework.launch.Framework;
public class ServiceRegistry
{
private Logger m_logger = null;
private long m_currentServiceId = 1L;
// Maps bundle to an array of service registrations.
private Map m_serviceRegsMap = new HashMap();
// Maps registration to thread to keep track when a
// registration is in use, which will cause other
// threads to wait.
private Map m_lockedRegsMap = new HashMap();
// Maps bundle to an array of usage counts.
private Map m_inUseMap = new HashMap();
private final ServiceRegistryCallbacks m_callbacks;
private final Object m_eventHookLock = new Object();
private Object[] m_eventHooks = new Object[0];
private final Object m_findHookLock = new Object();
private Object[] m_findHooks = new Object[0];
private final Object m_listenerHookLock = new Object();
private Object[] m_listenerHooks = new Object[0];
public ServiceRegistry(Logger logger, ServiceRegistryCallbacks callbacks)
{
m_logger = logger;
m_callbacks = callbacks;
}
public synchronized ServiceReference[] getRegisteredServices(Bundle bundle)
{
ServiceRegistration[] regs = (ServiceRegistration[]) m_serviceRegsMap.get(bundle);
if (regs != null)
{
ServiceReference[] refs = new ServiceReference[regs.length];
for (int i = 0; i < refs.length; i++)
{
refs[i] = regs[i].getReference();
}
return refs;
}
return null;
}
public ServiceRegistration registerService(
Bundle bundle, String[] classNames, Object svcObj, Dictionary dict)
{
ServiceRegistration 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);
// Get the bundles current registered services.
ServiceRegistration[] regs = (ServiceRegistration[]) m_serviceRegsMap.get(bundle);
m_serviceRegsMap.put(bundle, addServiceRegistration(regs, reg));
}
// 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);
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_serviceRegsMap.get(bundle);
m_serviceRegsMap.put(bundle, removeServiceRegistration(regs, reg));
}
// 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_serviceRegsMap.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_serviceRegsMap.remove(bundle);
}
}
public synchronized List getServiceReferences(String className, Filter filter)
{
// Create a filtered list of service references.
List list = new ArrayList();
// Iterator over all service registrations.
for (Iterator i = m_serviceRegsMap.entrySet().iterator(); i.hasNext(); )
{
Map.Entry entry = (Map.Entry) i.next();
ServiceRegistration[] regs = (ServiceRegistration[]) entry.getValue();
for (int regIdx = 0;
(regs != null) && (regIdx < regs.length);
regIdx++)
{
// Determine if the registered services matches
// the search criteria.
boolean matched = false;
// If className is null, then look at filter only.
if ((className == null) &&
((filter == null) || filter.match(regs[regIdx].getReference())))
{
matched = true;
}
// If className is not null, then first match the
// objectClass property before looking at the
// filter.
else if (className != null)
{
String[] objectClass = (String[])
((ServiceRegistrationImpl) regs[regIdx]).getProperty(FelixConstants.OBJECTCLASS);
for (int classIdx = 0;
classIdx < objectClass.length;
classIdx++)
{
if (objectClass[classIdx].equals(className) &&
((filter == null) || filter.match(regs[regIdx].getReference())))
{
matched = true;
break;
}
}
}
// Add reference if it was a match.
if (matched)
{
list.add(regs[regIdx].getReference());
}
}
}
return list;
}
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 Object getService(Bundle bundle, ServiceReference ref)
{
UsageCount usage = null;
Object svcObj = null;
// Get the service registration.
ServiceRegistrationImpl reg = ((ServiceRegistrationImpl.ServiceReferenceImpl) ref).getServiceRegistration();
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.getService() resulted in a cycle.");
}
// 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())
{
// If the service registration is not valid, then this means
// that the service provider unregistered the service. The spec
// says that calls to get an unregistered service should always
// return null (assumption: even if it is currently cached
// by the bundle). So in this case, flush the service reference
// from the cache and return null.
flushUsageCount(bundle, ref);
// It is not necessary to unget the service object from
// the providing bundle, since the associated service is
// unregistered and hence not in the list of registered services
// of the providing bundle. This is precisely why the service
// registration was not found above in the first place.
}
else
{
// Get the usage count, if any.
usage = getUsageCount(bundle, ref);
// If the service object is cached, then increase the usage
// count and return the cached service object.
if (usage != null)
{
usage.m_count++;
svcObj = usage.m_svcObj;
}
}
}
// If we have a valid registration, but no usage count, that means we
// don't have a cached service object, so we need to create one now
// without holding the lock, since we will potentially call out to a
// service factory.
try
{
if (reg.isValid() && (usage == null))
{
// Get service object from service registration.
svcObj = reg.getService(bundle);
// Cache the service object.
if (svcObj != null)
{
synchronized (this)
{
// Unregistration can happen concurrently, so we need
// to double-check that we are still valid.
if (reg.isValid())
{
addUsageCount(bundle, ref, svcObj);
}
else
{
// The service must have been unregistered in the
// middle of our get operation, so null it.
svcObj = null;
}
}
}
}
}
finally
{
// Finally, unlock the service registration so that any threads
// waiting for it can continue.
synchronized (this)
{
m_lockedRegsMap.remove(reg);
notifyAll();
}
}
return svcObj;
}
public boolean ungetService(Bundle bundle, ServiceReference ref)
{
UsageCount usage = null;
ServiceRegistrationImpl reg = ((ServiceRegistrationImpl.ServiceReferenceImpl) ref).getServiceRegistration();
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());
// Make sure the service registration is still valid.
if (!reg.isValid())
{
// If the service registration is not valid, then this means
// that the service provider unregistered the service, so just
// flush the usage count and we are done.
flushUsageCount(bundle, ref);
}
else if (reg.isValid())
{
// Decrement usage count.
usage.m_count--;
}
// If the usage count has reached zero, the flush it.
if (usage.m_count == 0)
{
flushUsageCount(bundle, ref);
}
}
// If usage count goes 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 == 0)
{
// Remove reference from usages array.
((ServiceRegistrationImpl.ServiceReferenceImpl) ref)
.getServiceRegistration().ungetService(bundle, usage.m_svcObj);
usage.m_svcObj = null;
}
}
finally
{
// Finally, unlock the service registration so that any threads
// waiting for it can continue.
synchronized (this)
{
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 void addUsageCount(Bundle bundle, ServiceReference ref, Object svcObj)
{
UsageCount[] usages = (UsageCount[]) m_inUseMap.get(bundle);
UsageCount usage = new UsageCount();
usage.m_ref = ref;
usage.m_svcObj = svcObj;
usage.m_count++;
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);
}
/**
* 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);
}
}
private void addHooks(String[] classNames, Object svcObj, ServiceRegistration reg)
{
if (isHook(classNames, EventHook.class, svcObj))
{
synchronized (m_eventHookLock)
{
m_eventHooks = addHookToArray(m_eventHooks, svcObj, reg);
}
}
if (isHook(classNames, FindHook.class, svcObj))
{
synchronized (m_findHookLock)
{
m_findHooks = addHookToArray(m_findHooks, svcObj, reg);
}
}
if (isHook(classNames, ListenerHook.class, svcObj))
{
synchronized (m_listenerHookLock)
{
m_listenerHooks = addHookToArray(m_listenerHooks, svcObj, reg);
}
}
}
private static Object[] addHookToArray(Object[] src, Object svcObj, ServiceRegistration reg)
{
Object hookRef = getHookRef(svcObj, reg);
Object[] dst = new Object[src.length + 1];
System.arraycopy(src, 0, dst, 0, src.length);
dst[src.length] = hookRef;
return dst;
}
boolean isHook(String[] classNames, Class hookClass, Object svcObj)
{
if (svcObj instanceof ServiceFactory)
{
return Arrays.asList(classNames).contains(hookClass.getName());
}
if (hookClass.isAssignableFrom(svcObj.getClass()))
{
String hookName = hookClass.getName();
for (int i = 0; i < classNames.length; i++)
{
if (classNames[i].equals(hookName))
{
return true;
}
}
}
return false;
}
private void removeHook(ServiceRegistration reg)
{
Object svcObj = ((ServiceRegistrationImpl) reg).getService();
String [] classNames = (String[]) reg.getReference().getProperty(Constants.OBJECTCLASS);
if (isHook(classNames, EventHook.class, svcObj))
{
synchronized (m_eventHookLock)
{
m_eventHooks = removeFromHookArray(m_eventHooks, svcObj, reg);
}
}
if (isHook(classNames, FindHook.class, svcObj))
{
synchronized (m_findHookLock)
{
m_findHooks = removeFromHookArray(m_findHooks, svcObj, reg);
}
}
if (isHook(classNames, ListenerHook.class, svcObj))
{
synchronized (m_listenerHookLock)
{
m_listenerHooks = removeFromHookArray(m_listenerHooks, svcObj, reg);
}
}
}
private static Object[] removeFromHookArray(Object[] src, Object svcObj, ServiceRegistration reg)
{
Object[] dst = src;
int idx = -1;
for (int i = 0; i < src.length; i++)
{
boolean found = false;
if (svcObj instanceof ServiceFactory)
{
if (src[i] instanceof Object[])
{
Object [] arrElem = (Object []) src[i];
if (arrElem[0].equals(svcObj) && arrElem[1].equals(reg))
{
found = true;
}
}
}
else
{
if (src[i].equals(svcObj))
{
found = true;
}
}
if (found)
{
idx = i;
break;
}
}
if (idx >= 0)
{
if (src.length == 1)
{
dst = new Object[0];
}
else
{
dst = new Object[src.length - 1];
System.arraycopy(dst, 0, src, 0, idx);
if (idx < dst.length)
{
System.arraycopy(
src, idx + 1, dst, idx, dst.length - idx);
}
}
}
return dst;
}
public List getEventHooks()
{
synchronized (m_eventHooks)
{
return Collections.unmodifiableList(Arrays.asList(m_eventHooks));
}
}
List getFindHooks()
{
synchronized (m_findHooks)
{
return Collections.unmodifiableList(Arrays.asList(m_findHooks));
}
}
List getListenerHooks()
{
synchronized (m_listenerHooks)
{
return Collections.unmodifiableList(Arrays.asList(m_listenerHooks));
}
}
/**
* Returns a hook reference object that can be passed to the
* {@link #invokeHook(Object, Framework, InvokeHookCallback)} method.
* @param svcObj The Service Object. Either the service itself, or a <tt>ServiceFactory</tt>
* object.
* @param reg The associated <tt>ServiceRegistration</tt>.
* @return In the case of a <tt>ServiceFactory</tt> an Object[2] with both the
* factory and the <tt>ServiceRegistration</tt> is returned. In all other
* cases the svcObj is returned as passed in. This is the behavior expected
* by the {@link #invokeHook(Object, Framework, InvokeHookCallback)} method.
*/
static Object getHookRef(Object svcObj, ServiceRegistration reg)
{
if (svcObj instanceof ServiceFactory)
{
return new Object[] {svcObj, reg};
}
else
{
return svcObj;
}
}
/**
* Invokes a Service Registry Hook
* @param hookRef The hook to be invoked, this is an element on the list returned by the
* {@link #getEventHooks()}, {@link #getFindHooks()} or {@link #getListenerHooks()}
* methods. It could either contain the hook object itself or an Object[2] when a
* ServiceFactory is used. In that case the first element is the hook ServiceFactory
* and the second element is its ServiceRegistration.
* @param framework The framework that is invoking the hook, typically the Felix object.
* @param callback This is a callback object that is invoked with the actual hook object to
* be used, either the plain hook, or one obtained from the ServiceFactory.
*/
public static void invokeHook(
Object hookRef, Framework framework, InvokeHookCallback callback)
{
Object hook;
boolean serviceFactory;
if (hookRef instanceof Object[])
{
serviceFactory = true;
Object[] array = (Object[]) hookRef;
ServiceFactory hookFactory = (ServiceFactory) array[0];
ServiceRegistration sr = (ServiceRegistration) array[1];
hook = hookFactory.getService(framework, sr);
}
else
{
serviceFactory = false;
hook = hookRef;
}
try
{
callback.invokeHook(hook);
// TODO: SERVICE HOOKS - Check that "services in use" is handled
}
finally
{
if (serviceFactory)
{
Object [] array = (Object []) hookRef;
ServiceFactory hookFactory = (ServiceFactory) array[0];
ServiceRegistration sr = (ServiceRegistration) array[1];
hookFactory.ungetService(framework, sr, hook);
}
}
}
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);
}
}