blob: e698a6a99f6aae87671dd351ffecf01ea8e08d96 [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.dm.impl.dependencies;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Dictionary;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.apache.felix.dm.ComponentDependencyDeclaration;
import org.apache.felix.dm.Dependency;
import org.apache.felix.dm.DependencyService;
import org.apache.felix.dm.ServiceDependency;
import org.apache.felix.dm.impl.DefaultNullObject;
import org.apache.felix.dm.impl.InvocationUtil;
import org.apache.felix.dm.impl.Logger;
import org.apache.felix.dm.tracker.ServiceTracker;
import org.apache.felix.dm.tracker.ServiceTrackerCustomizer;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.service.log.LogService;
/**
* Service dependency that can track an OSGi service.
*
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
public class ServiceDependencyImpl extends DependencyBase implements ServiceDependency, ServiceTrackerCustomizer, ComponentDependencyDeclaration {
protected List m_services = new ArrayList();
protected volatile ServiceTracker m_tracker;
protected BundleContext m_context;
protected volatile Class m_trackedServiceName;
private Object m_nullObject;
private volatile String m_trackedServiceFilter;
private volatile String m_trackedServiceFilterUnmodified;
private volatile ServiceReference m_trackedServiceReference;
private volatile boolean m_isStarted;
private Object m_callbackInstance;
private String m_callbackAdded;
private String m_callbackChanged;
private String m_callbackRemoved;
private boolean m_autoConfig;
protected ServiceReference m_reference;
protected Object m_serviceInstance;
private String m_autoConfigInstance;
private boolean m_autoConfigInvoked;
private Object m_defaultImplementation;
private Object m_defaultImplementationInstance;
private boolean m_isAvailable;
private ServiceReference[] m_references;
private boolean m_propagate;
private Object m_propagateCallbackInstance;
private String m_propagateCallbackMethod;
private static final Comparator COMPARATOR = new Comparator() {
public int getRank(ServiceReference ref) {
Object ranking = ref.getProperty(Constants.SERVICE_RANKING);
if (ranking != null && (ranking instanceof Integer)) {
return ((Integer) ranking).intValue();
}
return 0;
}
public int compare(Object a, Object b) {
ServiceReference ra = (ServiceReference) a, rb = (ServiceReference) b;
int ranka = getRank(ra);
int rankb = getRank(rb);
if (ranka < rankb) {
return -1;
}
else if (ranka > rankb) {
return 1;
}
return 0;
}
};
/**
* Entry to wrap service properties behind a Map.
*/
private final static class ServicePropertiesMapEntry implements Map.Entry {
private final String m_key;
private Object m_value;
public ServicePropertiesMapEntry(String key, Object value) {
m_key = key;
m_value = value;
}
public Object getKey() {
return m_key;
}
public Object getValue() {
return m_value;
}
public String toString() {
return m_key + "=" + m_value;
}
public Object setValue(Object value) {
Object oldValue = m_value;
m_value = value;
return oldValue;
}
public boolean equals(Object o) {
if (!(o instanceof Map.Entry)) {
return false;
}
Map.Entry e = (Map.Entry) o;
return eq(m_key, e.getKey()) && eq(m_value, e.getValue());
}
public int hashCode() {
return ((m_key == null) ? 0 : m_key.hashCode()) ^ ((m_value == null) ? 0 : m_value.hashCode());
}
private static final boolean eq(Object o1, Object o2) {
return (o1 == null ? o2 == null : o1.equals(o2));
}
}
/**
* Wraps service properties behind a Map.
*/
private final static class ServicePropertiesMap extends AbstractMap {
private final ServiceReference m_ref;
public ServicePropertiesMap(ServiceReference ref) {
m_ref = ref;
}
public Object get(Object key) {
return m_ref.getProperty(key.toString());
}
public int size() {
return m_ref.getPropertyKeys().length;
}
public Set entrySet() {
Set set = new HashSet();
String[] keys = m_ref.getPropertyKeys();
for (int i = 0; i < keys.length; i++) {
set.add(new ServicePropertiesMapEntry(keys[i], m_ref.getProperty(keys[i])));
}
return set;
}
}
/**
* Creates a new service dependency.
*
* @param context the bundle context
* @param logger the logger
*/
public ServiceDependencyImpl(BundleContext context, Logger logger) {
super(logger);
m_context = context;
m_autoConfig = true;
}
/** Copying constructor that clones an existing instance. */
public ServiceDependencyImpl(ServiceDependencyImpl prototype) {
super(prototype);
m_context = prototype.m_context;
m_autoConfig = prototype.m_autoConfig;
m_trackedServiceName = prototype.m_trackedServiceName;
m_nullObject = prototype.m_nullObject;
m_trackedServiceFilter = prototype.m_trackedServiceFilter;
m_trackedServiceFilterUnmodified = prototype.m_trackedServiceFilterUnmodified;
m_trackedServiceReference = prototype.m_trackedServiceReference;
m_callbackInstance = prototype.m_callbackInstance;
m_callbackAdded = prototype.m_callbackAdded;
m_callbackChanged = prototype.m_callbackChanged;
m_callbackRemoved = prototype.m_callbackRemoved;
m_autoConfigInstance = prototype.m_autoConfigInstance;
m_defaultImplementation = prototype.m_defaultImplementation;
}
public Dependency createCopy() {
return new ServiceDependencyImpl(this);
}
public synchronized boolean isAutoConfig() {
return m_autoConfig;
}
public synchronized boolean isAvailable() {
return m_isAvailable;
}
public synchronized Object getService() {
Object service = null;
if (m_isStarted) {
service = m_tracker.getService();
}
if (service == null && isAutoConfig()) {
service = getDefaultImplementation();
if (service == null) {
service = getNullObject();
}
}
return service;
}
public Object lookupService() {
Object service = null;
if (m_isStarted) {
service = getService();
}
else {
ServiceReference[] refs = null;
ServiceReference ref = null;
if (m_trackedServiceName != null) {
if (m_trackedServiceFilter != null) {
try {
refs = m_context.getServiceReferences(m_trackedServiceName.getName(), m_trackedServiceFilter);
if (refs != null) {
Arrays.sort(refs, COMPARATOR);
ref = refs[0];
}
}
catch (InvalidSyntaxException e) {
throw new IllegalStateException("Invalid filter definition for dependency.");
}
}
else if (m_trackedServiceReference != null) {
ref = m_trackedServiceReference;
}
else {
ref = m_context.getServiceReference(m_trackedServiceName.getName());
}
if (ref != null) {
service = m_context.getService(ref);
}
}
else {
throw new IllegalStateException("Could not lookup dependency, no service name specified.");
}
}
if (service == null && isAutoConfig()) {
service = getDefaultImplementation();
if (service == null) {
service = getNullObject();
}
}
return service;
}
// TODO lots of duplication in lookupService()
public ServiceReference lookupServiceReference() {
ServiceReference service = null;
if (m_isStarted) {
service = m_tracker.getServiceReference();
}
else {
ServiceReference[] refs = null;
ServiceReference ref = null;
if (m_trackedServiceName != null) {
if (m_trackedServiceFilter != null) {
try {
refs = m_context.getServiceReferences(m_trackedServiceName.getName(), m_trackedServiceFilter);
if (refs != null) {
Arrays.sort(refs, COMPARATOR);
ref = refs[0];
}
}
catch (InvalidSyntaxException e) {
throw new IllegalStateException("Invalid filter definition for dependency.");
}
}
else if (m_trackedServiceReference != null) {
ref = m_trackedServiceReference;
}
else {
ref = m_context.getServiceReference(m_trackedServiceName.getName());
}
if (ref != null) {
service = ref;
}
}
else {
throw new IllegalStateException("Could not lookup dependency, no service name specified.");
}
}
return service;
}
private Object getNullObject() {
if (m_nullObject == null) {
Class trackedServiceName;
synchronized (this) {
trackedServiceName = m_trackedServiceName;
}
try {
m_nullObject = Proxy.newProxyInstance(trackedServiceName.getClassLoader(), new Class[] {trackedServiceName}, new DefaultNullObject());
}
catch (Exception e) {
m_logger.log(Logger.LOG_ERROR, "Could not create null object for " + trackedServiceName + ".", e);
}
}
return m_nullObject;
}
private Object getDefaultImplementation() {
if (m_defaultImplementation != null) {
if (m_defaultImplementation instanceof Class) {
try {
m_defaultImplementationInstance = ((Class) m_defaultImplementation).newInstance();
}
catch (Exception e) {
m_logger.log(Logger.LOG_ERROR, "Could not create default implementation instance of class " + m_defaultImplementation + ".", e);
}
}
else {
m_defaultImplementationInstance = m_defaultImplementation;
}
}
return m_defaultImplementationInstance;
}
public synchronized Class getInterface() {
return m_trackedServiceName;
}
public void start(DependencyService service) {
boolean needsStarting = false;
synchronized (this) {
m_services.add(service);
if (!m_isStarted) {
if (m_trackedServiceName != null) {
if (m_trackedServiceFilter != null) {
try {
m_tracker = new ServiceTracker(m_context, m_context.createFilter(m_trackedServiceFilter), this);
}
catch (InvalidSyntaxException e) {
throw new IllegalStateException("Invalid filter definition for dependency: " + m_trackedServiceFilter);
}
}
else if (m_trackedServiceReference != null) {
m_tracker = new ServiceTracker(m_context, m_trackedServiceReference, this);
}
else {
m_tracker = new ServiceTracker(m_context, m_trackedServiceName.getName(), this);
}
}
else {
throw new IllegalStateException("Could not create tracker for dependency, no service name specified.");
}
m_isStarted = true;
needsStarting = true;
}
}
if (needsStarting) {
m_tracker.open();
}
}
public void stop(DependencyService service) {
boolean needsStopping = false;
synchronized (this) {
if (m_services.size() == 1 && m_services.contains(service)) {
m_isStarted = false;
needsStopping = true;
}
}
if (needsStopping) {
m_tracker.close();
m_tracker = null;
}
//moved this down
synchronized (this) {
m_services.remove(service);
}
}
public Object addingService(ServiceReference ref) {
Object service = m_context.getService(ref);
// first check to make sure the service is actually an instance of our service
if (!m_trackedServiceName.isInstance(service)) {
return null;
}
return service;
}
public void addedService(ServiceReference ref, Object service) {
boolean makeAvailable = makeAvailable();
Object[] services;
synchronized (this) {
services = m_services.toArray();
}
for (int i = 0; i < services.length; i++) {
DependencyService ds = (DependencyService) services[i];
if (makeAvailable) {
// The dependency callback will be defered until all required dependency are available.
ds.dependencyAvailable(this);
if (!isRequired()) {
// For optional dependency, we always invoke callback, because at this point, we know
// that the service has been started, and the service start method has been called.
// (See the ServiceImpl.bindService method, which will activate optional dependencies using
// startTrackingOptional() method).
invokeAdded(ds, ref, service);
}
}
else {
ds.dependencyChanged(this);
// At this point, either the dependency is optional (meaning that the service has been started,
// because if not, then our dependency would not be active); or the dependency is required,
// meaning that either the service is not yet started, or already started.
// In all cases, we have to inject the required dependency.
invokeAdded(ds, ref, service);
}
}
}
public void modifiedService(ServiceReference ref, Object service) {
Object[] services;
synchronized (this) {
services = m_services.toArray();
}
for (int i = 0; i < services.length; i++) {
DependencyService ds = (DependencyService) services[i];
ds.dependencyChanged(this);
if (ds.isRegistered()) {
invokeChanged(ds, ref, service);
}
}
}
public void removedService(ServiceReference ref, Object service) {
boolean makeUnavailable = makeUnavailable();
Object[] services;
synchronized (this) {
services = m_services.toArray();
}
for (int i = 0; i < services.length; i++) {
DependencyService ds = (DependencyService) services[i];
if (makeUnavailable) {
ds.dependencyUnavailable(this);
if (!isRequired()) {
invokeRemoved(ds, ref, service);
}
}
else {
ds.dependencyChanged(this);
invokeRemoved(ds, ref, service);
}
}
// unget what we got in addingService (see ServiceTracker 701.4.1)
m_context.ungetService(ref);
}
public void invokeAdded(DependencyService dependencyService, ServiceReference reference, Object service) {
invoke(dependencyService, reference, service, m_callbackAdded);
}
public void invokeChanged(DependencyService dependencyService, ServiceReference reference, Object service) {
invoke(dependencyService, reference, service, m_callbackChanged);
}
public void invokeRemoved(DependencyService dependencyService, ServiceReference reference, Object service) {
invoke(dependencyService, reference, service, m_callbackRemoved);
}
public void invoke(DependencyService dependencyService, ServiceReference reference, Object service, String name) {
if (name != null) {
dependencyService.invokeCallbackMethod(getCallbackInstances(dependencyService), name,
new Class[][] {{ServiceReference.class, m_trackedServiceName}, {ServiceReference.class, Object.class}, {ServiceReference.class}, {m_trackedServiceName}, {Object.class}, {}, {Map.class, m_trackedServiceName}},
new Object[][] {{reference, service}, {reference, service}, {reference}, {service}, {service}, {}, {new ServicePropertiesMap(reference), service}}
);
}
}
protected synchronized boolean makeAvailable() {
if (!isAvailable()) {
m_isAvailable = true;
return true;
}
return false;
}
private synchronized boolean makeUnavailable() {
if ((isAvailable()) && (m_tracker.getServiceReference() == null)) {
m_isAvailable = false;
return true;
}
return false;
}
private synchronized Object[] getCallbackInstances(DependencyService dependencyService) {
if (m_callbackInstance == null) {
return dependencyService.getCompositionInstances();
}
else {
return new Object[] { m_callbackInstance };
}
}
// ----- CREATION
/**
* Sets the name of the service that should be tracked.
*
* @param serviceName the name of the service
* @return this service dependency
*/
public synchronized ServiceDependency setService(Class serviceName) {
ensureNotActive();
if (serviceName == null) {
throw new IllegalArgumentException("Service name cannot be null.");
}
m_trackedServiceName = serviceName;
m_trackedServiceReference = null;
m_trackedServiceFilter = null;
return this;
}
/**
* Sets the name of the service that should be tracked. You can either specify
* only the name, or the name and a filter. In the latter case, the filter is used
* to track the service and should only return services of the type that was specified
* in the name. To make sure of this, the filter is actually extended internally to
* filter on the correct name.
*
* @param serviceName the name of the service
* @param serviceFilter the filter condition
* @return this service dependency
*/
public synchronized ServiceDependency setService(Class serviceName, String serviceFilter) {
ensureNotActive();
if (serviceName == null) {
throw new IllegalArgumentException("Service name cannot be null.");
}
m_trackedServiceName = serviceName;
if (serviceFilter != null) {
m_trackedServiceFilterUnmodified = serviceFilter;
m_trackedServiceFilter ="(&(" + Constants.OBJECTCLASS + "=" + serviceName.getName() + ")" + serviceFilter + ")";
}
else {
m_trackedServiceFilterUnmodified = null;
m_trackedServiceFilter = null;
}
m_trackedServiceReference = null;
return this;
}
public synchronized ServiceDependency setService(String serviceFilter) {
ensureNotActive();
if (serviceFilter == null) {
throw new IllegalArgumentException("Service filter cannot be null.");
}
m_trackedServiceName = Object.class;
if (serviceFilter != null) {
m_trackedServiceFilterUnmodified = serviceFilter;
m_trackedServiceFilter = serviceFilter;
}
m_trackedServiceReference = null;
return this;
}
/**
* Sets the name of the service that should be tracked. You can either specify
* only the name, or the name and a reference. In the latter case, the service reference
* is used to track the service and should only return services of the type that was
* specified in the name.
*
* @param serviceName the name of the service
* @param serviceReference the service reference to track
* @return this service dependency
*/
public synchronized ServiceDependency setService(Class serviceName, ServiceReference serviceReference) {
ensureNotActive();
if (serviceName == null) {
throw new IllegalArgumentException("Service name cannot be null.");
}
m_trackedServiceName = serviceName;
m_trackedServiceReference = serviceReference;
m_trackedServiceFilterUnmodified = null;
m_trackedServiceFilter = null;
return this;
}
/**
* Sets the default implementation for this service dependency. You can use this to supply
* your own implementation that will be used instead of a Null Object when the dependency is
* not available. This is also convenient if the service dependency is not an interface
* (which would cause the Null Object creation to fail) but a class.
*
* @param implementation the instance to use or the class to instantiate if you want to lazily
* instantiate this implementation
* @return this service dependency
*/
public synchronized ServiceDependency setDefaultImplementation(Object implementation) {
ensureNotActive();
m_defaultImplementation = implementation;
return this;
}
/**
* Sets the required flag which determines if this service is required or not.
*
* @param required the required flag
* @return this service dependency
*/
public synchronized ServiceDependency setRequired(boolean required) {
ensureNotActive();
setIsRequired(required);
return this;
}
public ServiceDependency setInstanceBound(boolean isInstanceBound) {
setIsInstanceBound(isInstanceBound);
return this;
}
/**
* Sets auto configuration for this service. Auto configuration allows the
* dependency to fill in any attributes in the service implementation that
* are of the same type as this dependency. Default is on.
*
* @param autoConfig the value of auto config
* @return this service dependency
*/
public synchronized ServiceDependency setAutoConfig(boolean autoConfig) {
ensureNotActive();
m_autoConfig = autoConfig;
m_autoConfigInvoked = true;
return this;
}
/**
* Sets auto configuration for this service. Auto configuration allows the
* dependency to fill in the attribute in the service implementation that
* has the same type and instance name.
*
* @param instanceName the name of attribute to auto config
* @return this service dependency
*/
public synchronized ServiceDependency setAutoConfig(String instanceName) {
ensureNotActive();
m_autoConfig = (instanceName != null);
m_autoConfigInstance = instanceName;
m_autoConfigInvoked = true;
return this;
}
/**
* Sets the callbacks for this service. These callbacks can be used as hooks whenever a
* dependency is added or removed. When you specify callbacks, the auto configuration
* feature is automatically turned off, because we're assuming you don't need it in this
* case.
*
* @param added the method to call when a service was added
* @param removed the method to call when a service was removed
* @return this service dependency
*/
public synchronized ServiceDependency setCallbacks(String added, String removed) {
return setCallbacks(null, added, null, removed);
}
/**
* Sets the callbacks for this service. These callbacks can be used as hooks whenever a
* dependency is added, changed or removed. When you specify callbacks, the auto
* configuration feature is automatically turned off, because we're assuming you don't
* need it in this case.
*
* @param added the method to call when a service was added
* @param changed the method to call when a service was changed
* @param removed the method to call when a service was removed
* @return this service dependency
*/
public synchronized ServiceDependency setCallbacks(String added, String changed, String removed) {
return setCallbacks(null, added, changed, removed);
}
/**
* Sets the callbacks for this service. These callbacks can be used as hooks whenever a
* dependency is added or removed. They are called on the instance you provide. When you
* specify callbacks, the auto configuration feature is automatically turned off, because
* we're assuming you don't need it in this case.
*
* @param instance the instance to call the callbacks on
* @param added the method to call when a service was added
* @param removed the method to call when a service was removed
* @return this service dependency
*/
public synchronized ServiceDependency setCallbacks(Object instance, String added, String removed) {
return setCallbacks(instance, added, null, removed);
}
/**
* Sets the callbacks for this service. These callbacks can be used as hooks whenever a
* dependency is added, changed or removed. They are called on the instance you provide. When you
* specify callbacks, the auto configuration feature is automatically turned off, because
* we're assuming you don't need it in this case.
*
* @param instance the instance to call the callbacks on
* @param added the method to call when a service was added
* @param changed the method to call when a service was changed
* @param removed the method to call when a service was removed
* @return this service dependency
*/
public synchronized ServiceDependency setCallbacks(Object instance, String added, String changed, String removed) {
ensureNotActive();
// if at least one valid callback is specified, we turn off auto configuration, unless
// someone already explicitly invoked autoConfig
if ((added != null || removed != null || changed != null) && ! m_autoConfigInvoked) {
setAutoConfig(false);
}
m_callbackInstance = instance;
m_callbackAdded = added;
m_callbackChanged = changed;
m_callbackRemoved = removed;
return this;
}
private void ensureNotActive() {
if (m_tracker != null) {
throw new IllegalStateException("Cannot modify state while active.");
}
}
public synchronized String toString() {
return "ServiceDependency[" + m_trackedServiceName + " " + m_trackedServiceFilterUnmodified + "]";
}
public String getAutoConfigName() {
return m_autoConfigInstance;
}
public Object getAutoConfigInstance() {
return lookupService();
}
public Class getAutoConfigType() {
return getInterface();
}
public String getName() {
StringBuilder sb = new StringBuilder();
if (m_trackedServiceName != null) {
sb.append(m_trackedServiceName.getName());
if (m_trackedServiceFilterUnmodified != null) {
sb.append(' ');
sb.append(m_trackedServiceFilterUnmodified);
}
}
if (m_trackedServiceReference != null) {
sb.append("{service.id=" + m_trackedServiceReference.getProperty(Constants.SERVICE_ID)+"}");
}
return sb.toString();
}
public int getState() {
return (isAvailable() ? 1 : 0) + (isRequired() ? 2 : 0);
}
public String getType() {
return "service";
}
public void invokeAdded(DependencyService service) {
ServiceReference[] refs = m_tracker.getServiceReferences();
if (refs != null) {
for (int i = 0; i < refs.length; i++) {
ServiceReference sr = refs[i];
Object svc = m_context.getService(sr);
invokeAdded(service, sr, svc);
}
}
m_references = refs;
}
public void invokeRemoved(DependencyService service) {
ServiceReference[] refs = m_references;
if (refs != null) {
for (int i = 0; i < refs.length; i++) {
ServiceReference sr = refs[i];
Object svc = m_context.getService(sr);
invokeRemoved(service, sr, svc);
}
}
m_references = null;
}
public Dictionary getProperties() {
ServiceReference reference = lookupServiceReference();
Object service = lookupService();
if (reference != null) {
if (m_propagateCallbackInstance != null && m_propagateCallbackMethod != null) {
try {
return (Dictionary) InvocationUtil.invokeCallbackMethod(m_propagateCallbackInstance, m_propagateCallbackMethod, new Class[][] {{ ServiceReference.class, Object.class }, { ServiceReference.class }}, new Object[][] {{ reference, service }, { reference }});
}
catch (InvocationTargetException e) {
m_logger.log(LogService.LOG_WARNING, "Exception while invoking callback method", e.getCause());
}
catch (Exception e) {
m_logger.log(LogService.LOG_WARNING, "Exception while trying to invoke callback method", e);
}
throw new IllegalStateException("Could not invoke callback");
}
else {
Properties props = new Properties();
String[] keys = reference.getPropertyKeys();
for (int i = 0; i < keys.length; i++) {
if (!(keys[i].equals(Constants.SERVICE_ID) || keys[i].equals(Constants.SERVICE_PID))) {
props.put(keys[i], reference.getProperty(keys[i]));
}
}
return props;
}
}
else {
throw new IllegalStateException("cannot find service reference");
}
}
public boolean isPropagated() {
return m_propagate;
}
public ServiceDependency setPropagate(boolean propagate) {
ensureNotActive();
m_propagate = propagate;
return this;
}
public ServiceDependency setPropagate(Object instance, String method) {
setPropagate(instance != null && method != null);
m_propagateCallbackInstance = instance;
m_propagateCallbackMethod = method;
return this;
}
}