blob: 7f4ca56da1402eb1a5acf866a9b22eb5a88db303 [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.proxy.impl.gen;
import static java.lang.reflect.Modifier.isFinal;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.apache.aries.proxy.FinalModifierException;
import org.apache.aries.proxy.UnableToProxyException;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.reflect.ReflectionFactory;
@SuppressWarnings("restriction")
public class ProxySubclassGenerator
{
private final static Logger LOGGER = LoggerFactory.getLogger(ProxySubclassGenerator.class);
// This map holds references to the names of classes created by this Class
// It is a weak map (so when a ClassLoader is garbage collected we remove
// the map of
// Class names to sub-Class names)
private static final Map<ClassLoader, ConcurrentMap<String, String>> proxyClassesByClassLoader;
private static final ClassLoader defaultClassLoader = new ClassLoader() {};
static {
// Ensure that this is a synchronized map as we may use it from multiple
// threads concurrently
//
proxyClassesByClassLoader = Collections
.synchronizedMap(new WeakHashMap<ClassLoader, ConcurrentMap<String, String>>());
}
private static final char FINAL_MODIFIER = '!';
private static final char UNABLE_TO_PROXY = '#';
public static Class<?> getProxySubclass(Class<?> aClass) throws UnableToProxyException
{
return getProxySubclass(aClass, aClass.getClassLoader());
}
public static Class<?> getProxySubclass(Class<?> aClass, ClassLoader loader) throws UnableToProxyException
{
LOGGER.debug(Constants.LOG_ENTRY, "getProxySubclass", new Object[] { aClass });
// in the special case where the loader is null we use a default classloader
// this is for subclassing java.* or javax.* packages, so that one will do
if (loader == null) loader = defaultClassLoader;
ConcurrentMap<String, String> proxyMap;
synchronized (loader) {
proxyMap = proxyClassesByClassLoader.get(loader);
if (proxyMap == null) {
proxyMap = new ConcurrentHashMap<String, String>();
proxyClassesByClassLoader.put(loader, proxyMap);
}
}
// check the map to see if we have already generated a subclass for this
// class
// if we have return the mapped class object
// if we haven't generate the subclass and return it
Class<?> classToReturn = null;
synchronized (aClass) {
String key = aClass.getName();
String className = proxyMap.get(key);
if (className != null) {
LOGGER.debug("Found proxy subclass with key {} and name {}.", key, className);
if (className.charAt(0) == FINAL_MODIFIER) {
String[] exceptionParts = className.substring(1).split(":");
if (exceptionParts.length == 1) {
throw new FinalModifierException(aClass);
} else {
throw new FinalModifierException(aClass, exceptionParts[1]);
}
} else if (className.charAt(0) == UNABLE_TO_PROXY) {
throw new UnableToProxyException(aClass);
}
try {
classToReturn = loader.loadClass(className);
} catch (ClassNotFoundException cnfe) {
LOGGER.debug(Constants.LOG_EXCEPTION, cnfe);
throw new UnableToLoadProxyException(className, cnfe);
}
} else {
LOGGER.debug("Need to generate subclass. Using key {}.", key);
try {
scanForFinalModifiers(aClass);
classToReturn = generateAndLoadSubclass(aClass, loader);
if (classToReturn != null) {
proxyMap.put(key, classToReturn.getName());
} else {
proxyMap.put(key, UNABLE_TO_PROXY + aClass.getName());
throw new UnableToProxyException(aClass);
}
} catch (FinalModifierException e) {
if (e.isFinalClass()) {
proxyMap.put(key, FINAL_MODIFIER + e.getClassName());
throw e;
} else {
proxyMap.put(key, FINAL_MODIFIER + e.getClassName() + ':' + e.getFinalMethods());
throw e;
}
}
}
}
LOGGER.debug(Constants.LOG_EXIT, "getProxySubclass", classToReturn);
return classToReturn;
}
public static Object newProxySubclassInstance(Class<?> classToProxy, InvocationHandler ih)
throws UnableToProxyException
{
return newProxySubclassInstance(classToProxy, classToProxy.getClassLoader(), ih);
}
public static Object newProxySubclassInstance(Class<?> classToProxy, ClassLoader loader, InvocationHandler ih)
throws UnableToProxyException
{
LOGGER.debug(Constants.LOG_ENTRY, "newProxySubclassInstance", new Object[] {
classToProxy, loader, ih });
Object proxySubclassInstance = null;
try {
Class<?> generatedProxySubclass = getProxySubclass(classToProxy, loader);
LOGGER.debug("Getting the proxy subclass constructor");
// Because the newer JVMs throw a VerifyError if a class attempts to in a constructor other than their superclasses constructor,
// and because we can't know what objects/values we need to pass into the class being proxied constructor,
// we instantiate the proxy class using the ReflectionFactory.newConstructorForSerialization() method which allows us to instantiate the
// proxy class without calling the proxy class' constructor. It is in fact using the java.lang.Object constructor so is in effect
// doing what we were doing before.
ReflectionFactory factory = ReflectionFactory.getReflectionFactory();
Constructor<?> constr = Object.class.getConstructor();
Constructor<?> subclassConstructor = factory.newConstructorForSerialization(generatedProxySubclass, constr);
proxySubclassInstance = subclassConstructor.newInstance();
Method setIHMethod = proxySubclassInstance.getClass().getMethod("setInvocationHandler", InvocationHandler.class);
setIHMethod.invoke(proxySubclassInstance, ih);
LOGGER.debug("Invoked proxy subclass constructor");
} catch (NoSuchMethodException nsme) {
LOGGER.debug(Constants.LOG_EXCEPTION, nsme);
throw new ProxyClassInstantiationException(classToProxy, nsme);
} catch (InvocationTargetException ite) {
LOGGER.debug(Constants.LOG_EXCEPTION, ite);
throw new ProxyClassInstantiationException(classToProxy, ite);
} catch (InstantiationException ie) {
LOGGER.debug(Constants.LOG_EXCEPTION, ie);
throw new ProxyClassInstantiationException(classToProxy, ie);
} catch (IllegalAccessException iae) {
LOGGER.debug(Constants.LOG_EXCEPTION, iae);
throw new ProxyClassInstantiationException(classToProxy, iae);
} catch (VerifyError ve) {
LOGGER.info(String.format("The no-argument constructor of class %s is private and therefore it may not be possible to generate a valid proxy.",
classToProxy));
LOGGER.debug(Constants.LOG_EXCEPTION, ve);
throw new ProxyClassInstantiationException(classToProxy, ve);
}
LOGGER.debug(Constants.LOG_EXIT, "newProxySubclassInstance", proxySubclassInstance);
return proxySubclassInstance;
}
private static Class<?> generateAndLoadSubclass(Class<?> aClass, ClassLoader loader)
throws UnableToProxyException
{
LOGGER.debug(Constants.LOG_ENTRY, "generateAndLoadSubclass", new Object[] { aClass,
loader });
// set the newClassName
String newClassName = "$" + aClass.getSimpleName() + aClass.hashCode();
String packageName = aClass.getPackage().getName();
if (packageName.startsWith("java.") || packageName.startsWith("javax.")) {
packageName = "org.apache.aries.blueprint.proxy." + packageName;
}
String fullNewClassName = (packageName + "." + newClassName).replaceAll("\\.", "/");
LOGGER.debug("New class name: {}", newClassName);
LOGGER.debug("Full new class name: {}", fullNewClassName);
Class<?> clazz = null;
try {
ClassReader cReader = new ClassReader(loader.getResourceAsStream(aClass.getName().replaceAll(
"\\.", "/")
+ ".class"));
ClassWriter cWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
ClassVisitor dynamicSubclassAdapter = new ProxySubclassAdapter(cWriter, fullNewClassName,
loader);
byte[] byteClassData = processClass(cReader, cWriter, dynamicSubclassAdapter);
clazz = loadClassFromBytes(loader, getBinaryName(fullNewClassName), byteClassData, aClass
.getName());
} catch (IOException ioe) {
LOGGER.debug(Constants.LOG_EXCEPTION, ioe);
throw new ProxyClassBytecodeGenerationException(aClass.getName(), ioe);
} catch (TypeNotPresentException tnpe) {
LOGGER.debug(Constants.LOG_EXCEPTION, tnpe);
throw new ProxyClassBytecodeGenerationException(tnpe.typeName(), tnpe.getCause());
}
LOGGER.debug(Constants.LOG_EXIT, "generateAndLoadSubclass", clazz);
return clazz;
}
private static byte[] processClass(ClassReader cReader, ClassWriter cWriter, ClassVisitor cVisitor)
{
LOGGER.debug(Constants.LOG_ENTRY, "processClass", new Object[] { cReader, cWriter,
cVisitor });
cReader.accept(cVisitor, ClassReader.SKIP_DEBUG);
byte[] byteClassData = cWriter.toByteArray();
LOGGER.debug(Constants.LOG_EXIT, "processClass", byteClassData);
return byteClassData;
}
private static String getBinaryName(String name)
{
LOGGER.debug(Constants.LOG_ENTRY, "getBinaryName", name);
String binaryName = name.replaceAll("/", "\\.");
LOGGER.debug(Constants.LOG_EXIT, "getBinaryName", binaryName);
return binaryName;
}
private static Class<?> loadClassFromBytes(ClassLoader loader, String name, byte[] classData,
String classToProxyName) throws UnableToProxyException
{
LOGGER.debug(Constants.LOG_ENTRY, "loadClassFromBytes", new Object[] { loader, name,
classData });
Class<?> clazz = null;
try {
Method defineClassMethod = Class.forName("java.lang.ClassLoader").getDeclaredMethod(
"defineClass", String.class, byte[].class, int.class, int.class, ProtectionDomain.class);
defineClassMethod.setAccessible(true);
// define the class in the same classloader where aClass is loaded,
// but use the protection domain of our code
clazz = (Class<?>) defineClassMethod.invoke(loader, name, classData, 0, classData.length,
ProxySubclassGenerator.class.getProtectionDomain());
defineClassMethod.setAccessible(false);
} catch (ClassNotFoundException cnfe) {
LOGGER.debug(Constants.LOG_EXCEPTION, cnfe);
throw new ProxyClassDefinitionException(classToProxyName, cnfe);
} catch (NoSuchMethodException nsme) {
LOGGER.debug(Constants.LOG_EXCEPTION, nsme);
throw new ProxyClassDefinitionException(classToProxyName, nsme);
} catch (InvocationTargetException ite) {
LOGGER.debug(Constants.LOG_EXCEPTION, ite);
throw new ProxyClassDefinitionException(classToProxyName, ite);
} catch (IllegalAccessException iae) {
LOGGER.debug(Constants.LOG_EXCEPTION, iae);
throw new ProxyClassDefinitionException(classToProxyName, iae);
}
LOGGER.debug(Constants.LOG_EXIT, "loadClassFromBytes", clazz);
return clazz;
}
public static boolean isProxySubclass(Class<?> aClass)
{
LOGGER.debug(Constants.LOG_ENTRY, "isProxySubclass", new Object[] { aClass });
// We will always have a proxy map for the class loader of any proxy
// class, so if
// this is null we know to return false
Map<String, String> proxies = proxyClassesByClassLoader.get(aClass.getClassLoader());
boolean isProxySubclass = (proxies != null && proxies.containsValue(aClass.getName()));
LOGGER.debug(Constants.LOG_EXIT, "isProxySubclass", isProxySubclass);
return isProxySubclass;
}
private static void scanForFinalModifiers(Class<?> clazz) throws FinalModifierException
{
LOGGER.debug(Constants.LOG_ENTRY, "scanForFinalModifiers", new Object[] { clazz });
if (isFinal(clazz.getModifiers())) {
throw new FinalModifierException(clazz);
}
List<String> finalMethods = new ArrayList<String>();
// we don't want to check for final methods on java.* or javax.* Class
// also, clazz can never be null here (we will always hit
// java.lang.Object first)
while (!clazz.getName().startsWith("java.") && !clazz.getName().startsWith("javax.")) {
for (Method m : clazz.getDeclaredMethods()) {
//Static finals are ok, because we won't be overriding them :)
if (isFinal(m.getModifiers()) && !Modifier.isStatic(m.getModifiers())) {
finalMethods.add(m.toGenericString());
}
}
clazz = clazz.getSuperclass();
}
if (!finalMethods.isEmpty()) {
String methodList = finalMethods.toString();
methodList = methodList.substring(1, methodList.length() - 1);
throw new FinalModifierException(clazz, methodList);
}
LOGGER.debug(Constants.LOG_EXIT, "scanForFinalModifiers");
}
public static InvocationHandler getInvocationHandler(Object o)
{
LOGGER.debug(Constants.LOG_ENTRY, "getInvoationHandler", new Object[] { o });
InvocationHandler ih = null;
if (isProxySubclass(o.getClass())) {
// we have to catch exceptions here, but we just log them
// the reason for this is that it should be impossible to get these
// exceptions
// since the Object we are dealing with is a class we generated on
// the fly
try {
ih = (InvocationHandler) o.getClass().getDeclaredMethod("getInvocationHandler",
new Class[] {}).invoke(o, new Object[] {});
} catch (IllegalArgumentException e) {
LOGGER.debug(Constants.LOG_EXCEPTION, e);
} catch (SecurityException e) {
LOGGER.debug(Constants.LOG_EXCEPTION, e);
} catch (IllegalAccessException e) {
LOGGER.debug(Constants.LOG_EXCEPTION, e);
} catch (InvocationTargetException e) {
LOGGER.debug(Constants.LOG_EXCEPTION, e);
} catch (NoSuchMethodException e) {
LOGGER.debug(Constants.LOG_EXCEPTION, e);
}
}
LOGGER.debug(Constants.LOG_EXIT, "getInvoationHandler", ih);
return ih;
}
}