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    }