| /* |
| * 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; |
| } |
| |
| } |