001 // Copyright 2006, 2007 The Apache Software Foundation 002 // 003 // Licensed under the Apache License, Version 2.0 (the "License"); 004 // you may not use this file except in compliance with the License. 005 // You may obtain a copy of the License at 006 // 007 // http://www.apache.org/licenses/LICENSE-2.0 008 // 009 // Unless required by applicable law or agreed to in writing, software 010 // distributed under the License is distributed on an "AS IS" BASIS, 011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012 // See the License for the specific language governing permissions and 013 // limitations under the License. 014 015 package org.apache.tapestry5.ioc.internal.services; 016 017 import javassist.*; 018 import org.apache.tapestry5.ioc.internal.util.CollectionFactory; 019 import org.apache.tapestry5.ioc.internal.util.InternalUtils; 020 import org.apache.tapestry5.ioc.services.ClassFabUtils; 021 022 import java.lang.reflect.InvocationTargetException; 023 import java.lang.reflect.Method; 024 import java.security.AccessController; 025 import java.security.PrivilegedActionException; 026 import java.security.PrivilegedExceptionAction; 027 import java.security.ProtectionDomain; 028 import java.util.Map; 029 import java.util.Set; 030 031 /** 032 * Used to ensure that {@link javassist.ClassPool#appendClassPath(javassist.ClassPath)} is invoked within a synchronized 033 * lock, and also handles tricky class loading issues (caused by the creation of classes, and class loaders, at 034 * runtime). 035 */ 036 public class ClassFactoryClassPool extends ClassPool 037 { 038 039 // Kind of duplicating some logic from ClassPool to avoid a deadlock-producing synchronized block. 040 041 private static final Method defineClass = findMethod("defineClass", String.class, byte[].class, 042 int.class, int.class); 043 044 private static final Method defineClassWithProtectionDomain = findMethod("defineClass", String.class, byte[].class, 045 int.class, int.class, 046 ProtectionDomain.class); 047 048 private static Method findMethod(final String methodName, final Class... parameterTypes) 049 { 050 try 051 { 052 return AccessController.doPrivileged(new PrivilegedExceptionAction<Method>() 053 { 054 public Method run() throws Exception 055 { 056 Class cl = Class.forName("java.lang.ClassLoader"); 057 058 Method result = cl.getDeclaredMethod(methodName, parameterTypes); 059 060 // Just make it accessible; no particular reason to make it unaccessible again. 061 062 result.setAccessible(true); 063 064 return result; 065 } 066 }); 067 } 068 catch (PrivilegedActionException ex) 069 { 070 throw new RuntimeException(String.format("Unable to initialize ClassFactoryClassPool: %s", 071 InternalUtils.toMessage(ex)), ex); 072 } 073 } 074 075 /** 076 * Used to identify which class loaders have already been integrated into the pool. 077 */ 078 private final Set<ClassLoader> allLoaders = CollectionFactory.newSet(); 079 080 private final Map<ClassLoader, ClassPath> leafLoaders = CollectionFactory.newMap(); 081 082 public ClassFactoryClassPool(ClassLoader contextClassLoader) 083 { 084 super(null); 085 086 addClassLoaderIfNeeded(contextClassLoader); 087 } 088 089 /** 090 * Returns the nearest super-class of the provided class that can be converted to a {@link CtClass}. This is used to 091 * filter out Hibernate-style proxies (created as subclasses of oridnary classes). This will automatically add the 092 * class' classLoader to the pool's class path. 093 * 094 * @param clazz class to import 095 * @return clazz, or a super-class of clazz 096 */ 097 public Class importClass(Class clazz) 098 { 099 addClassLoaderIfNeeded(clazz.getClassLoader()); 100 101 while (true) 102 { 103 try 104 { 105 String name = ClassFabUtils.toJavaClassName(clazz); 106 107 get(name); 108 109 break; 110 } 111 catch (NotFoundException ex) 112 { 113 clazz = clazz.getSuperclass(); 114 } 115 } 116 117 return clazz; 118 } 119 120 /** 121 * Convienience method for adding to the ClassPath for a particular class loader. 122 * <p/> 123 * 124 * @param loader the class loader to add (derived from a loaded class, and may be null for some system classes) 125 */ 126 public synchronized void addClassLoaderIfNeeded(ClassLoader loader) 127 { 128 Set<ClassLoader> leaves = leafLoaders.keySet(); 129 if (loader == null || leaves.contains(loader) || allLoaders.contains(loader)) return; 130 131 // Work out if this loader is a child of a loader we have already. 132 ClassLoader existingLeaf = loader; 133 while (existingLeaf != null && !leaves.contains(existingLeaf)) 134 { 135 existingLeaf = existingLeaf.getParent(); 136 } 137 138 if (existingLeaf != null) 139 { 140 // The new loader is a child of an existing leaf. 141 // So we remove the old leaf before we add the new loader 142 ClassPath priorPath = leafLoaders.get(existingLeaf); 143 removeClassPath(priorPath); 144 leafLoaders.remove(existingLeaf); 145 } 146 147 ClassPath path = new LoaderClassPath(loader); 148 leafLoaders.put(loader, path); 149 insertClassPath(path); 150 151 ClassLoader l = loader; 152 while (l != null) 153 { 154 allLoaders.add(l); 155 l = l.getParent(); 156 } 157 } 158 159 /** 160 * Overriden to remove a deadlock producing synchronized block. We expect that the defineClass() methods will have 161 * been marked as accessible statically (by this class), so there's no need to set them accessible again. 162 */ 163 @Override 164 public Class toClass(CtClass ct, ClassLoader loader, ProtectionDomain domain) 165 throws CannotCompileException 166 { 167 Throwable failure; 168 169 try 170 { 171 byte[] b = ct.toBytecode(); 172 173 boolean hasDomain = domain != null; 174 175 Method method = hasDomain ? defineClassWithProtectionDomain : defineClass; 176 177 Object[] args = hasDomain 178 ? new Object[] {ct.getName(), b, 0, b.length, domain} 179 : new Object[] {ct.getName(), b, 0, b.length}; 180 181 return (Class) method.invoke(loader, args); 182 } 183 catch (InvocationTargetException ite) 184 { 185 failure = ite.getTargetException(); 186 } 187 catch (Exception ex) 188 { 189 failure = ex; 190 } 191 192 throw new CannotCompileException( 193 String.format("Failure defining new class %s: %s", 194 ct.getName(), 195 InternalUtils.toMessage(failure)), failure); 196 } 197 }