blob: 9446cc37f9939610240a10f7ed57a9521caba080 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2003, 2006 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.osgi.framework.internal.core;
import java.lang.reflect.Array;
import java.util.*;
import org.eclipse.osgi.framework.debug.Debug;
import org.eclipse.osgi.framework.util.Headers;
import org.osgi.framework.*;
/**
* A registered service.
*
* The framework returns a ServiceRegistration object when a
* {@link BundleContextImpl#registerService(String, Object, Dictionary) BundleContext.registerService}
* method is successful. This object is for the private use of
* the registering bundle and should not be shared with other bundles.
* <p>The ServiceRegistration object may be used to update the properties
* for the service or to unregister the service.
*
* <p>If the ServiceRegistration is garbage collected the framework may remove
* the service. This implies that if a
* bundle wants to keep its service registered, it should keep the
* ServiceRegistration object referenced.
*/
//TODO That's kind of big
public class ServiceRegistrationImpl implements ServiceRegistration {
/** Reference to this registration. */
protected ServiceReferenceImpl reference;
/** Internal framework object. */
protected Framework framework;
/** context which registered this service. */
protected BundleContextImpl context;
/** bundle which registered this service. */
protected AbstractBundle bundle;
/** list of contexts using the service.
* Access to this should be protected by the registrationLock */
protected ArrayList contextsUsing;
/** service classes for this registration. */
protected String[] clazzes;
/** service object for this registration. */
protected Object service;
/** properties for this registration. */
protected Properties properties;
/** service id. */
protected long serviceid;
/** service ranking. */
protected int serviceranking;
/* internal object to use for synchronization */
protected Object registrationLock = new Object();
/** The registration state */
protected int state = REGISTERED;
public static final int REGISTERED = 0x00;
public static final int UNREGISTERING = 0x01;
public static final int UNREGISTERED = 0x02;
/**
* Construct a ServiceRegistration and register the service
* in the framework's service registry.
*
*/
protected ServiceRegistrationImpl(BundleContextImpl context, String[] clazzes, Object service, Dictionary properties) {
this.context = context;
this.bundle = context.bundle;
this.framework = context.framework;
this.clazzes = clazzes; /* must be set before calling createProperties. */
this.service = service;
this.contextsUsing = null;
this.reference = new ServiceReferenceImpl(this);
synchronized (framework.serviceRegistry) {
serviceid = framework.getNextServiceId(); /* must be set before calling createProperties. */
this.properties = createProperties(properties); /* must be valid after unregister is called. */
if (Debug.DEBUG && Debug.DEBUG_SERVICES) {
Debug.println("registerService[" + bundle + "](" + this + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
framework.serviceRegistry.publishService(context, this);
}
/* must not hold the registrations lock when this event is published */
framework.publishServiceEvent(ServiceEvent.REGISTERED, reference);
}
/**
* Unregister the service.
* Remove a service registration from the framework's service
* registry.
* All {@link ServiceReferenceImpl} objects for this registration
* can no longer be used to interact with the service.
*
* <p>The following steps are followed to unregister a service:
* <ol>
* <li>The service is removed from the framework's service
* registry so that it may no longer be used.
* {@link ServiceReferenceImpl}s for the service may no longer be used
* to get a service object for the service.
* <li>A {@link ServiceEvent} of type {@link ServiceEvent#UNREGISTERING}
* is synchronously sent so that bundles using this service
* may release their use of the service.
* <li>For each bundle whose use count for this service is greater
* than zero:
* <ol>
* <li>The bundle's use count for this service is set to zero.
* <li>If the service was registered with a {@link ServiceFactory},
* the {@link ServiceFactory#ungetService ServiceFactory.ungetService} method
* is called to release the service object for the bundle.
* </ol>
* </ol>
*
* @exception java.lang.IllegalStateException If
* this ServiceRegistration has already been unregistered.
* @see BundleContextImpl#ungetService
*/
public void unregister() {
synchronized (registrationLock) {
if (state != REGISTERED) /* in the process of unregisterING */
{
throw new IllegalStateException(Msg.SERVICE_ALREADY_UNREGISTERED_EXCEPTION);
}
/* remove this object from the service registry */
if (Debug.DEBUG && Debug.DEBUG_SERVICES) {
Debug.println("unregisterService[" + bundle + "](" + this + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
synchronized (framework.serviceRegistry) {
framework.serviceRegistry.unpublishService(context, this);
}
state = UNREGISTERING; /* mark unregisterING */
}
/* must not hold the registrationLock when this event is published */
framework.publishServiceEvent(ServiceEvent.UNREGISTERING, reference);
/* we have published the ServiceEvent, now mark the service fully unregistered */
service = null;
state = UNREGISTERED;
int size = 0;
BundleContextImpl[] users = null;
synchronized (registrationLock) {
if (contextsUsing != null) {
size = contextsUsing.size();
if (size > 0) {
if (Debug.DEBUG && Debug.DEBUG_SERVICES) {
Debug.println("unregisterService: releasing users"); //$NON-NLS-1$
}
users = (BundleContextImpl[]) contextsUsing.toArray(new BundleContextImpl[size]);
}
}
}
/* must not hold the registrationLock while releasing services */
for (int i = 0; i < size; i++) {
releaseService(users[i]);
}
contextsUsing = null;
reference = null; /* mark registration dead */
context = null;
/* These fields must be valid after unregister is called:
* properties
*/
}
/**
* Returns a {@link ServiceReferenceImpl} object for this registration.
* The {@link ServiceReferenceImpl} object may be shared with other bundles.
*
* @exception java.lang.IllegalStateException If
* this ServiceRegistration has already been unregistered.
* @return A {@link ServiceReferenceImpl} object.
*/
public org.osgi.framework.ServiceReference getReference() {
/* use reference instead of unregistered so that ServiceFactorys, called
* by releaseService after the registration is unregistered, can
* get the ServiceReference. Note this technically may voilate the spec
* but makes more sense.
*/
if (reference == null) {
throw new IllegalStateException(Msg.SERVICE_ALREADY_UNREGISTERED_EXCEPTION);
}
return (reference);
}
/**
* Update the properties associated with this service.
*
* <p>The key "objectClass" cannot be modified by this method. It's
* value is set when the service is registered.
*
* <p>The following steps are followed to modify a service's properties:
* <ol>
* <li>The service's properties are replaced with the provided properties.
* <li>A {@link ServiceEvent} of type {@link ServiceEvent#MODIFIED}
* is synchronously sent.
* </ol>
*
* @param props The properties for this service.
* Changes should not be made to this object after calling this method.
* To update the service's properties this method should be called again.
* @exception java.lang.IllegalStateException If
* this ServiceRegistration has already been unregistered.
*
* @exception IllegalArgumentException If the <tt>properties</tt>
* parameter contains case variants of the same key name.
*/
public void setProperties(Dictionary props) {
synchronized (registrationLock) {
if (state != REGISTERED) /* in the process of unregistering */
{
throw new IllegalStateException(Msg.SERVICE_ALREADY_UNREGISTERED_EXCEPTION);
}
this.properties = createProperties(props);
}
/* must not hold the registrationLock when this event is published */
framework.publishServiceEvent(ServiceEvent.MODIFIED, reference);
}
/**
* Construct a properties object from the dictionary for this
* ServiceRegistration.
*
* @param props The properties for this service.
* @return A Properties object for this ServiceRegistration.
*/
protected Properties createProperties(Dictionary props) {
Properties properties = new Properties(props);
properties.set(Constants.OBJECTCLASS, clazzes, true);
properties.set(Constants.SERVICE_ID, new Long(serviceid), true);
Object ranking = properties.getProperty(Constants.SERVICE_RANKING);
serviceranking = (ranking instanceof Integer) ? ((Integer) ranking).intValue() : 0;
return (properties);
}
/**
* Get the value of a service's property.
*
* <p>This method will continue to return property values after the
* service has been unregistered. This is so that references to
* unregistered service can be interrogated.
* (For example: ServiceReference objects stored in the log.)
*
* @param key Name of the property.
* @return Value of the property or <code>null</code> if there is
* no property by that name.
*/
protected Object getProperty(String key) {
synchronized (registrationLock) {
return (properties.getProperty(key));
}
}
/**
* Get the list of key names for the service's properties.
*
* <p>This method will continue to return the keys after the
* service has been unregistered. This is so that references to
* unregistered service can be interrogated.
* (For example: ServiceReference objects stored in the log.)
*
* @return The list of property key names.
*/
protected String[] getPropertyKeys() {
synchronized (registrationLock) {
return (properties.getPropertyKeys());
}
}
/**
* Return the bundle which registered the service.
*
* <p>This method will always return <code>null</code> when the
* service has been unregistered. This can be used to
* determine if the service has been unregistered.
*
* @return The bundle which registered the service.
*/
protected AbstractBundle getBundle() {
if (reference == null) {
return (null);
}
return (bundle);
}
/**
* Get a service object for the using BundleContext.
*
* @param user BundleContext using service.
* @return Service object
*/
protected Object getService(BundleContextImpl user) {
synchronized (registrationLock) {
if (state == UNREGISTERED) /* service unregistered */
{
return (null);
}
if (Debug.DEBUG && Debug.DEBUG_SERVICES) {
Debug.println("getService[" + user.bundle + "](" + this + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
Hashtable servicesInUse = user.servicesInUse;
ServiceUse use = (ServiceUse) servicesInUse.get(reference);
if (use == null) {
use = new ServiceUse(user, this);
Object service = use.getService();
if (service != null) {
servicesInUse.put(reference, use);
if (contextsUsing == null) {
contextsUsing = new ArrayList(10);
}
contextsUsing.add(user);
}
return (service);
} else {
return (use.getService());
}
}
}
/**
* Unget a service for the using BundleContext.
*
* @param user BundleContext using service.
* @return <code>false</code> if the context bundle's use count for the service
* is zero or if the service has been unregistered,
* otherwise <code>true</code>.
*/
protected boolean ungetService(BundleContextImpl user) {
synchronized (registrationLock) {
if (state == UNREGISTERED) {
return (false);
}
if (Debug.DEBUG && Debug.DEBUG_SERVICES) {
String bundle = (user.bundle == null) ? "" : user.bundle.toString(); //$NON-NLS-1$
Debug.println("ungetService[" + bundle + "](" + this + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
Hashtable servicesInUse = user.servicesInUse;
if (servicesInUse != null) {
ServiceUse use = (ServiceUse) servicesInUse.get(reference);
if (use != null) {
if (use.ungetService()) {
/* use count is now zero */
servicesInUse.remove(reference);
contextsUsing.remove(user);
}
return (true);
}
}
return (false);
}
}
/**
* Release the service for the using BundleContext.
*
* @param user BundleContext using service.
*/
protected void releaseService(BundleContextImpl user) {
synchronized (registrationLock) {
if (reference == null) /* registration dead */
{
return;
}
if (Debug.DEBUG && Debug.DEBUG_SERVICES) {
String bundle = (user.bundle == null) ? "" : user.bundle.toString(); //$NON-NLS-1$
Debug.println("releaseService[" + bundle + "](" + this + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
Hashtable servicesInUse = user.servicesInUse;
if (servicesInUse != null) {
ServiceUse use = (ServiceUse) servicesInUse.remove(reference);
if (use != null) {
use.releaseService();
// contextsUsing may have been nulled out by use.releaseService() if the registrant bundle
// is listening for events and unregisters the service
if (contextsUsing != null)
contextsUsing.remove(user);
}
}
}
}
/**
* Return the list of bundle which are using this service.
*
* @return Array of Bundles using this service.
*/
protected AbstractBundle[] getUsingBundles() {
synchronized (registrationLock) {
if (state == UNREGISTERED) /* service unregistered */
return (null);
if (contextsUsing == null)
return (null);
int size = contextsUsing.size();
if (size == 0)
return (null);
/* Copy list of BundleContext into an array of Bundle. */
AbstractBundle[] bundles = new AbstractBundle[size];
for (int i = 0; i < size; i++)
bundles[i] = ((BundleContextImpl) contextsUsing.get(i)).bundle;
return (bundles);
}
}
/**
* Return a String representation of this object.
*
* @return String representation of this object.
*/
public String toString() {
String[] clazzes = this.clazzes;
int size = clazzes.length;
StringBuffer sb = new StringBuffer(50 * size);
sb.append('{');
for (int i = 0; i < size; i++) {
if (i > 0) {
sb.append(", "); //$NON-NLS-1$
}
sb.append(clazzes[i]);
}
sb.append("}="); //$NON-NLS-1$
sb.append(properties);
return (sb.toString());
}
/**
* Hashtable for service properties.
*/
static class Properties extends Headers {
/**
* Create a properties object for the service.
*
* @param props The properties for this service.
*/
private Properties(int size, Dictionary props) {
super(size);
if (props != null) {
synchronized (props) {
Enumeration keysEnum = props.keys();
while (keysEnum.hasMoreElements()) {
Object key = keysEnum.nextElement();
if (key instanceof String) {
String header = (String) key;
setProperty(header, props.get(header));
}
}
}
}
}
/**
* Create a properties object for the service.
*
* @param props The properties for this service.
*/
protected Properties(Dictionary props) {
this((props == null) ? 2 : props.size() + 2, props);
}
/**
* Get a clone of the value of a service's property.
*
* @param key header name.
* @return Clone of the value of the property or <code>null</code> if there is
* no property by that name.
*/
protected Object getProperty(String key) {
return (cloneValue(get(key)));
}
/**
* Get the list of key names for the service's properties.
*
* @return The list of property key names.
*/
protected synchronized String[] getPropertyKeys() {
int size = size();
String[] keynames = new String[size];
Enumeration keysEnum = keys();
for (int i = 0; i < size; i++) {
keynames[i] = (String) keysEnum.nextElement();
}
return (keynames);
}
/**
* Put a clone of the property value into this property object.
*
* @param key Name of property.
* @param value Value of property.
* @return previous property value.
*/
protected synchronized Object setProperty(String key, Object value) {
return (set(key, cloneValue(value)));
}
/**
* Attempt to clone the value if necessary and possible.
*
* For some strange reason, you can test to see of an Object is
* Cloneable but you can't call the clone method since it is
* protected on Object!
*
* @param value object to be cloned.
* @return cloned object or original object if we didn't clone it.
*/
protected static Object cloneValue(Object value) {
if (value == null)
return null;
if (value instanceof String) /* shortcut String */
return (value);
if (value instanceof Number) /* shortcut Number */
return value;
if (value instanceof Character) /* shortcut Character */
return value;
if (value instanceof Boolean) /* shortcut Boolean */
return value;
Class clazz = value.getClass();
if (clazz.isArray()) {
// Do an array copy
Class type = clazz.getComponentType();
int len = Array.getLength(value);
Object clonedArray = Array.newInstance(type, len);
System.arraycopy(value, 0, clonedArray, 0, len);
return clonedArray;
}
// must use reflection because Object clone method is protected!!
try {
return (clazz.getMethod("clone", null).invoke(value, null)); //$NON-NLS-1$
} catch (Exception e) {
/* clone is not a public method on value's class */
} catch (Error e) {
/* JCL does not support reflection; try some well known types */
if (value instanceof Vector)
return (((Vector) value).clone());
if (value instanceof Hashtable)
return (((Hashtable) value).clone());
}
return (value);
}
public synchronized String toString() {
String keys[] = getPropertyKeys();
int size = keys.length;
StringBuffer sb = new StringBuffer(20 * size);
sb.append('{');
int n = 0;
for (int i = 0; i < size; i++) {
String key = keys[i];
if (!key.equals(Constants.OBJECTCLASS)) {
if (n > 0)
sb.append(", "); //$NON-NLS-1$
sb.append(key);
sb.append('=');
Object value = get(key);
if (value.getClass().isArray()) {
sb.append('[');
int length = Array.getLength(value);
for (int j = 0; j < length; j++) {
if (j > 0)
sb.append(',');
sb.append(Array.get(value, j));
}
sb.append(']');
} else {
sb.append(value);
}
n++;
}
}
sb.append('}');
return (sb.toString());
}
}
}