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;
019    
020    import java.io.Serializable;
021    import java.lang.reflect.InvocationHandler;
022    import java.lang.reflect.InvocationTargetException;
023    import java.lang.reflect.Method;
024    import java.lang.reflect.Proxy;
025    
026    /**
027     * A <code>ProxyFactory</code> can be used to create three different &quot;flavors&quot; of proxy objects.
028     * <p/>
029     * <ul>
030     * <li>Delegator - the proxy will delegate to an object provided by an {@link ObjectProvider}</li>
031     * <li>Interceptor - the proxy will pass each method invocation through an {@link Interceptor}</li>
032     * <li>Invoker - the proxy will allow an {@link Invoker} to handle all method invocations</li>
033     * </ul>
034     * <p/>
035     * <p>
036     * Originally, the ProxyFactory class was an interface.  However, to allow for future changes to the
037     * class without breaking binary or semantic compatibility, it has been changed to a concrete class.
038     * <p/>
039     * </p>
040     * <p>
041     * <b>Note</b>: This class uses Java reflection.  For more efficient proxies, try using either
042     * {@link org.apache.commons.proxy.factory.cglib.CglibProxyFactory CglibProxyFactory} or
043     * {@link org.apache.commons.proxy.factory.javassist.JavassistProxyFactory JavassistProxyFactory} instead.
044     * </p>
045     *
046     * @author James Carman
047     * @since 1.0
048     */
049    public class ProxyFactory
050    {
051    //**********************************************************************************************************************
052    // Other Methods
053    //**********************************************************************************************************************
054    
055        /**
056         * Returns true if all <code>proxyClasses</code> are interfaces.
057         *
058         * @param proxyClasses the proxy classes
059         * @return true if all <code>proxyClasses</code> are interfaces
060         */
061        public boolean canProxy(Class[] proxyClasses)
062        {
063            for (int i = 0; i < proxyClasses.length; i++)
064            {
065                Class proxyClass = proxyClasses[i];
066                if (!proxyClass.isInterface())
067                {
068                    return false;
069                }
070            }
071            return true;
072        }
073    
074        /**
075         * Creates a proxy which delegates to the object provided by <code>delegateProvider</code>.  The proxy will be
076         * generated using the current thread's "context class loader."
077         *
078         * @param delegateProvider the delegate provider
079         * @param proxyClasses     the interfaces that the proxy should implement
080         * @return a proxy which delegates to the object provided by the target object provider
081         */
082        public Object createDelegatorProxy(ObjectProvider delegateProvider, Class[] proxyClasses)
083        {
084            return createDelegatorProxy(Thread.currentThread().getContextClassLoader(), delegateProvider, proxyClasses);
085        }
086    
087        /**
088         * Creates a proxy which delegates to the object provided by <code>delegateProvider</code>.
089         *
090         * @param classLoader      the class loader to use when generating the proxy
091         * @param delegateProvider the delegate provider
092         * @param proxyClasses     the interfaces that the proxy should implement
093         * @return a proxy which delegates to the object provided by the target <code>delegateProvider>
094         */
095        public Object createDelegatorProxy(ClassLoader classLoader, ObjectProvider delegateProvider,
096                                           Class[] proxyClasses)
097        {
098            return Proxy.newProxyInstance(classLoader, proxyClasses,
099                    new DelegatorInvocationHandler(delegateProvider));
100        }
101    
102        /**
103         * Creates a proxy which passes through a {@link Interceptor interceptor} before eventually reaching the
104         * <code>target</code> object.  The proxy will be generated using the current thread's "context class loader."
105         *
106         * @param target       the target object
107         * @param interceptor  the method interceptor
108         * @param proxyClasses the interfaces that the proxy should implement
109         * @return a proxy which passes through a {@link Interceptor interceptor} before eventually reaching the
110         *         <code>target</code> object.
111         */
112        public Object createInterceptorProxy(Object target, Interceptor interceptor,
113                                             Class[] proxyClasses)
114        {
115            return createInterceptorProxy(Thread.currentThread().getContextClassLoader(), target, interceptor,
116                    proxyClasses);
117        }
118    
119        /**
120         * Creates a proxy which passes through a {@link Interceptor interceptor} before eventually reaching the
121         * <code>target</code> object.
122         *
123         * @param classLoader  the class loader to use when generating the proxy
124         * @param target       the target object
125         * @param interceptor  the method interceptor
126         * @param proxyClasses the interfaces that the proxy should implement.
127         * @return a proxy which passes through a {@link Interceptor interceptor} before eventually reaching the
128         *         <code>target</code> object.
129         */
130        public Object createInterceptorProxy(ClassLoader classLoader, Object target, Interceptor interceptor,
131                                             Class[] proxyClasses)
132        {
133            return Proxy
134                    .newProxyInstance(classLoader, proxyClasses, new InterceptorInvocationHandler(target, interceptor));
135        }
136    
137        /**
138         * Creates a proxy which uses the provided {@link Invoker} to handle all method invocations.  The proxy will be
139         * generated using the current thread's "context class loader."
140         *
141         * @param invoker      the invoker
142         * @param proxyClasses the interfaces that the proxy should implement
143         * @return a proxy which uses the provided {@link Invoker} to handle all method invocations
144         */
145        public Object createInvokerProxy(Invoker invoker, Class[] proxyClasses)
146        {
147            return createInvokerProxy(Thread.currentThread().getContextClassLoader(), invoker,
148                    proxyClasses);
149        }
150    
151        /**
152         * Creates a proxy which uses the provided {@link Invoker} to handle all method invocations.
153         *
154         * @param classLoader  the class loader to use when generating the proxy
155         * @param invoker      the invoker
156         * @param proxyClasses the interfaces that the proxy should implement
157         * @return a proxy which uses the provided {@link Invoker} to handle all method invocations
158         */
159        public Object createInvokerProxy(ClassLoader classLoader, Invoker invoker,
160                                         Class[] proxyClasses)
161        {
162            return Proxy.newProxyInstance(classLoader, proxyClasses, new InvokerInvocationHandler(invoker));
163        }
164    
165    //**********************************************************************************************************************
166    // Inner Classes
167    //**********************************************************************************************************************
168    
169        private static class DelegatorInvocationHandler extends AbstractInvocationHandler
170        {
171            private final ObjectProvider delegateProvider;
172    
173            protected DelegatorInvocationHandler(ObjectProvider delegateProvider)
174            {
175                this.delegateProvider = delegateProvider;
176            }
177    
178            public Object invokeImpl(Object proxy, Method method, Object[] args) throws Throwable
179            {
180                try
181                {
182                    return method.invoke(delegateProvider.getObject(), args);
183                }
184                catch (InvocationTargetException e)
185                {
186                    throw e.getTargetException();
187                }
188            }
189        }
190    
191        private static class InterceptorInvocationHandler extends AbstractInvocationHandler
192        {
193            private final Object target;
194            private final Interceptor methodInterceptor;
195    
196            public InterceptorInvocationHandler(Object target, Interceptor methodInterceptor)
197            {
198                this.target = target;
199                this.methodInterceptor = methodInterceptor;
200            }
201    
202            public Object invokeImpl(Object proxy, Method method, Object[] args) throws Throwable
203            {
204                final ReflectionInvocation invocation = new ReflectionInvocation(target, method, args);
205                return methodInterceptor.intercept(invocation);
206            }
207        }
208    
209        private abstract static class AbstractInvocationHandler implements InvocationHandler, Serializable
210        {
211            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
212            {
213                if (isHashCode(method))
214                {
215                    return new Integer(System.identityHashCode(proxy));
216                }
217                else if (isEqualsMethod(method))
218                {
219                    return Boolean.valueOf(proxy == args[0]);
220                }
221                else
222                {
223                    return invokeImpl(proxy, method, args);
224                }
225            }
226    
227            protected abstract Object invokeImpl(Object proxy, Method method, Object[] args) throws Throwable;
228        }
229    
230        private static class InvokerInvocationHandler extends AbstractInvocationHandler
231        {
232            private final Invoker invoker;
233    
234            public InvokerInvocationHandler(Invoker invoker)
235            {
236                this.invoker = invoker;
237            }
238    
239            public Object invokeImpl(Object proxy, Method method, Object[] args) throws Throwable
240            {
241                return invoker.invoke(proxy, method, args);
242            }
243        }
244    
245        protected static boolean isHashCode(Method method)
246        {
247            return "hashCode".equals(method.getName()) &&
248                    Integer.TYPE.equals(method.getReturnType()) &&
249                    method.getParameterTypes().length == 0;
250        }
251    
252        protected static boolean isEqualsMethod(Method method)
253        {
254            return "equals".equals(method.getName()) &&
255                    Boolean.TYPE.equals(method.getReturnType()) &&
256                    method.getParameterTypes().length == 1 &&
257                    Object.class.equals(method.getParameterTypes()[0]);
258        }
259    
260        private static class ReflectionInvocation implements Invocation, Serializable
261        {
262            private final Method method;
263            private final Object[] arguments;
264            private final Object target;
265    
266            public ReflectionInvocation(Object target, Method method, Object[] arguments)
267            {
268                this.method = method;
269                this.arguments = (arguments == null ? ProxyUtils.EMPTY_ARGUMENTS : arguments);
270                this.target = target;
271            }
272    
273            public Object[] getArguments()
274            {
275                return arguments;
276            }
277    
278            public Method getMethod()
279            {
280                return method;
281            }
282    
283            public Object getProxy()
284            {
285                return target;
286            }
287    
288            public Object proceed() throws Throwable
289            {
290                try
291                {
292                    return method.invoke(target, arguments);
293                }
294                catch (InvocationTargetException e)
295                {
296                    throw e.getTargetException();
297                }
298            }
299        }
300    }
301