blob: 95c5cf3e332acedd0ff44bddaa2ba9ce043b1d3d [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.aries.blueprint.container;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicBoolean;
import net.sf.cglib.proxy.Dispatcher;
import net.sf.cglib.proxy.Enhancer;
import org.apache.aries.blueprint.BlueprintConstants;
import org.apache.aries.blueprint.ExtendedBlueprintContainer;
import org.apache.aries.blueprint.ExtendedReferenceListMetadata;
import org.apache.aries.blueprint.ExtendedServiceReferenceMetadata;
import org.apache.aries.blueprint.di.AbstractRecipe;
import org.apache.aries.blueprint.di.CollectionRecipe;
import org.apache.aries.blueprint.di.Recipe;
import org.apache.aries.blueprint.utils.BundleDelegatingClassLoader;
import org.apache.aries.blueprint.utils.ReflectionUtils;
import org.osgi.framework.Bundle;
import org.osgi.framework.Constants;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceReference;
import org.osgi.service.blueprint.container.ComponentDefinitionException;
import org.osgi.service.blueprint.container.ReifiedType;
import org.osgi.service.blueprint.reflect.ReferenceListener;
import org.osgi.service.blueprint.reflect.ReferenceMetadata;
import org.osgi.service.blueprint.reflect.ServiceReferenceMetadata;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Abstract class for service reference recipes.
*
* TODO: if we have a single interface (which is the standard behavior), then we should be able to get rid of
* the proxyClassloader and just use this interface classloader to define the proxy
*
* TODO: it is allowed to have no interface defined at all, which should result in an empty proxy
*
* @version $Rev: 950985 $, $Date: 2010-06-03 14:19:22 +0100 (Thu, 03 Jun 2010) $
*/
public abstract class AbstractServiceReferenceRecipe extends AbstractRecipe implements ServiceListener, SatisfiableRecipe {
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractServiceReferenceRecipe.class);
protected final ExtendedBlueprintContainer blueprintContainer;
protected final ServiceReferenceMetadata metadata;
protected final CollectionRecipe listenersRecipe;
protected final List<Recipe> explicitDependencies;
protected final ClassLoader proxyClassLoader;
protected final boolean optional;
/** The OSGi filter for tracking references */
protected final String filter;
/** The list of listeners for this reference. This list will be lazy created */
protected List<Listener> listeners;
private final List<ServiceReference> references = new ArrayList<ServiceReference>();
private final AtomicBoolean started = new AtomicBoolean();
private final AtomicBoolean satisfied = new AtomicBoolean();
private SatisfactionListener satisfactionListener;
private volatile ProxyFactory proxyFactory;
protected AbstractServiceReferenceRecipe(String name,
ExtendedBlueprintContainer blueprintContainer,
ServiceReferenceMetadata metadata,
CollectionRecipe listenersRecipe,
List<Recipe> explicitDependencies) {
super(name);
this.prototype = false;
this.blueprintContainer = blueprintContainer;
this.metadata = metadata;
this.listenersRecipe = listenersRecipe;
this.explicitDependencies = explicitDependencies;
this.proxyClassLoader = makeProxyClassLoader(blueprintContainer, metadata);
this.optional = (metadata.getAvailability() == ReferenceMetadata.AVAILABILITY_OPTIONAL);
this.filter = createOsgiFilter(metadata);
}
// Create a ClassLoader delegating to the bundle, but also being able to see our bundle classes
// so that the created proxy can access cglib classes.
// TODO: we should be able to get rid of this classloader when using JDK 1.4 proxies with a single interface
// (the case defined by the spec) and use the interface classloader instead
private ClassLoader makeProxyClassLoader(
final ExtendedBlueprintContainer blueprintContainer,
ServiceReferenceMetadata metadata) {
String typeName = metadata.getInterface();
Class typeClass = metadata instanceof ExtendedServiceReferenceMetadata
? ((ExtendedServiceReferenceMetadata) metadata).getRuntimeInterface() : null;
if (typeName == null && typeClass == null) {
return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
public ClassLoader run() {
return new BundleDelegatingClassLoader(blueprintContainer.getBundleContext().getBundle(),
AbstractServiceReferenceRecipe.class.getClassLoader());
}
});
}
final ClassLoader interfaceClassLoader;
if (typeClass != null) {
interfaceClassLoader = typeClass.getClassLoader();
} else {
try {
Bundle clientBundle = blueprintContainer.getBundleContext().getBundle();
interfaceClassLoader = clientBundle.loadClass(typeName).getClassLoader();
} catch (ClassNotFoundException cnfe) {
throw new ComponentDefinitionException("Unable to load class " + typeName + " from recipe " + this, cnfe);
}
}
return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
public ClassLoader run() {
return new DualClassloader(interfaceClassLoader);
}
});
}
private static class DualClassloader extends ClassLoader {
DualClassloader(ClassLoader parent) {
super(parent);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
return getClass().getClassLoader().loadClass(name);
}
@Override
protected URL findResource(String name) {
return getClass().getClassLoader().getResource(name);
}
}
public CollectionRecipe getListenersRecipe() {
return listenersRecipe;
}
public void start(SatisfactionListener listener) {
if (listener == null) throw new NullPointerException("satisfactionListener is null");
if (started.compareAndSet(false, true)) {
try {
satisfactionListener = listener;
satisfied.set(optional);
// Synchronized block on references so that service events won't interfere with initial references tracking
// though this may not be sufficient because we don't control ordering of those events
synchronized (references) {
blueprintContainer.getBundleContext().addServiceListener(this, getOsgiFilter());
ServiceReference[] references = blueprintContainer.getBundleContext().getServiceReferences(null, getOsgiFilter());
if (references != null) {
for (ServiceReference reference : references) {
this.references.add(reference);
track(reference);
}
satisfied.set(optional || !this.references.isEmpty());
}
LOGGER.debug("Found initial references {} for OSGi service {}", references, getOsgiFilter());
}
} catch (InvalidSyntaxException e) {
throw new ComponentDefinitionException(e);
}
}
}
public void stop() {
if (started.compareAndSet(true, false)) {
synchronized (references) {
blueprintContainer.getBundleContext().removeServiceListener(this);
doStop();
for (Iterator<ServiceReference> it = references.iterator(); it.hasNext();) {
ServiceReference ref = it.next();
it.remove();
untrack(ref);
}
satisfied.set(false);
}
}
}
protected void doStop() {
}
protected boolean isStarted() {
return started.get();
}
public boolean isSatisfied() {
return satisfied.get();
}
@Override
public List<Recipe> getConstructorDependencies() {
List<Recipe> recipes = new ArrayList<Recipe>();
if (explicitDependencies != null) {
recipes.addAll(explicitDependencies);
}
return recipes;
}
public List<Recipe> getDependencies() {
List<Recipe> recipes = new ArrayList<Recipe>();
if (listenersRecipe != null) {
recipes.add(listenersRecipe);
}
recipes.addAll(getConstructorDependencies());
return recipes;
}
public String getOsgiFilter() {
return filter;
}
protected void createListeners() {
try {
if (listenersRecipe != null) {
List<Listener> listeners = (List<Listener>) listenersRecipe.create();
for (Listener listener : listeners) {
List<Class> cl = new ArrayList<Class>();
if (metadata instanceof ExtendedServiceReferenceMetadata && ((ExtendedServiceReferenceMetadata) metadata).getRuntimeInterface() != null) {
cl.add(((ExtendedServiceReferenceMetadata) metadata).getRuntimeInterface());
} else if (metadata.getInterface() != null) {
cl.addAll(loadAllClasses(Collections.singletonList(metadata.getInterface())));
} else {
cl.add(Object.class);
}
listener.init(cl);
}
this.listeners = listeners;
} else {
this.listeners = Collections.emptyList();
}
} catch (ClassNotFoundException e) {
throw new ComponentDefinitionException(e);
}
}
protected List<Class> loadAllClasses(Iterable<String> interfaceNames) throws ClassNotFoundException {
List<Class> classes = new ArrayList<Class>();
for (String name : interfaceNames) {
Class clazz = loadClass(name);
classes.add(clazz);
}
return classes;
}
protected ReifiedType loadType(String typeName, ClassLoader fromClassLoader) {
if (typeName == null) {
return null;
}
try {
// this method is overriden to use the blueprint container directly
// because proxies can be created outside of the recipe creation which
// would lead to an exception because the context is not set
// TODO: consider having the context as a property on the recipe rather than a thread local
return GenericType.parse(typeName, fromClassLoader != null ? fromClassLoader : blueprintContainer);
} catch (ClassNotFoundException e) {
throw new ComponentDefinitionException("Unable to load class " + typeName + " from recipe " + this, e);
}
}
protected Object createProxy(final Callable<Object> dispatcher, Set<Class> interfaces) throws Exception {
if (!interfaces.iterator().hasNext()) {
return new Object();
} else {
return getProxyFactory().createProxy(proxyClassLoader, toClassArray(interfaces), dispatcher);
}
}
protected synchronized ProxyFactory getProxyFactory() throws ClassNotFoundException {
if (proxyFactory == null) {
boolean proxyClass = false;
if (metadata instanceof ExtendedServiceReferenceMetadata) {
proxyClass = (((ExtendedServiceReferenceMetadata) metadata).getProxyMethod() & ExtendedServiceReferenceMetadata.PROXY_METHOD_CLASSES) != 0;
}
if (!proxyClass) {
Set<Class> interfaces = new HashSet<Class>();
if (metadata.getInterface() != null) {
interfaces.add(loadClass(metadata.getInterface()));
}
if (metadata instanceof ExtendedReferenceListMetadata
&& ((ExtendedServiceReferenceMetadata) metadata).getRuntimeInterface() != null) {
interfaces.add(((ExtendedServiceReferenceMetadata) metadata).getRuntimeInterface());
}
for (Class cl : interfaces) {
if (!cl.isInterface()) {
throw new ComponentDefinitionException("A class " + cl.getName() + " was found in the interfaces list, but class proxying is not allowed by default. The ext:proxy-method='classes' attribute needs to be added to this service reference.");
}
}
}
try {
// Try load load a cglib class (to make sure it's actually available
// then create the cglib factory
getClass().getClassLoader().loadClass("net.sf.cglib.proxy.Enhancer");
proxyFactory = new CgLibProxyFactory();
} catch (Throwable t) {
if (proxyClass) {
throw new ComponentDefinitionException("Class proxying has been enabled but cglib can not be used", t);
}
proxyFactory = new JdkProxyFactory();
}
}
return proxyFactory;
}
public void serviceChanged(ServiceEvent event) {
int eventType = event.getType();
ServiceReference ref = event.getServiceReference();
switch (eventType) {
case ServiceEvent.REGISTERED:
serviceAdded(ref);
break;
case ServiceEvent.MODIFIED:
serviceModified(ref);
break;
case ServiceEvent.UNREGISTERING:
serviceRemoved(ref);
break;
}
}
private void serviceAdded(ServiceReference ref) {
LOGGER.debug("Tracking reference {} for OSGi service {}", ref, getOsgiFilter());
synchronized (references) {
references.add(ref);
}
track(ref);
setSatisfied(true);
}
private void serviceModified(ServiceReference ref) {
// ref must be in references and must be satisfied
track(ref);
}
private void serviceRemoved(ServiceReference ref) {
LOGGER.debug("Untracking reference {} for OSGi service {}", ref, getOsgiFilter());
boolean removed;
boolean satisfied;
synchronized (references) {
removed = references.remove(ref);
satisfied = optional || !references.isEmpty();
}
if (removed) {
untrack(ref);
}
setSatisfied(satisfied);
}
protected void setSatisfied(boolean s) {
// This check will ensure an atomic comparision and set
// so that it will only be true if the value actually changed
if (satisfied.getAndSet(s) != s) {
LOGGER.debug("Service reference with filter {} satisfied {}", getOsgiFilter(), this.satisfied);
this.satisfactionListener.notifySatisfaction(this);
}
}
protected abstract void track(ServiceReference reference);
protected abstract void untrack(ServiceReference reference);
protected abstract void retrack();
protected void updateListeners() {
if (references.isEmpty()) {
unbind(null, null);
} else {
retrack();
}
}
protected void bind(ServiceReference reference, Object service) {
if (listeners != null) {
for (Listener listener : listeners) {
if (listener != null) {
listener.bind(reference, service);
}
}
}
}
protected void unbind(ServiceReference reference, Object service) {
if (listeners != null) {
for (Listener listener : listeners) {
if (listener != null) {
listener.unbind(reference, service);
}
}
}
}
public List<ServiceReference> getServiceReferences() {
synchronized (references) {
return new ArrayList<ServiceReference>(references);
}
}
public ServiceReference getBestServiceReference() {
synchronized (references) {
int length = references.size();
if (length == 0) { /* if no service is being tracked */
return null;
}
int index = 0;
if (length > 1) { /* if more than one service, select highest ranking */
int maxRanking = Integer.MIN_VALUE;
long minId = Long.MAX_VALUE;
for (int i = 0; i < length; i++) {
Object property = references.get(i).getProperty(Constants.SERVICE_RANKING);
int ranking = (property instanceof Integer) ? (Integer) property : 0;
long id = (Long) references.get(i).getProperty(Constants.SERVICE_ID);
if ((ranking > maxRanking) || (ranking == maxRanking && id < minId)) {
index = i;
maxRanking = ranking;
minId = id;
}
}
}
return references.get(index);
}
}
public static class Listener {
private static final Logger LOGGER = LoggerFactory.getLogger(Listener.class);
private Object listener;
private ReferenceListener metadata;
private ExtendedBlueprintContainer blueprintContainer;
private Set<Method> bindMethodsReference = new HashSet<Method>();
private Set<Method> bindMethodsObjectProp = new HashSet<Method>();
private Set<Method> bindMethodsObject = new HashSet<Method>();
private Set<Method> unbindMethodsReference = new HashSet<Method>();
private Set<Method> unbindMethodsObject = new HashSet<Method>();
private Set<Method> unbindMethodsObjectProp = new HashSet<Method>();
public void setListener(Object listener) {
this.listener = listener;
}
public void setMetadata(ReferenceListener metadata) {
this.metadata = metadata;
}
public void setBlueprintContainer(ExtendedBlueprintContainer blueprintContainer) {
this.blueprintContainer = blueprintContainer;
}
public void init(Collection<Class> classes) {
Set<Class> clazzes = new HashSet<Class>(classes);
clazzes.add(Object.class);
Class listenerClass = listener.getClass();
String bindName = metadata.getBindMethod();
if (bindName != null) {
bindMethodsReference.addAll(ReflectionUtils.findCompatibleMethods(listenerClass, bindName, new Class[] { ServiceReference.class }));
for (Class clazz : clazzes) {
bindMethodsObject.addAll(ReflectionUtils.findCompatibleMethods(listenerClass, bindName, new Class[] { clazz }));
bindMethodsObjectProp.addAll(ReflectionUtils.findCompatibleMethods(listenerClass, bindName, new Class[] { clazz, Map.class }));
}
if (bindMethodsReference.size() + bindMethodsObject.size() + bindMethodsObjectProp.size() == 0) {
throw new ComponentDefinitionException("No matching methods found for listener bind method: " + bindName);
}
}
String unbindName = metadata.getUnbindMethod();
if (unbindName != null) {
unbindMethodsReference.addAll(ReflectionUtils.findCompatibleMethods(listenerClass, unbindName, new Class[] { ServiceReference.class }));
for (Class clazz : clazzes) {
unbindMethodsObject.addAll(ReflectionUtils.findCompatibleMethods(listenerClass, unbindName, new Class[] { clazz }));
unbindMethodsObjectProp.addAll(ReflectionUtils.findCompatibleMethods(listenerClass, unbindName, new Class[] { clazz, Map.class }));
}
if (unbindMethodsReference.size() + unbindMethodsObject.size() + unbindMethodsObjectProp.size() == 0) {
throw new ComponentDefinitionException("No matching methods found for listener unbind method: " + unbindName);
}
}
}
public void bind(ServiceReference reference, Object service) {
invokeMethods(bindMethodsReference, bindMethodsObject, bindMethodsObjectProp, reference, service);
}
public void unbind(ServiceReference reference, Object service) {
invokeMethods(unbindMethodsReference, unbindMethodsObject, unbindMethodsObjectProp, reference, service);
}
private void invokeMethods(Set<Method> referenceMethods, Set<Method> objectMethods, Set<Method> objectPropMethods, ServiceReference reference, Object service) {
for (Method method : referenceMethods) {
try {
ReflectionUtils.invoke(blueprintContainer.getAccessControlContext(),
method, listener, reference);
} catch (Exception e) {
LOGGER.error("Error calling listener method " + method, e);
}
}
for (Method method : objectMethods) {
try {
ReflectionUtils.invoke(blueprintContainer.getAccessControlContext(),
method, listener, service);
} catch (Exception e) {
LOGGER.error("Error calling listener method " + method, e);
}
}
Map<String, Object> props = null;
for (Method method : objectPropMethods) {
if (props == null) {
props = new HashMap<String, Object>();
if (reference != null) {
for (String name : reference.getPropertyKeys()) {
props.put(name, reference.getProperty(name));
}
}
}
try {
ReflectionUtils.invoke(blueprintContainer.getAccessControlContext(),
method, listener, service, props);
} catch (Exception e) {
LOGGER.error("Error calling listener method " + method, e);
}
}
}
}
/**
* Create the OSGi filter corresponding to the ServiceReferenceMetadata constraints
*
* @param metadata the service reference metadata
* @return the OSGi filter
*/
private static String createOsgiFilter(ServiceReferenceMetadata metadata) {
List<String> members = new ArrayList<String>();
// Handle filter
String flt = metadata.getFilter();
if (flt != null && flt.length() > 0) {
if (!flt.startsWith("(")) {
flt = "(" + flt + ")";
}
members.add(flt);
}
// Handle interfaces
String interfaceName = metadata.getInterface();
if (metadata instanceof ExtendedServiceReferenceMetadata && ((ExtendedServiceReferenceMetadata) metadata).getRuntimeInterface() != null) {
interfaceName = ((ExtendedServiceReferenceMetadata) metadata).getRuntimeInterface().getName();
}
if (interfaceName != null && interfaceName.length() > 0) {
members.add("(" + Constants.OBJECTCLASS + "=" + interfaceName + ")");
}
// Handle component name
String componentName = metadata.getComponentName();
if (componentName != null && componentName.length() > 0) {
members.add("(" + BlueprintConstants.COMPONENT_NAME_PROPERTY + "=" + componentName + ")");
}
// Create filter
if (members.isEmpty()) {
throw new IllegalStateException("No constraints were specified on the service reference");
}
if (members.size() == 1) {
return members.get(0);
}
StringBuilder sb = new StringBuilder("(&");
for (String member : members) {
sb.append(member);
}
sb.append(")");
return sb.toString();
}
private static Class[] getInterfaces(Class[] classes) {
Set<Class> interfaces = new HashSet<Class>();
for (Class clazz : classes) {
if (clazz.isInterface()) {
interfaces.add(clazz);
}
}
return toClassArray(interfaces);
}
private static Class[] toClassArray(Set<Class> classes) {
return classes.toArray(new Class [classes.size()]);
}
public static interface ProxyFactory {
public Object createProxy(ClassLoader classLoader, Class[] classes, Callable<Object> dispatcher);
}
public static class JdkProxyFactory implements ProxyFactory {
public Object createProxy(final ClassLoader classLoader, final Class[] classes, final Callable<Object> dispatcher) {
return Proxy.newProxyInstance(classLoader, getInterfaces(classes), new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
return method.invoke(dispatcher.call(), args);
} catch (InvocationTargetException ite) {
throw ite.getTargetException();
}
}
});
}
}
public static class CgLibProxyFactory implements ProxyFactory {
public Object createProxy(final ClassLoader classLoader, final Class[] classes, final Callable<Object> dispatcher) {
Enhancer e = new Enhancer();
e.setClassLoader(classLoader);
e.setSuperclass(getTargetClass(classes));
e.setInterfaces(getInterfaces(classes));
e.setInterceptDuringConstruction(false);
e.setCallback(new Dispatcher() {
public Object loadObject() throws Exception {
return dispatcher.call();
}
});
e.setUseFactory(false);
return e.create();
}
protected Class<?> getTargetClass(Class<?>[] interfaceNames) {
// Only allow class proxying if specifically asked to
Class<?> root = Object.class;
for (Class<?> clazz : interfaceNames) {
if (!clazz.isInterface()) {
if (root.isAssignableFrom(clazz)) {
root = clazz;
} else if (clazz.isAssignableFrom(root)) {
//nothing to do, root is correct
} else {
throw new ComponentDefinitionException("Classes " + root.getClass().getName() + " and " + clazz.getName() + " are not in the same hierarchy");
}
}
}
return root;
}
}
}