    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    }