/**
*
* Copyright 2004 The Apache Software Foundation
*
* Licensed 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.geronimo.kernel.basic;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.reflect.FastClass;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.geronimo.gbean.GBeanInfo;
import org.apache.geronimo.kernel.GBeanNotFoundException;
import org.apache.geronimo.kernel.Kernel;
import org.apache.geronimo.kernel.proxy.ProxyFactory;
import org.apache.geronimo.kernel.proxy.ProxyManager;
import org.apache.geronimo.kernel.proxy.ProxyCreationException;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.Map;
import java.util.Collections;
import java.lang.reflect.InvocationTargetException;
/**
* Creates proxies that communicate directly with a Kernel located in the same
* JVM as the client.
*
* @version $Rev$ $Date$
*/
public class BasicProxyManager implements ProxyManager {
private final static String MANAGED_BEAN_NAME = "org.apache.geronimo.kernel.proxy.GeronimoManagedBean";
private final static Log log = LogFactory.getLog(BasicProxyManager.class);
private final Kernel kernel;
private final Map interceptors = Collections.synchronizedMap(new BasicProxyMap());
public BasicProxyManager(Kernel kernel) {
this.kernel = kernel;
}
/**
* Creates a proxy factory for GBeans of the specified type. The proxy class will be created within the class
* loader from which the specified type was loaded, or from the system class loader if the specified type has
* a null class loader.
*
* @param type the type of the proxies this factory should create
* @return the proxy factory
*/
public ProxyFactory createProxyFactory(Class type) {
if (type == null) throw new NullPointerException("type is null");
ClassLoader classLoader = type.getClassLoader();
if(classLoader == null) {
classLoader = getClass().getClassLoader();
}
if(classLoader == null) {
classLoader = ClassLoader.getSystemClassLoader();
}
return createProxyFactory(new Class[] {type}, classLoader);
}
public ProxyFactory createProxyFactory(Class[] types, ClassLoader classLoader) {
if (types == null) throw new NullPointerException("type is null");
if (types.length == 0) throw new IllegalArgumentException("interface list is empty");
if (classLoader == null) throw new NullPointerException("classLoader is null");
Class managedBean = null;
try {
managedBean = classLoader.loadClass(MANAGED_BEAN_NAME);
} catch (ClassNotFoundException e) {
// Can't load GeronimoManagedBean if the incoming type doesn't have a ClassLoader set
log.debug("Unable to add GeronimoManagedBean to proxy (specified class loader does not have class)");
}
if(managedBean != null) {
Class[] adjusted = new Class[types.length+1];
System.arraycopy(types, 0, adjusted, 0, types.length);
adjusted[types.length] = managedBean;
types = adjusted;
}
return new ManagedProxyFactory(types, classLoader);
}
public Object createProxy(ObjectName target, Class type) {
if (target == null) throw new NullPointerException("target is null");
if (type == null) throw new NullPointerException("type is null");
ProxyFactory proxyFactory = createProxyFactory(type);
Object proxy = proxyFactory.createProxy(target);
return proxy;
}
public Object createProxy(ObjectName target, ClassLoader classLoader) {
if (target == null) throw new NullPointerException("target is null");
if (classLoader == null) throw new NullPointerException("classLoader is null");
try {
GBeanInfo info = kernel.getGBeanInfo(target);
Set interfaces = info.getInterfaces();
if(interfaces.size() == 0) {
log.warn("No interfaces found for " + target + " ("+info.getClassName()+")");
return null;
}
String[] names = (String[]) interfaces.toArray(new String[0]);
List intfs = new ArrayList();
for (int i = 0; i < names.length; i++) {
try {
intfs.add(classLoader.loadClass(names[i]));
} catch (ClassNotFoundException e) {
log.warn("Could not load interface "+names[i]+" in provided ClassLoader for "+target.getKeyProperty("name"));
}
}
return createProxyFactory((Class[]) intfs.toArray(new Class[intfs.size()]), classLoader).createProxy(target);
} catch (GBeanNotFoundException e) {
throw new IllegalArgumentException("Could not get GBeanInfo for target object: " + target);
}
}
public Object[] createProxies(String[] objectNameStrings, ClassLoader classLoader) throws MalformedObjectNameException {
Object[] result = new Object[objectNameStrings.length];
for (int i = 0; i < result.length; i++) {
result[i] = createProxy(ObjectName.getInstance(objectNameStrings[i]), classLoader);
}
return result;
}
public void destroyProxy(Object proxy) {
if (proxy == null) {
return;
}
MethodInterceptor methodInterceptor = (MethodInterceptor) interceptors.remove(proxy);
if (methodInterceptor != null) {
doDestroy(methodInterceptor);
}
}
public boolean isProxy(Object proxy) {
return interceptors.containsKey(proxy);
}
public ObjectName getProxyTarget(Object proxy) {
MethodInterceptor methodInterceptor = (MethodInterceptor) interceptors.get(proxy);
if (methodInterceptor == null) {
return null;
}
return getObjectName(methodInterceptor);
}
private class ManagedProxyFactory implements ProxyFactory {
private final Class proxyType;
private final FastClass fastClass;
public ManagedProxyFactory(Class type, ClassLoader classLoader) {
this(new Class[]{type}, classLoader);
}
public ManagedProxyFactory(Class[] type, ClassLoader classLoader) {
Enhancer enhancer = new Enhancer();
if(type.length > 1) { // shrink first -- may reduce from many to one
type = reduceInterfaces(type);
}
if(type.length == 0) {
throw new IllegalArgumentException("Cannot generate proxy for 0 interfaces!");
} else if(type.length == 1) { // Unlikely (as a result of GeronimoManagedBean)
enhancer.setSuperclass(type[0]);
} else {
if(type[0].isInterface()) {
enhancer.setSuperclass(Object.class);
enhancer.setInterfaces(type);
} else { // there's a class and reduceInterfaces put the class in the first spot
Class[] intfs = new Class[type.length-1];
System.arraycopy(type, 1, intfs, 0, intfs.length);
enhancer.setSuperclass(type[0]);
enhancer.setInterfaces(intfs);
}
}
enhancer.setClassLoader(classLoader);
enhancer.setCallbackType(MethodInterceptor.class);
enhancer.setUseFactory(false);
proxyType = enhancer.createClass();
fastClass = FastClass.create(proxyType);
}
public Object createProxy(ObjectName target) {
assert target != null: "target is null";
Callback callback = getMethodInterceptor(proxyType, kernel, target);
Enhancer.registerCallbacks(proxyType, new Callback[]{callback});
try {
Object proxy = fastClass.newInstance();
interceptors.put(proxy, callback);
return proxy;
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
} else if (cause instanceof Error) {
throw (RuntimeException) cause;
} else if (cause != null) {
throw new ProxyCreationException(cause);
} else {
throw new ProxyCreationException(e);
}
}
}
/**
* If there are multiple interfaces, and some of them extend each other,
* eliminate the superclass in favor of the subclasses that extend them.
*
* If one of the entries is a class (not an interface), make sure it's
* the first one in the array. If more than one of the entries is a
* class, throws an IllegalArgumentException
*
* @param source the original list of interfaces
* @return the equal or smaller list of interfaces
*/
private Class[] reduceInterfaces(Class[] source) {
boolean changed = false;
Class cls = null;
for (int i = 0; i < source.length-1; i++) {
Class original = source[i];
if(original == null) {
continue;
}
if(!original.isInterface()) {
if(cls != null) {
throw new IllegalArgumentException(original.getName()+" is not an interface (already have "+cls.getName()+"); can only have one non-interface class for proxy");
} else {
cls = original;
}
}
for (int j = i+1; j < source.length; j++) {
Class other = source[j];
if(other == null) {
continue;
}
if(!other.isInterface()) {
if(cls != null) {
throw new IllegalArgumentException(other.getName()+" is not an interface (already have "+cls.getName()+"); can only have one non-interface class for proxy");
} else {
cls = other;
}
}
if(other.isAssignableFrom(original)) {
source[j] = null; // clear out "other"
changed = true;
} else if(original.isAssignableFrom(other)) {
source[i] = null; // clear out "original"
changed = true;
break; // the original has been eliminated; move on to the next original
}
}
}
if(cls != null) {
if(cls != source[0]) {
for (int i = 0; i < source.length; i++) {
if(cls == source[i]) {
Class temp = source[0];
source[0] = source[i];
source[i] = temp;
break;
}
}
changed = true;
}
}
if(!changed) {
return source;
}
List list = new ArrayList(source.length);
for (int i = 0; i < source.length; i++) {
if(source[i] != null) {
list.add(source[i]);
}
}
return (Class[]) list.toArray(new Class[list.size()]);
}
}
protected Callback getMethodInterceptor(Class proxyType, Kernel kernel, ObjectName target) {
return new ProxyMethodInterceptor(proxyType, kernel, target);
}
protected void doDestroy(MethodInterceptor methodInterceptor) {
((ProxyMethodInterceptor)methodInterceptor).destroy();
}
protected ObjectName getObjectName(MethodInterceptor methodInterceptor) {
return ((ProxyMethodInterceptor)methodInterceptor).getObjectName();
}
}