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
015package org.apache.tapestry5.ioc.internal.services;
016
017import javassist.*;
018import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
019import org.apache.tapestry5.ioc.internal.util.InternalUtils;
020import org.apache.tapestry5.ioc.services.ClassFabUtils;
021
022import java.lang.reflect.InvocationTargetException;
023import java.lang.reflect.Method;
024import java.security.AccessController;
025import java.security.PrivilegedActionException;
026import java.security.PrivilegedExceptionAction;
027import java.security.ProtectionDomain;
028import java.util.Map;
029import 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 */
036public 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}