org.eclipse.osgi.internal.serviceregistry.ServiceRegistrationImpl Maven / Gradle / Ivy
/*******************************************************************************
* Copyright (c) 2003, 2017 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.internal.serviceregistry;
import java.util.*;
import org.eclipse.osgi.internal.debug.Debug;
import org.eclipse.osgi.internal.framework.BundleContextImpl;
import org.eclipse.osgi.internal.loader.sources.PackageSource;
import org.eclipse.osgi.internal.messages.Msg;
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.
* The ServiceRegistration object may be used to update the properties
* for the service or to unregister the service.
*
*
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.
*
* @ThreadSafe
*/
public class ServiceRegistrationImpl implements ServiceRegistration, Comparable> {
private final ServiceRegistry registry;
/** context which registered this service. */
private final BundleContextImpl context;
/** bundle which registered this service. */
private final Bundle bundle;
/** service classes for this registration. */
private final String[] clazzes;
/** service object for this registration. */
private final S service;
/** Reference to this registration. */
/* @GuardedBy("registrationLock") */
private ServiceReferenceImpl reference;
/** List of contexts using the service.
* List<BundleContextImpl>.
* */
/* @GuardedBy("registrationLock") */
private final List contextsUsing;
/** properties for this registration. */
/* @GuardedBy("registrationLock") */
private Map properties;
/** service id. */
private final long serviceid;
/** service ranking. */
/* @GuardedBy("registrationLock") */
private int serviceranking;
/* internal object to use for synchronization */
private final Object registrationLock = new Object();
/** The registration state */
/* @GuardedBy("registrationLock") */
private int state;
private static final int REGISTERED = 0x00;
private static final int UNREGISTERING = 0x01;
private static final int UNREGISTERED = 0x02;
/**
* Construct a ServiceRegistration and register the service
* in the framework's service registry.
*
*/
ServiceRegistrationImpl(ServiceRegistry registry, BundleContextImpl context, String[] clazzes, S service) {
this.registry = registry;
this.context = context;
this.bundle = context.getBundleImpl();
this.clazzes = clazzes; /* must be set before calling createProperties. */
this.service = service; /* must be set before calling createProperties. */
this.serviceid = registry.getNextServiceId(); /* must be set before calling createProperties. */
this.contextsUsing = new ArrayList<>(10);
synchronized (registrationLock) {
this.state = REGISTERED;
/* We leak this from the constructor here, but it is ok
* because the ServiceReferenceImpl constructor only
* stores the value in a final field without
* otherwise using it.
*/
this.reference = new ServiceReferenceImpl<>(this);
}
}
/**
* Call after constructing this object to complete the registration.
*/
void register(Dictionary props) {
final ServiceReferenceImpl ref;
synchronized (registry) {
context.checkValid();
synchronized (registrationLock) {
ref = reference; /* used to publish event outside sync */
this.properties = createProperties(props); /* must be valid after unregister is called. */
}
if (registry.debug.DEBUG_SERVICES) {
Debug.println("registerService[" + bundle + "](" + this + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
registry.addServiceRegistration(context, this);
}
/* must not hold the registrations lock when this event is published */
registry.publishServiceEvent(new ServiceEvent(ServiceEvent.REGISTERED, ref));
}
/**
* Update the properties associated with this service.
*
* The key "objectClass" cannot be modified by this method. It's
* value is set when the service is registered.
*
*
The following steps are followed to modify a service's properties:
*
* - The service's properties are replaced with the provided properties.
*
- A {@link ServiceEvent} of type {@link ServiceEvent#MODIFIED}
* is synchronously sent.
*
*
* @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 properties
* parameter contains case variants of the same key name.
*/
public void setProperties(Dictionary props) {
final ServiceReferenceImpl ref;
final Map previousProperties;
synchronized (registry) {
synchronized (registrationLock) {
if (state != REGISTERED) { /* in the process of unregisterING */
throw new IllegalStateException(Msg.SERVICE_ALREADY_UNREGISTERED_EXCEPTION);
}
ref = reference; /* used to publish event outside sync */
previousProperties = this.properties;
this.properties = createProperties(props);
}
registry.modifyServiceRegistration(context, this);
}
/* must not hold the registrationLock when this event is published */
registry.publishServiceEvent(new ModifiedServiceEvent(ref, previousProperties));
}
/**
* 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.
*
* The following steps are followed to unregister a service:
*
* - 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.
*
- A {@link ServiceEvent} of type {@link ServiceEvent#UNREGISTERING}
* is synchronously sent so that bundles using this service
* may release their use of the service.
*
- For each bundle whose use count for this service is greater
* than zero:
*
* - The bundle's use count for this service is set to zero.
*
- 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.
*
*
*
* @exception java.lang.IllegalStateException If
* this ServiceRegistration has already been unregistered.
* @see BundleContextImpl#ungetService
*/
public void unregister() {
final ServiceReferenceImpl ref;
synchronized (registry) {
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 (registry.debug.DEBUG_SERVICES) {
Debug.println("unregisterService[" + bundle + "](" + this + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
registry.removeServiceRegistration(context, this);
state = UNREGISTERING; /* mark unregisterING */
ref = reference; /* used to publish event outside sync */
}
}
/* must not hold the registrationLock when this event is published */
registry.publishServiceEvent(new ServiceEvent(ServiceEvent.UNREGISTERING, ref));
int size = 0;
BundleContextImpl[] users = null;
synchronized (registrationLock) {
/* we have published the ServiceEvent, now mark the service fully unregistered */
state = UNREGISTERED;
size = contextsUsing.size();
if (size > 0) {
if (registry.debug.DEBUG_SERVICES) {
Debug.println("unregisterService: releasing users"); //$NON-NLS-1$
}
users = contextsUsing.toArray(new BundleContextImpl[size]);
}
}
/* must not hold the registrationLock while releasing services */
for (int i = 0; i < size; i++) {
releaseService(users[i]);
}
synchronized (registrationLock) {
contextsUsing.clear();
reference = null; /* mark registration dead */
}
/* The properties field must remain valid after unregister completes. */
}
/**
* Is this registration unregistered?
*
* @return true if unregistered; otherwise false.
*/
boolean isUnregistered() {
synchronized (registrationLock) {
return state == UNREGISTERED;
}
}
/**
* 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 ServiceReference getReference() {
return getReferenceImpl();
}
ServiceReferenceImpl getReferenceImpl() {
/* use reference instead of unregistered so that ServiceFactorys, called
* by releaseService after the registration is unregistered, can
* get the ServiceReference. Note this technically may violate the spec
* but makes more sense.
*/
synchronized (registrationLock) {
if (reference == null) {
throw new IllegalStateException(Msg.SERVICE_ALREADY_UNREGISTERED_EXCEPTION);
}
return reference;
}
}
/**
* Count of service properties set by framework for each
* service registration.
*
* - Constants.OBJECTCLASS
* - Constants.SERVICE_ID
* - Constants.SERVICE_BUNDLEID
* - Constants.SERVICE_SCOPE
*
* @see #createProperties(Dictionary)
*/
private static final int FRAMEWORK_SET_SERVICE_PROPERTIES_COUNT = 4;
/**
* Construct a properties object from the dictionary for this
* ServiceRegistration.
*
* @param p The properties for this service.
* @return A Properties object for this ServiceRegistration.
*/
/* @GuardedBy("registrationLock") */
private Map createProperties(Dictionary p) {
assert Thread.holdsLock(registrationLock);
ServiceProperties props = new ServiceProperties(p, FRAMEWORK_SET_SERVICE_PROPERTIES_COUNT);
props.put(Constants.OBJECTCLASS, clazzes);
props.put(Constants.SERVICE_ID, Long.valueOf(serviceid));
props.put(Constants.SERVICE_BUNDLEID, Long.valueOf(bundle.getBundleId()));
final String scope;
if (service instanceof ServiceFactory) {
if (service instanceof PrototypeServiceFactory) {
scope = Constants.SCOPE_PROTOTYPE;
} else {
scope = Constants.SCOPE_BUNDLE;
}
} else {
scope = Constants.SCOPE_SINGLETON;
}
props.put(Constants.SERVICE_SCOPE, scope);
Object ranking = props.get(Constants.SERVICE_RANKING);
if (ranking instanceof Integer) {
serviceranking = ((Integer) ranking).intValue();
} else {
serviceranking = 0;
if (ranking != null) {
registry.getContainer().getEventPublisher().publishFrameworkEvent(FrameworkEvent.WARNING, getBundle(), new ServiceException("Invalid ranking type: " + ranking.getClass(), ServiceException.UNSPECIFIED)); //$NON-NLS-1$
}
}
return props.asUnmodifiableMap();
}
/**
* Return the properties object. This is for framework internal use only.
* @return The service registration's properties.
*/
public Map getProperties() {
synchronized (registrationLock) {
return properties;
}
}
/**
* Get the value of a service's property.
*
* 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 null
if there is
* no property by that name.
*/
Object getProperty(String key) {
synchronized (registrationLock) {
return ServiceProperties.cloneValue(properties.get(key));
}
}
/**
* Get the list of key names for the service's properties.
*
*
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.
*/
String[] getPropertyKeys() {
synchronized (registrationLock) {
return properties.keySet().toArray(new String[0]);
}
}
/**
* Get a copy of the service's properties.
*
*
This method will continue to return the properties 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 A copy of the properties.
*/
Dictionary getPropertiesCopy() {
synchronized (registrationLock) {
return new ServiceProperties(properties);
}
}
/**
* Return the service id for this service.
* @return The service id for this service.
*/
long getId() {
return serviceid;
}
/**
* Return the service ranking for this service.
* @return The service ranking for this service.
*/
int getRanking() {
synchronized (registrationLock) {
return serviceranking;
}
}
String[] getClasses() {
return clazzes;
}
S getServiceObject() {
return service;
}
/**
* Return the bundle which registered the service.
*
* This method will always return null
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.
*/
Bundle getBundle() {
synchronized (registrationLock) {
if (reference == null) {
return null;
}
return bundle;
}
}
/**
* This method returns the bundle which registered the
* service regardless of the registration status of this
* service registration. This is not an OSGi specified
* method.
* @return The bundle which registered the service.
*/
public Bundle getRegisteringBundle() {
return bundle;
}
S getSafeService(BundleContextImpl user, ServiceConsumer consumer) {
try {
return getService(user, consumer);
} catch (IllegalStateException e) {
// can happen if the user is stopped on another thread
return null;
}
}
/**
* Get a service object for the using BundleContext.
*
* @param user BundleContext using service.
* @param consumer The closure for the consumer type.
* @return Service object
*/
S getService(BundleContextImpl user, ServiceConsumer consumer) {
if (isUnregistered()) { /* service unregistered */
return null;
}
Map, ServiceUse>> servicesInUse = user.getServicesInUseMap();
if (servicesInUse == null) { /* user is closed */
user.checkValid(); /* throw exception */
}
if (registry.debug.DEBUG_SERVICES) {
Debug.println("getService[" + user.getBundleImpl() + "](" + this + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
/* Use a while loop to support retry if a call to a ServiceFactory fails */
while (true) {
ServiceUse use;
boolean added = false;
/* Obtain the ServiceUse object for this service by bundle user */
synchronized (servicesInUse) {
user.checkValid();
@SuppressWarnings("unchecked")
ServiceUse u = (ServiceUse) servicesInUse.get(this);
use = u;
if (use == null) {
/* if this is the first use of the service
* optimistically record this service is being used. */
use = newServiceUse(user);
added = true;
synchronized (registrationLock) {
if (state == UNREGISTERED) { /* service unregistered */
return null;
}
servicesInUse.put(this, use);
contextsUsing.add(user);
}
}
}
/* Obtain and return the service object */
synchronized (use) {
/* if another thread removed the ServiceUse, then
* go back to the top and start again */
synchronized (servicesInUse) {
user.checkValid();
if (servicesInUse.get(this) != use) {
continue;
}
}
S serviceObject = consumer.getService(use);
/* if the service factory failed to return an object and
* we created the service use, then remove the
* optimistically added ServiceUse. */
if ((serviceObject == null) && added) {
synchronized (servicesInUse) {
synchronized (registrationLock) {
servicesInUse.remove(this);
contextsUsing.remove(user);
}
}
}
return serviceObject;
}
}
}
/**
* Create a new ServiceObjects for the requesting bundle.
*
* @param user The requesting bundle.
* @return A new ServiceObjects for this service and the requesting bundle.
*/
ServiceObjectsImpl getServiceObjects(BundleContextImpl user) {
if (isUnregistered()) { /* service unregistered */
return null;
}
if (registry.debug.DEBUG_SERVICES) {
Debug.println("getServiceObjects[" + user.getBundleImpl() + "](" + this + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
return new ServiceObjectsImpl<>(user, this);
}
/**
* Create a new ServiceUse object for this service and user.
*
* @param user The bundle using this service.
* @return The ServiceUse object for the bundle using this service.
*/
private ServiceUse newServiceUse(BundleContextImpl user) {
if (service instanceof ServiceFactory) {
if (service instanceof PrototypeServiceFactory) {
return new PrototypeServiceFactoryUse<>(user, this);
}
return new ServiceFactoryUse<>(user, this);
}
return new ServiceUse<>(user, this);
}
/**
* Unget a service for the using BundleContext.
*
* @param user BundleContext using service.
* @param consumer The closure for the consumer type.
* @param serviceObject The service object to release for prototype consumers.
* @return false
if the context bundle's use count for the service
* is zero or if the service has been unregistered,
* otherwise true
.
*/
boolean ungetService(BundleContextImpl user, ServiceConsumer consumer, S serviceObject) {
if (isUnregistered()) {
return false;
}
Map, ServiceUse>> servicesInUse = user.getServicesInUseMap();
if (servicesInUse == null) {
return false;
}
if (registry.debug.DEBUG_SERVICES) {
Debug.println("ungetService[" + user.getBundleImpl() + "](" + this + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
ServiceUse use;
synchronized (servicesInUse) {
@SuppressWarnings("unchecked")
ServiceUse u = (ServiceUse) servicesInUse.get(this);
use = u;
if (use == null) {
return false;
}
}
boolean result;
synchronized (use) {
result = consumer.ungetService(use, serviceObject);
if (use.isEmpty()) { /* service use can be discarded */
synchronized (servicesInUse) {
synchronized (registrationLock) {
servicesInUse.remove(this);
contextsUsing.remove(user);
}
}
}
}
return result;
}
/**
* Release the service for the using BundleContext.
*
* @param user BundleContext using service.
*/
void releaseService(BundleContextImpl user) {
synchronized (registrationLock) {
if (reference == null) { /* registration dead */
return;
}
}
if (registry.debug.DEBUG_SERVICES) {
Debug.println("releaseService[" + user.getBundleImpl() + "](" + this + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
Map, ServiceUse>> servicesInUse = user.getServicesInUseMap();
if (servicesInUse == null) {
return;
}
ServiceUse use;
synchronized (servicesInUse) {
synchronized (registrationLock) {
@SuppressWarnings("unchecked")
ServiceUse u = (ServiceUse) servicesInUse.remove(this);
use = u;
if (use == null) {
return;
}
contextsUsing.remove(user);
}
}
synchronized (use) {
use.release();
}
}
/**
* Return the list of bundle which are using this service.
*
* @return Array of Bundles using this service.
*/
Bundle[] getUsingBundles() {
synchronized (registrationLock) {
if (state == UNREGISTERED) /* service unregistered */
return null;
int size = contextsUsing.size();
if (size == 0)
return null;
/* Copy list of BundleContext into an array of Bundle. */
Bundle[] bundles = new Bundle[size];
for (int i = 0; i < size; i++)
bundles[i] = contextsUsing.get(i).getBundleImpl();
return bundles;
}
}
boolean isAssignableTo(Bundle client, String className) {
return PackageSource.isServiceAssignableTo(bundle, client, className, service.getClass(), context.getContainer());
}
/**
* Return a String representation of this object.
*
* @return String representation of this object.
*/
public String toString() {
int size = clazzes.length;
StringBuilder sb = new StringBuilder(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(getProperties().toString());
return sb.toString();
}
/**
* Compares this ServiceRegistrationImpl
with the specified
* ServiceRegistrationImpl
for order.
*
*
* This does a reverse comparison so that the highest item is sorted to the left.
* We keep ServiceRegistationImpls in sorted lists such that the highest
* ranked service is at element 0 for quick retrieval.
*
* @param other The ServiceRegistrationImpl
to be compared.
* @return Returns a negative integer, zero, or a positive integer if this
* ServiceRegistrationImpl
is greater than, equal to, or
* less than the specified ServiceRegistrationImpl
.
*/
public int compareTo(ServiceRegistrationImpl> other) {
final int thisRanking = this.getRanking();
final int otherRanking = other.getRanking();
if (thisRanking != otherRanking) {
if (thisRanking < otherRanking) {
return 1;
}
return -1;
}
final long thisId = this.getId();
final long otherId = other.getId();
if (thisId == otherId) {
return 0;
}
if (thisId < otherId) {
return -1;
}
return 1;
}
}