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 }