001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    package org.apache.commons.proxy.factory.javassist;
019    
020    import javassist.CannotCompileException;
021    import javassist.CtClass;
022    import javassist.CtConstructor;
023    import javassist.CtMethod;
024    import org.apache.commons.proxy.Interceptor;
025    import org.apache.commons.proxy.Invoker;
026    import org.apache.commons.proxy.ObjectProvider;
027    import org.apache.commons.proxy.exception.ProxyFactoryException;
028    import org.apache.commons.proxy.factory.util.AbstractProxyClassGenerator;
029    import org.apache.commons.proxy.factory.util.AbstractSubclassingProxyFactory;
030    import org.apache.commons.proxy.factory.util.ProxyClassCache;
031    
032    import java.lang.reflect.Method;
033    
034    /**
035     * A <a href="http://www.jboss.org/products/javassist">Javassist</a>-based {@link org.apache.commons.proxy.ProxyFactory}
036     * implementation.
037     * <p/>
038     * <b>Dependencies</b>: <ul> <li>Javassist version 3.0 or greater</li> </ul> </p>
039     *
040     * @author James Carman
041     * @since 1.0
042     */
043    public class JavassistProxyFactory extends AbstractSubclassingProxyFactory
044    {
045    //**********************************************************************************************************************
046    // Fields
047    //**********************************************************************************************************************
048    
049        private static final String GET_METHOD_METHOD_NAME = "_javassistGetMethod";
050    
051        private static final ProxyClassCache delegatingProxyClassCache = new ProxyClassCache(
052                new DelegatingProxyClassGenerator());
053        private static final ProxyClassCache interceptorProxyClassCache = new ProxyClassCache(
054                new InterceptorProxyClassGenerator());
055        private static final ProxyClassCache invocationHandlerProxyClassCache = new ProxyClassCache(
056                new InvokerProxyClassGenerator());
057    
058    //**********************************************************************************************************************
059    // Static Methods
060    //**********************************************************************************************************************
061    
062        private static void addGetMethodMethod(CtClass proxyClass) throws CannotCompileException
063        {
064            final CtMethod method = new CtMethod(JavassistUtils.resolve(Method.class), GET_METHOD_METHOD_NAME,
065                    JavassistUtils.resolve(new Class[]{String.class, String.class, Class[].class}), proxyClass);
066            final String body = "try { return Class.forName($1).getMethod($2, $3); } catch( Exception e ) " +
067                    "{ throw new RuntimeException(\"Unable to look up method.\", e); }";
068            method.setBody(body);
069            proxyClass.addMethod(method);
070        }
071    
072    //**********************************************************************************************************************
073    // Other Methods
074    //**********************************************************************************************************************
075    
076        public Object createDelegatorProxy(ClassLoader classLoader, ObjectProvider targetProvider,
077                                           Class[] proxyClasses)
078        {
079            try
080            {
081                final Class clazz = delegatingProxyClassCache.getProxyClass(classLoader, proxyClasses);
082                return clazz.getConstructor(new Class[]{ObjectProvider.class})
083                        .newInstance(new Object[]{targetProvider});
084            }
085            catch (Exception e)
086            {
087                throw new ProxyFactoryException("Unable to instantiate proxy from generated proxy class.", e);
088            }
089        }
090    
091        public Object createInterceptorProxy(ClassLoader classLoader, Object target, Interceptor interceptor,
092                                             Class[] proxyClasses)
093        {
094            try
095            {
096                final Class clazz = interceptorProxyClassCache.getProxyClass(classLoader, proxyClasses);
097                return clazz.getConstructor(new Class[]{Object.class, Interceptor.class})
098                        .newInstance(new Object[]{target, interceptor});
099            }
100            catch (Exception e)
101            {
102                throw new ProxyFactoryException("Unable to instantiate proxy class instance.", e);
103            }
104        }
105    
106        public Object createInvokerProxy(ClassLoader classLoader, Invoker invoker,
107                                         Class[] proxyClasses)
108        {
109            try
110            {
111                final Class clazz = invocationHandlerProxyClassCache.getProxyClass(classLoader, proxyClasses);
112                return clazz.getConstructor(new Class[]{Invoker.class})
113                        .newInstance(new Object[]{invoker});
114            }
115            catch (Exception e)
116            {
117                throw new ProxyFactoryException("Unable to instantiate proxy from generated proxy class.", e);
118            }
119        }
120    
121    //**********************************************************************************************************************
122    // Inner Classes
123    //**********************************************************************************************************************
124    
125        private static class DelegatingProxyClassGenerator extends AbstractProxyClassGenerator
126        {
127            public Class generateProxyClass(ClassLoader classLoader, Class[] proxyClasses)
128            {
129                try
130                {
131                    final CtClass proxyClass = JavassistUtils.createClass(getSuperclass(proxyClasses));
132                    JavassistUtils.addField(ObjectProvider.class, "provider", proxyClass);
133                    final CtConstructor proxyConstructor = new CtConstructor(
134                            JavassistUtils.resolve(new Class[]{ObjectProvider.class}),
135                            proxyClass);
136                    proxyConstructor.setBody("{ this.provider = $1; }");
137                    proxyClass.addConstructor(proxyConstructor);
138                    JavassistUtils.addInterfaces(proxyClass, toInterfaces(proxyClasses));
139                    addHashCodeMethod(proxyClass);
140                    addEqualsMethod(proxyClass);
141                    final Method[] methods = getImplementationMethods(proxyClasses);
142                    for (int i = 0; i < methods.length; ++i)
143                    {
144                        if (!isEqualsMethod(methods[i]) && !isHashCode(methods[i]))
145                        {
146                            final Method method = methods[i];
147                            final CtMethod ctMethod = new CtMethod(JavassistUtils.resolve(method.getReturnType()),
148                                    method.getName(),
149                                    JavassistUtils.resolve(method.getParameterTypes()),
150                                    proxyClass);
151                            final String body = "{ return ( $r ) ( ( " + method.getDeclaringClass().getName() +
152                                    " )provider.getObject() )." +
153                                    method.getName() + "($$); }";
154                            ctMethod.setBody(body);
155                            proxyClass.addMethod(ctMethod);
156                        }
157                    }
158                    return proxyClass.toClass(classLoader);
159                }
160                catch (CannotCompileException e)
161                {
162                    throw new ProxyFactoryException("Could not compile class.", e);
163                }
164            }
165        }
166    
167        private static class InterceptorProxyClassGenerator extends AbstractProxyClassGenerator
168        {
169            public Class generateProxyClass(ClassLoader classLoader, Class[] proxyClasses)
170            {
171                try
172                {
173                    final CtClass proxyClass = JavassistUtils.createClass(getSuperclass(proxyClasses));
174                    final Method[] methods = getImplementationMethods(proxyClasses);
175                    JavassistUtils.addInterfaces(proxyClass, toInterfaces(proxyClasses));
176                    JavassistUtils.addField(Object.class, "target", proxyClass);
177                    JavassistUtils.addField(Interceptor.class, "interceptor", proxyClass);
178                    addGetMethodMethod(proxyClass);
179                    addHashCodeMethod(proxyClass);
180                    addEqualsMethod(proxyClass);
181                    final CtConstructor proxyConstructor = new CtConstructor(
182                            JavassistUtils.resolve(
183                                    new Class[]{Object.class, Interceptor.class}),
184                            proxyClass);
185                    proxyConstructor
186                            .setBody(
187                                    "{\n\tthis.target = $1;\n\tthis.interceptor = $2; }");
188                    proxyClass.addConstructor(proxyConstructor);
189                    for (int i = 0; i < methods.length; ++i)
190                    {
191                        if (!isEqualsMethod(methods[i]) && !isHashCode(methods[i]))
192                        {
193                            final CtMethod method = new CtMethod(JavassistUtils.resolve(methods[i].getReturnType()),
194                                    methods[i].getName(),
195                                    JavassistUtils.resolve(methods[i].getParameterTypes()),
196                                    proxyClass);
197                            final Class invocationClass = JavassistInvocation
198                                    .getMethodInvocationClass(classLoader, methods[i]);
199    
200                            final String body = "{\n\t return ( $r ) interceptor.intercept( new " + invocationClass.getName() +
201                                    "( " + GET_METHOD_METHOD_NAME + "(\"" + methods[i].getDeclaringClass().getName() +
202                                    "\", \"" + methods[i].getName() + "\", $sig), target, $args ) );\n }";
203                            method.setBody(body);
204                            proxyClass.addMethod(method);
205                        }
206    
207                    }
208                    return proxyClass.toClass(classLoader);
209                }
210                catch (CannotCompileException e)
211                {
212                    throw new ProxyFactoryException("Could not compile class.", e);
213                }
214            }
215    
216    
217        }
218    
219        private static void addEqualsMethod(CtClass proxyClass)
220                throws CannotCompileException
221        {
222            final CtMethod equalsMethod = new CtMethod(JavassistUtils.resolve(Boolean.TYPE), "equals",
223                    JavassistUtils.resolve(new Class[]{Object.class}), proxyClass);
224            final String body = "{\n\treturn this == $1;\n}";
225            equalsMethod.setBody(body);
226            proxyClass.addMethod(equalsMethod);
227        }
228    
229        private static void addHashCodeMethod(CtClass proxyClass)
230                throws CannotCompileException
231        {
232            final CtMethod hashCodeMethod = new CtMethod(JavassistUtils.resolve(Integer.TYPE), "hashCode",
233                    new CtClass[0], proxyClass);
234            hashCodeMethod.setBody("{\n\treturn System.identityHashCode(this);\n}");
235            proxyClass.addMethod(hashCodeMethod);
236        }
237    
238        private static class InvokerProxyClassGenerator extends AbstractProxyClassGenerator
239        {
240            public Class generateProxyClass(ClassLoader classLoader, Class[] proxyClasses)
241            {
242                try
243                {
244                    final CtClass proxyClass = JavassistUtils.createClass(getSuperclass(proxyClasses));
245                    final Method[] methods = getImplementationMethods(proxyClasses);
246                    JavassistUtils.addInterfaces(proxyClass, toInterfaces(proxyClasses));
247                    JavassistUtils.addField(Invoker.class, "invoker", proxyClass);
248                    final CtConstructor proxyConstructor = new CtConstructor(
249                            JavassistUtils.resolve(
250                                    new Class[]{Invoker.class}),
251                            proxyClass);
252                    proxyConstructor
253                            .setBody("{\n\tthis.invoker = $1; }");
254                    proxyClass.addConstructor(proxyConstructor);
255                    addGetMethodMethod(proxyClass);
256                    addHashCodeMethod(proxyClass);
257                    addEqualsMethod(proxyClass);
258                    for (int i = 0; i < methods.length; ++i)
259                    {
260                        if (!isEqualsMethod(methods[i]) && !isHashCode(methods[i]))
261                        {
262                            final CtMethod method = new CtMethod(JavassistUtils.resolve(methods[i].getReturnType()),
263                                    methods[i].getName(),
264                                    JavassistUtils.resolve(methods[i].getParameterTypes()),
265                                    proxyClass);
266                            final String body = "{\n\t return ( $r ) invoker.invoke( this, " + GET_METHOD_METHOD_NAME + "(\"" +
267                                    methods[i].getDeclaringClass().getName() +
268                                    "\", \"" + methods[i].getName() + "\", $sig), $args );\n }";
269                            method.setBody(body);
270                            proxyClass.addMethod(method);
271                        }
272                    }
273                    return proxyClass.toClass(classLoader);
274                }
275                catch (CannotCompileException e)
276                {
277                    throw new ProxyFactoryException("Could not compile class.", e);
278                }
279            }
280        }
281    }
282