/*******************************************************************************
* Copyright (c) 2008, 2014 Stuart McCulloch
* 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:
* Stuart McCulloch - initial API and implementation
*******************************************************************************/
package org.eclipse.sisu.peaberry.internal;
import static java.security.AccessController.doPrivileged;
import static jsr166y.ConcurrentReferenceHashMap.ReferenceType.WEAK;
import static org.eclipse.sisu.peaberry.internal.ComputedMapFactory.computedMap;
import static org.eclipse.sisu.peaberry.internal.ImportGlue.generateProxy;
import static org.eclipse.sisu.peaberry.internal.ImportGlue.getClazzName;
import static org.eclipse.sisu.peaberry.internal.ImportGlue.getProxyName;
import java.lang.reflect.Constructor;
import java.security.PrivilegedAction;
import java.util.concurrent.ConcurrentMap;
import org.eclipse.sisu.peaberry.Import;
import org.eclipse.sisu.peaberry.ServiceException;
import org.eclipse.sisu.peaberry.ServiceUnavailableException;
import org.eclipse.sisu.peaberry.internal.ComputedMapFactory.Function;
/**
* Custom classloader that provides optimized proxies for imported services.
*
* @author mcculls@gmail.com (Stuart McCulloch)
*/
final class ImportProxyClassLoader
extends ClassLoader {
private static final String UNAVAILABLE_CLAZZ_NAME = ServiceUnavailableException.class.getName();
private static final String IMPORT_CLAZZ_NAME = Import.class.getName();
private static final Object NULL_CLASS_LOADER_KEY = new Object();
@SuppressWarnings("unchecked")
static <T> Constructor<T> getProxyConstructor(final Class<T> clazz) {
try {
// use a different custom classloader for each class-space, to avoid leaks
return (Constructor<T>) getProxyClass(clazz).getConstructor(Import.class);
} catch (final LinkageError e) {
throw new ServiceException(e);
} catch (final NoSuchMethodException e) {
// /CLOVER:OFF
throw new ServiceException(e);
} catch (final ClassNotFoundException e) {
throw new ServiceException(e);
// /CLOVER:ON
}
}
/**
* @return unique proxy class per given type
*/
static Class<?> getProxyClass(final Class<?> clazz)
throws ClassNotFoundException {
final Object key = getKeyFromClassLoader(clazz.getClassLoader());
final String name = getProxyName(clazz.getName());
return LOADER_MAP.get(key).loadClass(name);
}
/**
* @return non-null key for the given class loader
*/
static Object getKeyFromClassLoader(final ClassLoader classLoader) {
if (null != classLoader) {
return classLoader;
}
try {
return doPrivileged(new PrivilegedAction<ClassLoader>() {
public ClassLoader run() {
return getSystemClassLoader();
}
});
} catch (final SecurityException e) {
return NULL_CLASS_LOADER_KEY; // unable to canonicalise!
}
}
/**
* @return class loader related to the given key
*/
static ClassLoader getClassLoaderFromKey(final Object key) {
return NULL_CLASS_LOADER_KEY != key ? (ClassLoader) key : null; // NOPMD
}
// weak map of classloaders, to allow eager collection of proxied classes
private static final ConcurrentMap<Object, ClassLoader> LOADER_MAP =
computedMap(WEAK, WEAK, 32, new Function<Object, ClassLoader>() {
public ClassLoader compute(final Object parentKey) {
return doPrivileged(new PrivilegedAction<ClassLoader>() {
public ClassLoader run() {
return new ImportProxyClassLoader(getClassLoaderFromKey(parentKey));
}
});
}
});
// delegate to the original type's classloader
ImportProxyClassLoader(final ClassLoader parent) {
super(parent);
}
@Override
protected Class<?> loadClass(final String name, final boolean resolve)
throws ClassNotFoundException {
// short-circuit access to these classes
if (IMPORT_CLAZZ_NAME.equals(name)) {
return Import.class;
}
if (UNAVAILABLE_CLAZZ_NAME.equals(name)) {
return ServiceUnavailableException.class;
}
return super.loadClass(name, resolve);
}
@Override
protected Class<?> findClass(final String clazzOrProxyName)
throws ClassNotFoundException {
final String clazzName = getClazzName(clazzOrProxyName);
// is this a new proxy class request?
if (!clazzName.equals(clazzOrProxyName)) {
final byte[] code = generateProxy(loadClass(clazzName));
return defineClass(clazzOrProxyName, code, 0, code.length);
}
// ignore any non-proxy requests
throw new ClassNotFoundException(clazzOrProxyName);
}
}