All Downloads are FREE. Search and download functionalities are using the official Maven repository.

lithium.classloadertest.FunctionalTestClassLoader Maven / Gradle / Ivy

The newest version!
package lithium.classloadertest;


import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import lithium.classloadertest.annotation.ClassLoaderInit;
import lithium.classloadertest.annotation.TestVisible;
import lithium.classloadertest.common.TransformingURLClassLoader;
import lithium.classloadertest.impl.UnfinalizingClassTransformer;
import net.sf.cglib.core.CodeGenerationException;
import net.sf.cglib.core.DefaultNamingPolicy;
import net.sf.cglib.core.Predicate;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.InvocationHandler;
import net.sf.cglib.proxy.NoOp;

import org.objenesis.Objenesis;
import org.objenesis.ObjenesisStd;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestContextManager;

/**
 * FunctionalTestClassLoader creates an ability to dynamically proxy calls into a child ClassLoader from a parent ClassLoader. 
 * This makes it possible to load classes independently in multiple classloaders to create a simulation of a multi-node 
 * environment where all classes, and statics are separated from each other, and then drive the calls to the various
 * ClassLoader instances from an overall "parent" testcase.
 *  
 * To make writing tests easier, a FunctionalTestClassLoader will provide an "entryObject", which is a proxy to an instance
 * of the entryClass in the contained ClassLoader.  Note that if this is a junit test, the test *is not* run in the 
 * other ClassLoader.  The test only runs in the current ClassLoader so that it can control multiple instances of 
 * the components under test.
 * 
 * This entryObject proxy allows the client to retrieve any other object by calling methods or accessing properties on the 
 * entryObject.  Each returned object will itself be a proxy of another object.
 * 
 * Because testing large scale components can require loading a lot of classes, a typical pattern for a test suite that 
 * uses FunctionaTestClassLoader would look like this to conserve resources:
 * 
 * 	@BeforeClass
 *	public static void setup() {
 *		cl = new FunctionalTestClassLoader(FunctionalTestClassLoaderTest.class);
 *		cl2 = new FunctionalTestClassLoader(FunctionalTestClassLoaderTest.class);
 *	}
 *	
 *	@AfterClass
 *	public static void tearDown() {
 *		cl = null;
 *		cl2 = null;
 *	}
 * 
 * This class is also designed to work with the Spring {@link ContextConfiguration} annotation in that a FunctionalTestClassLoader
 * that is given an entryClass with this annotation will load Spring if the class has this annotation.  This is useful
 * for tests of classes that leverage Spring. Use of the {@link ContextConfiguration} will be interpreted inside the
 * FunctionalTestClassLoader child instance.  This convenience allows callers to easily initialize the Spring container
 * once for each classloader. 
 * 
 * When using {@link ContextConfiguration} a special @TestVisible annotation can be used on classes that are passed to 
 * FunctionalTestClassLoader.  This annotation will cause field values in the entryObject to be copied up to the 
 * parent proxy instance.  This allows Spring @Autowired fields to be visible in the parent test context.
 * 
 * For example:
 * 
 * @TestVisible
 * @Autowired
 * private final MyInterface myDependencyInjectedValue = null;
 * 
 * ...
 * {
 * 		...
 * 		myTestClass.myDependencyInjectedValue.getValue();
 * 		...
 * }
 * 
 * If initialization needs to be performed in the FunctionalTestClassLoader, the {link @ClassLoaderInit} annotation can be 
 * placed on a single instance method on the entryClass with no arguments.  This method will be invoked before 
 * Spring is initialized.  This can be useful to set any custom state necessary before Spring is loaded.  
 * 
 * Another capability of the FunctionalTestClassLoader is the ability to reload specified classes without final 
 * designation and to force all members and classes to be public.  Because of the expense of creating multiple 
 * new ClassLoaders for testing multi-node behavior, it makes sense to avoid the creation of yet another ClassLoader with
 * other mocking tools (PowerMock etc.) since we just paid the cost of reloading the classes under
 * test.  
 * 
 * By default, the FunctionalTestClassLoader will automatically unfinalize the entryClass.  To unfinalize other classes
 * for mocking, specify them in the second constructor argument.
 * 
 * Only classses that will be mocked within FunctionalTestClassLoader helper methods in the context of a multi-node test 
 * need be specified.  For instance:
 * 
 *		myClassLoader1 = new FunctionalTestClassLoader(FunctionalTestClassLoaderTest.class, new Class[]{
 *					MyFinalConcreteClass.class
 *				});
 * 
 * @author jeff.collins
 */
public class FunctionalTestClassLoader extends TransformingURLClassLoader {
	/*
	 * The base functionality of Enhancer in CGLib is used, but modified slightly to change some behavior in order to accomodate
	 * proxy-ing of concrete base classes.  Instead of allowing classes to inherit from a common base class using the  
	 * "dynamic Proxy" approach - which only works with interfaces, the generated classes inherit from the actual local base
	 * class and implement all the same interfaces similar to EasyMock.  This allows roundtripping through to the child classloader.
	 */ 
	private final Class entryClass;
	private Object entryObject;
	private static final String LITHIUM_TEST_PROXY = "$Li_Pxy$";
	private static final String JAVA_LANG_REFLECT_PROXY_PREFIX = "$Proxy";
	private static final String CGLIB_TAG = "$$EnhancerByCGLIB$$";
	private HashMap>> methodCache = new HashMap>>();
	private static Logger logger = LoggerFactory.getLogger(FunctionalTestClassLoader.class);
	
	/**
	 * Creates a new instance of the FunctionalTestClassLoader.  The entry class will be loaded on demand
	 * during later interactions.	 
	 * 
	 * @param entryClass the entry class to load later, used to create the instance returned by {@link #getEntryClassInstance()}
	 */
	public FunctionalTestClassLoader(Class entryClass) {
		super(new Class[] {entryClass}, new UnfinalizingClassTransformer());
		this.entryClass = entryClass;
		loadTransformedClass(FunctionalTestClassLoader.class.getName());
	}
	
	/**
	 * Creates a new instance of the FunctionalTestClassLoader.  The entry class will be loaded on demand
	 * during later interactions.  Any classes specified in the classesToUnfinalize array will be loaded
	 * immediately and stripped of private and final designations on all class and method definitions.  This 
	 * allows tests to be written that mock final classes and methods and invoke private methods.  This is
	 * done to avoid the cost of loading yet another classloader instance using a tool like PowerMock to do
	 * the same thing, since we're already loading a new ClassLoader.
	 * 
	 * @param entryClass the entry class to load later, used to create the instance returned by {@link #getEntryClassInstance()}
	 * @param classesToUnfinalize classes to filter and load without final or private designations
	 */
	public FunctionalTestClassLoader(Class entryClass, Class classesToUnfinalize[]) {
		super(classesToUnfinalize, new UnfinalizingClassTransformer());
		this.entryClass = entryClass;
		loadTransformedClass(entryClass.getName());
		loadTransformedClass(FunctionalTestClassLoader.class.getName());
	}

	/**
	 * Reflective call to the class loader based on a previously returned instance.  
	 * Normally not necessary if you're using the proxy-based approach, but
	 * can be used if there are corner-case issues with a particular test case. 
	 * 
	 * @param  the type of the return
	 * @param methodName the name of the method to invoke in the other classloader on the instance
	 * @param args the arguments to pass
	 * @return the result of the method call
	 */
	public  T call(String methodName, Object... args) {
		ensureInitialized();
		return this.call(entryObject, entryObject.getClass().getName(), methodName, args);
	}

	private void ensureInitialized() {
		if (entryObject == null) {
			entryObject = callStatic(FunctionalTestClassLoader.class, "getEntryClassInstance", this, entryClass);
		}
	}
	
	/**
	 * Called reflectively by {@link #ensureInitialized()} to set up the entry class instance in the other classloader.
	 * 
	 * @param entryClass
	 * @return the new instance
	 */
	@SuppressWarnings("unused")
	private static Object getEntryClassInstance(ClassLoader classLoader, Class entryClass) {
		try {
			Class localClass = getClassFromClassLoader(entryClass, classLoader);
			Object testInstance = localClass.newInstance();
			Method m = getMethodForAnnotation(localClass, ClassLoaderInit.class);
			if (m != null) {
		        m.invoke(testInstance);
			}
			if (localClass.isAnnotationPresent(ContextConfiguration.class)) {
		        createTestContextManager(testInstance);
			}
			return testInstance;
		} catch (InvocationTargetException e) {
			if (e.getTargetException() instanceof ClassLoaderException) {
				logger.error("Failed getting entry object", e.getTargetException());
				throw (ClassLoaderException)e.getTargetException();
			}
			throw new ClassLoaderException("Failed getting entry object", e.getTargetException());
		} catch (Exception e) {
			throw new ClassLoaderException("Failed getting entry object", e);
		}
	}
	
	/**
	 * Initializes the setup of each FunctionalTestClassLoader by accessing the {@link #getEntryClassInstance()} method
	 * for each instance on a separate thread.  This makes clasloading run in parallel, which will make the runtime
	 * similar to a single classloading test.
	 * 
	 * @param args the list of FunctionalTestClassLoader objects to initialize
	 */
	public static void parallelSetup(final FunctionalTestClassLoader... args) {
		List> tasks = new ArrayList>();
		ExecutorService executorService = Executors.newFixedThreadPool(args.length);
		for (int i = 0; i < args.length; i++) {
			final int var = i;
			tasks.add(new Callable() {
				FunctionalTestClassLoader param = args[var];

				@Override
				public Object call() throws Exception {
					return param.getEntryClassInstance();
				}
				
			});
		}
		try {
			List> futures = executorService.invokeAll(tasks);
			for (Future future : futures) {
				future.get();
			}
			executorService.shutdown();
		} catch (Exception e) {
			logger.error("Failed during initialization of classloader", e);
			throw new ClassLoaderException("Failed during initialization of classloader", e);
		}
		
	}

	/**
	 * Calls code in parallel, and returns the results as Future objects after all Callable objects complete .  
	 * Allows a test to run expensive processing in parallel if necessary to avoid long-running serialized steps 
	 * between two ClassLoaders.
	 * 
	 * @param tasks the list of Callables to invoke, waiting for all to complete before proceeding
	 */
	@SuppressWarnings({"unchecked"})
	public static  Future[] parallelExec(final Callable... tasks) {
		ExecutorService executorService = Executors.newFixedThreadPool(tasks.length);
		List> futures = null;
		try {
			futures = executorService.invokeAll(Arrays.asList(tasks));
			executorService.shutdown();
		} catch (InterruptedException e) {
			throw new ClassLoaderException("Initialization interrupted", e);
		}
		Future result[] = new Future[tasks.length];
		return futures.toArray(result);
	}
	
	/**
	 * Called reflectively by {@link #ensureInitialized()} to set up the Spring test context manager in the remote
	 * classloader.
	 * 
	 * @param testInstance - an instance of the class under test in the current ClassLoader
	 * @return a new TestContextManager
	 */
	private static TestContextManager createTestContextManager(Object testInstance) {
		TestContextManager testContextManager = new TestContextManager(testInstance.getClass());
		try {
			testContextManager.prepareTestInstance(testInstance);
		} catch (Exception e) {
			throw new ClassLoaderException("Failed setting up Spring test instance", e);
		}
		return testContextManager;
	}

	/**
	 * Reflective call to a static method in the other class loader.  Useful when it's necessary to call a helper
	 * function in the child classloader during a multi-classloader test.
	 * 
	 * It's preferable to call {@link #getEntryClassInstance()} which will give an instance of the entry class
	 * in the other classloader.  
	 * @param  The type of object to be returned
	 * @param clazz The class to invoke the method of
	 * @param methodName The name of the method
	 * @param args the arguments to pass
	 * @return the value returned
	 */
	public  T callStatic(Class clazz, String methodName, Object... args) {
		return this.call(null, clazz.getName(), methodName, args);
	}


	@SuppressWarnings("unchecked")
	private  T call(Object object, String classname, String methodName, Object... args) {
		ClassLoader orgClassLoader = null;
		try {
			// well-behaved Java packages work relative to the
			// context classloader. Others don't (like commons-logging)
			orgClassLoader = Thread.currentThread().getContextClassLoader();
			Thread.currentThread().setContextClassLoader(this);

			// relative to that classloader, find the main class
			// you want to bootstrap, which is the first cmd line arg
			Class loadedClass = loadClass(classname);
			Method method = getMethodForArgs(loadedClass, methodName, args);

			method.setAccessible(true);
			return (T) method.invoke(object, args);
		} catch (InvocationTargetException e) {
			if (e.getTargetException() instanceof ClassLoaderException) {
				logger.error("call() failed", e.getTargetException().getCause());
				throw (ClassLoaderException)e.getTargetException();
			}
			logger.error("call() failed", e.getTargetException());
			throw new ClassLoaderException("Reflective call failed", e.getTargetException());
		} catch (Exception e) {
			logger.error("call() failed", e);
			throw new ClassLoaderException("Reflective call failed", e);
		} finally {
			if (orgClassLoader != null) {
				Thread.currentThread().setContextClassLoader(orgClassLoader);
			}
		}
	}
	
	@SuppressWarnings("unchecked")
	private static Method getMethodForAnnotation(Class clazz, 
			Class annotationClass) {
		Class localClass = 
			(Class)getClassFromClassLoader(annotationClass, clazz.getClassLoader());
		Method methods[] = clazz.getMethods();		
		for (Method method : methods) {
			if (method.isAnnotationPresent(localClass)) {
				return method;
			}
		}
		return null;
	}
	
	private List getMethods(Class clazz, String methodName) {
		HashMap> classMethods = getMethods(clazz);
		return classMethods.get(methodName);
	}

	private HashMap> getMethods(Class clazz) {
		HashMap> classMethods = methodCache.get(clazz.getName());
		if (classMethods == null) {
			classMethods = new HashMap>();
			methodCache.put(clazz.getName(), classMethods);
			while(clazz != null && clazz.getClassLoader() != null) {
				Method methods[] = clazz.getDeclaredMethods();
				for (Method method : methods) {
					List methodsByName = classMethods.get(method.getName());
					if (methodsByName == null) {
						methodsByName = new ArrayList();
						classMethods.put(method.getName(), methodsByName);
					}
					methodsByName.add(method);
				}
				clazz = clazz.getSuperclass();
			}
		}
		return classMethods;
	}

	private Method getMethodForArgs(Class loadedClass, String methodName, Object[] args) throws SecurityException,
			NoSuchMethodException {
		List methods = getMethods(loadedClass, methodName);
		if (methods != null) {
			for (Method method : methods) {
				Class paramTypes[] = method.getParameterTypes();
				if (paramTypes.length == args.length) {
					boolean isCompatible = true;
					for (int i = 0; i < paramTypes.length; i++) {
						Class paramType = paramTypes[i];
						if (args[i] != null) {
							Class argType = args[i].getClass();
							if (!boxedType(paramType).isAssignableFrom(boxedType(argType))) {
								if (argType.getClassLoader() != null && argType.getClassLoader() != this) {
									throw new ClassLoaderException("Invalid parameter " + i + " passed to method " + methodName
											+ ": object is an instance of class " + argType.getName()
											+ " from ClassLoader " + argType.getClassLoader() + ". Only objects"
											+ " returned from calls to " + this + " can be passed.");
								}
								isCompatible = false;
								break;
							}
						}
					}
					if (isCompatible) {
						return method;
					}
				}
			}
		}
		throw new ClassLoaderException("Could not find a method named " + methodName + " matching the arguments " + Arrays.toString(args));
	}

    private static Class boxedType( Class primitive )
    {
        if( !primitive.isPrimitive() )
            return( primitive );

        if( Boolean.TYPE.equals( primitive ) )
            return( Boolean.class );
        if( Character.TYPE.equals( primitive ) )
            return( Character.class );
        if( Byte.TYPE.equals( primitive ) )
            return( Byte.class );
        if( Short.TYPE.equals( primitive ) )
            return( Short.class );
        if( Integer.TYPE.equals( primitive ) )
            return( Integer.class );
        if( Long.TYPE.equals( primitive ) )
            return( Long.class );
        if( Float.TYPE.equals( primitive ) )
            return( Float.class );
        if( Double.TYPE.equals( primitive ) )
            return( Double.class );

        throw new ClassLoaderException( "Error translating type:" + primitive );
    }

    /**
	 * Instantiates the entry class instance in the other class loader, and caches it.  Then returns the entryObject
	 * wrapped in a proxy so that it appears like an instance of a class in the current ClassLoader.
	 * 
	 * @param  the type to return
	 * @return the value returned from the method
	 */
	@SuppressWarnings("unchecked")
	public  T getEntryClassInstance() {
		ensureInitialized();
		return (T) RemoteProxy.newInstance(entryObject, this);
	}

	private static Class getClassFromClassLoader(Class clazz, ClassLoader classLoader) {
		try {
			String className = clazz.getName();
			if (className.contains(JAVA_LANG_REFLECT_PROXY_PREFIX) || className.contains(CGLIB_TAG)) {
				if (clazz.getSuperclass() != java.lang.reflect.Proxy.class) {
					className = clazz.getSuperclass().getName();
				} else {
					// only interface proxy
					className = clazz.getInterfaces()[0].getName();
				}
			}
			return Class.forName(className, true, classLoader);
		} catch (ClassNotFoundException e) {
			throw new ClassLoaderException("Can't find class " + clazz.getName(), e);
		}
	}

	private static Class getLocalClass(Class clazz) {
		// check if the class is from our classloader
		if (clazz.getClassLoader() != FunctionalTestClassLoader.class.getClassLoader()) {
			return getClassFromClassLoader(clazz, FunctionalTestClassLoader.class.getClassLoader());
		} else {
			return clazz;
		}
	}
	
	private static class LithiumTestProxyNamingPolicy extends DefaultNamingPolicy {

		@Override
		public String getClassName(String prefix, String source, Object key, Predicate names) {
			String s = super.getClassName(prefix, source, key, names);
			return LITHIUM_TEST_PROXY + s;
		}		
	}

	/**
	 * Mostly similar to the Dynamic Proxy pattern from Java, but with the one difference
	 * that a custom code generation strategy is applied so we can support Concrete Classes.  Concrete
	 * classes of course can't be final.
	 *
	 */
	private static class Proxy {

	    private static final CallbackFilter BAD_OBJECT_METHOD_FILTER = new CallbackFilter() {
	        @Override
			public int accept(Method method) {
	            if (method.getDeclaringClass().getName().equals("java.lang.Object")) {
	                String name = method.getName();
	                if (!(name.equals("hashCode") ||
	                      name.equals("equals") ||
	                      name.equals("toString"))) {
	                    return 1;
	                }
	            }
	            return 0;
	        }
	    };

	    public static InvocationHandler getInvocationHandler(Object proxy) {
	    	return (InvocationHandler)((Factory)proxy).getCallback(0);
	    }

	    public static Class getProxyClass(Class clazz) {
	        Enhancer e = new Enhancer();
	        if (clazz.isInterface()) {
		        e.setSuperclass(clazz);
	        } else {
		        e.setSuperclass(clazz);
		        e.setInterfaces(clazz.getInterfaces());
	        }
	        e.setCallbackTypes(new Class[]{
	            InvocationHandler.class,
	            NoOp.class,
	        });
	        e.setCallbackFilter(BAD_OBJECT_METHOD_FILTER);
	        e.setUseFactory(true);
	        e.setNamingPolicy(new LithiumTestProxyNamingPolicy());
	        return e.createClass();
	    }

	    public static boolean isProxyClass(Class cl) {
	        return cl.getName().startsWith(LITHIUM_TEST_PROXY);
	    }
	    
	    private static Object constructInstance(Class clazz)  {
	    	/**
	    	 * Use the objensis magic (like PowerMock/EasyMock) to construct an instance of a Java
	    	 * object without invoking a constructor.  This is a local proxy, and it shouldn't
	    	 * run code locally.  We only inherit in concrete class cases to allow a proper
	    	 * programming interface in the local classloader.
	    	 */
 			Objenesis objenesis = new ObjenesisStd();
 			return objenesis.newInstance(clazz);
	    }
	    
	    public static Object newProxyInstance(Class clazz, InvocationHandler h) {
	        try {
	            Class c = getProxyClass(clazz);
	            Factory o = (Factory)constructInstance(c);
	            o.setCallbacks(new Callback[]{ h, null });
				copyDelegateFields(o, h);
	            return o;
	        } catch (RuntimeException e) {
	            throw e;
	        } catch (Exception e) {
	            throw new CodeGenerationException(e);
	        }
	    }

		@SuppressWarnings("unchecked")
		private static void copyDelegateFields(Object target, InvocationHandler source) {
			Object obj = ((RemoteProxy)source).obj;
			if (obj == null) {
				return;
			}
			if (!(obj.getClass().getClassLoader() instanceof FunctionalTestClassLoader)) {
				throw new ClassLoaderException("Failed wrapping field on remote object - wrong classloader type");
			}
			FunctionalTestClassLoader classLoader = (FunctionalTestClassLoader)obj.getClass().getClassLoader();
			
			// get the source fields
			Field[] fields = getAllFields(obj.getClass());
			
			// get the target proxy parent fields
			Field[] targetFields = getAllFields(target.getClass().getSuperclass());
			
			for(Field field : fields) {
				if (!Modifier.isStatic(field.getModifiers())) {
					/**
					 * The @TestVisible annotation tells the framework to automatically bring the value 
					 * from the child classloader into the proxy for easy access during a test.
					 * 
					 * @TestVisible
					 * @Autowired
					 * private final MyInterface myDependencyInjectedValue = null;
					 * 
					 * ...
					 * {
					 * 		...
					 * 		myTestClass.myDependencyInjectedValue.getValue();
					 * 		...
					 * }
					 * 
					 */
					Class testVisible;
					testVisible = (Class)getClassFromClassLoader(TestVisible.class, classLoader);
					if (field.isAnnotationPresent(testVisible)) {
						// copy the values over for final fields					
						Field targetField = null;
						try {
							for(Field f : targetFields) {
								if (f.getName().equals(field.getName())) {
									targetField = f;
								}
							}
						} catch (Exception e) {
							 // benign
						}
						if (targetField != null) {
							copyProxiedValue(field, obj, targetField, target, classLoader);
						}
					}
				}
			}
		}

		private static Field[] getAllFields(Class clazz) {
			List fields = new ArrayList();
			while(clazz != null && clazz != Object.class) {
				Collections.addAll(fields, clazz.getDeclaredFields());
				clazz = clazz.getSuperclass();
			}
			return fields.toArray(new Field[] {});
		}

		private static void copyProxiedValue(Field srcField, Object srcObj, Field targetField, Object targetObj,
				FunctionalTestClassLoader classLoader) {
			try {
				targetField.setAccessible(true);
				Object fieldVal = srcField.get(srcObj);
				if (fieldVal != null) {
					targetField.set(targetObj, RemoteProxy.newInstance(fieldVal, srcField.getType(), classLoader));
				} else {
					targetField.set(targetObj, null);
				}
			} catch(Exception e) {
				throw new ClassLoaderException("Failed reflecting classes for proxy field copy", e);
			}
		}
	}

    /**
	 * This is the container class for delegating to a remote object in a different ClassLoader.  The RemoteProxy
	 * is stored in the "callback" field of the generated class, and calls to this class through the InvocationHandler 
	 * interface will be proxied to the internal delegate using reflection.
	 *
	 */
	private static class RemoteProxy implements InvocationHandler {
		private Object obj;
		private FunctionalTestClassLoader classLoader;

		public static Object newInstance(Object delegate, Class clazz, FunctionalTestClassLoader classLoader) {
			if (clazz.getClassLoader() != null) {
				if (clazz.getClassLoader().getClass().getName().equals(FunctionalTestClassLoader.class.getName())) {
					Class localClass = getLocalClass(clazz);
					return Proxy.newProxyInstance(localClass, new RemoteProxy(delegate, classLoader));
				} else {
					throw new ClassLoaderException("Behavior not defined for classloaders other than FunctionalTestClassLoader");
				}
			} else {
				return delegate;
			}
		}

		public static Object newInstance(Object delegate, FunctionalTestClassLoader classLoader) {
			return newInstance(delegate, delegate.getClass(), classLoader);
		}

		private RemoteProxy(Object obj, FunctionalTestClassLoader classLoader) {
			this.obj = obj;
			this.classLoader = classLoader;
		}

		@Override
		public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
			Object unpackedArgs[] = null;
			if (args != null) {
				unpackedArgs = new Object[args.length];
				for (int i = 0; i < args.length; i++) {
					if (args[i] != null) {
						if (Proxy.isProxyClass(args[i].getClass())) {
							unpackedArgs[i] = ((RemoteProxy) Proxy.getInvocationHandler(args[i])).obj;
						} else {
							// if something other than a proxy was passed in, then we can only accept
							// objects from a higher classloader (i.e. null) because otherwise we're 
							// passing something to a classloader that it won't understand
							if (args[i].getClass().getClassLoader() == null) {
								unpackedArgs[i] = args[i];
							} else {
								throw new ClassLoaderException("Invalid parameter " + i + " passed to method " + m.getName()
										+ ": object is an instance of " + "class " + args[i].getClass().getName()
										+ " from ClassLoader " + args[i].getClass().getClassLoader() + ". Only objects"
										+ " returned from calls to " + this + " can be passed.");
							}
						}
					}
				}
			}
			Object result = classLoader.call(obj, m.getDeclaringClass().getName(), m.getName(), unpackedArgs);
			if (result != null && result.getClass().getClassLoader() == classLoader) {
				return RemoteProxy.newInstance(result, classLoader);
			} else {
				return result;
			}
		}
	}

}