View Javadoc

1   /* Copyright (2005-2007) Schibsted Søk AS
2    *   This file is part of Sesat Commons.
3    *
4    *   Sesat Commons is free software: you can redistribute it and/or modify
5    *   it under the terms of the GNU Lesser General Public License as published by
6    *   the Free Software Foundation, either version 3 of the License, or
7    *   (at your option) any later version.
8    *
9    *   Sesat Commons is distributed in the hope that it will be useful,
10   *   but WITHOUT ANY WARRANTY; without even the implied warranty of
11   *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   *   GNU Lesser General Public License for more details.
13   *
14   *   You should have received a copy of the GNU Lesser General Public License
15   *   along with Sesat Commons.  If not, see <http://www.gnu.org/licenses/>.
16   *
17   * BasicInvocationHandler.java
18   *
19   * Created on 21 February 2006, 18:33
20   *
21   */
22  
23  package no.sesat.commons.ioc;
24  
25  import java.lang.reflect.InvocationHandler;
26  import java.lang.reflect.InvocationTargetException;
27  import java.lang.reflect.Method;
28  import java.util.ArrayList;
29  import java.util.Arrays;
30  import java.util.Collections;
31  import java.util.HashMap;
32  import java.util.List;
33  import java.util.Map;
34  import java.util.concurrent.locks.ReentrantReadWriteLock;
35  import org.apache.log4j.Logger;
36  
37  /** InvocationHandler implementation to be used by ContextWrapper.
38   * Constructed with a list of BaseContexts that are used to proxy to.
39   * This is a basic implementation that proxy's between identical method signatures.
40   * The order in the BaseContext list is important as the first method with the correct signature is used.
41   * <br/>
42   * 
43   * Serialisation depends on whether all supplied contexts are themselves serialisable.
44   *
45   * @version $Id: BasicInvocationHandler.java 1127 2009-01-21 16:16:08Z ssmiweve $
46   * @author <a href="mailto:mick@wever.org">Michael Semb Wever</a>
47   */
48  final class BasicInvocationHandler implements InvocationHandler, java.io.Serializable {
49  
50      private final transient Map<Method,Map<List<Class<?>>,Method>> methodCache
51              = new HashMap<Method,Map<List<Class<?>>,Method>>();
52      private final transient Map<Method,Map<List<Class<?>>,BaseContext>> contextCache
53              = new HashMap<Method,Map<List<Class<?>>,BaseContext>>();
54  
55      /** threading lock to the cache maps since they are not synchronised, 
56       * and it's overkill to make them Hashtables. **/
57      private final transient ReentrantReadWriteLock cacheGate = new ReentrantReadWriteLock();
58  
59     // Attributes ----------------------------------------------------
60  
61      private final List<BaseContext> contexts;
62  
63     // Static --------------------------------------------------------
64  
65      private static final Invoker FAIR_DINKUM = new FairDinkumInvoker();
66      private static final Invoker CACTUS = new CactusInvoker();
67  
68      private static final Logger LOG = Logger.getLogger(BasicInvocationHandler.class);
69  
70      private static final String ERR_METHOD_NOT_IN_INTERFACE
71              = "Unable to proxy to the contexts associated to this BasicInvocationHandler for ";
72      private static final String ERR_METHOD_NOT_IN_EXACT_INTERFACE
73              = "No exact signature to the contexts associated to this BasicInvocationHandler for ";
74      private static final String DEBUG_LOOKING_FOR = " Looking for ";
75      private static final String DEBUG_LOOKING_IN = "Looking in ";
76      private static final String DEBUG_FOUND = "Found method while: ";
77      private static final String DEBUG_NOT_FOUND = "Did not found method while: ";
78      private static final String DEBUG_ADD_TO_CACHE = "Adding to cache ";
79      private static final String DEBUG_CACHE_USED_FOR = "Using cache for ";
80  
81     // Constructors --------------------------------------------------
82  
83      /**
84       * Creates a new instance of BasicInvocationHandler.
85       */
86      BasicInvocationHandler(final BaseContext... cxts) {
87          this(Arrays.asList(cxts));
88  
89      }
90  
91      /**
92       * Creates a new instance of BasicInvocationHandler.
93       */
94      BasicInvocationHandler(final List<? extends BaseContext> cxts) {
95          contexts = Collections.unmodifiableList(cxts);
96  
97      }
98  
99     // Public --------------------------------------------------------
100 
101    // InvocationHandler implementation ----------------------------------------------
102 
103 
104     /** {@inheritDoc}
105      */
106     public Object invoke(
107             final Object object,
108             final Method method,
109             final Object[] objArr) throws Throwable {
110 
111         boolean paramsNotNull = true;
112         // construct method's parameter signature
113         final List<Class<?>> paramSignature = new ArrayList<Class<?>>();
114         if(objArr != null){
115             for (Object obj : objArr) {
116                 paramSignature.add(obj == null ? null : obj.getClass());
117                 paramsNotNull &= obj != null;
118             }
119         }
120 
121         // This is one of the applications performance hotspots.
122         //  It is a benefit to keep a cache to remember what method to use.
123         final Method cachedMethod = checkCache(method, paramSignature);
124         if(cachedMethod != null){
125             LOG.trace( DEBUG_CACHE_USED_FOR + method);
126             return FAIR_DINKUM.invoke(cachedMethod, getContextFromCache(method,paramSignature), objArr);
127         }
128 
129         // first pass is to find an exact signature match, we can skip if any of the params were null.
130         if(paramsNotNull){
131             try{
132                 return invokeExactSignature(FAIR_DINKUM, object, method, paramSignature, objArr);
133 
134             }  catch (NoSuchMethodException ex) {
135                 // handled exception
136                 LOG.trace( ex);
137             }
138         }
139 
140         return invokeSubclassedSignature(FAIR_DINKUM, object, method, paramSignature, objArr);
141 
142     }
143 
144    // Package protected ---------------------------------------------
145 
146     boolean assertContextContract(final Class<? extends BaseContext> cxtClass){
147 
148         try{
149             for( Method m : cxtClass.getMethods() ){
150                 invokeExactSignature(CACTUS, null, m, Arrays.asList(m.getParameterTypes()), null);
151             }
152         }catch(NoSuchMethodException nsme){
153             LOG.error(nsme.getMessage(), nsme);
154             return false;
155         }catch(IllegalAccessException iae){
156             LOG.error(iae.getMessage(), iae);
157             return false;
158         }catch(InvocationTargetException ite){
159             LOG.error(ite.getMessage(), ite);
160             return false;
161         }
162         return true;
163     }
164 
165    // Protected -----------------------------------------------------
166 
167    // Private -------------------------------------------------------
168 
169     /** Look for an exact signature method match to the argument objects passed in.
170      **/
171     private Object invokeExactSignature(
172             final Invoker invoker,
173             final Object object,
174             final Method method,
175             final List<Class<?>> paramSignature,
176             final Object[] objArr) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException{
177 
178         for (BaseContext cxt : contexts) {
179 
180             final Class cls = cxt.getClass();
181             try {
182 
183                 final Method m = cls.getMethod(method.getName(), paramSignature.toArray(new Class[paramSignature.size()]));
184                 if (m != null) {
185                     if(LOG.isTraceEnabled()){
186                         LOG.trace( DEBUG_FOUND
187                                 + DEBUG_LOOKING_IN + cls.getName()
188                                 + DEBUG_LOOKING_FOR + method.getName() + toString(paramSignature));
189                     }
190 
191                     addToCache(method, paramSignature, m, cxt);
192                     return invoker.invoke(m, cxt, objArr);
193 
194                 }
195             }  catch (NoSuchMethodException ex) {
196                 if(LOG.isTraceEnabled()){
197                     LOG.trace( DEBUG_NOT_FOUND
198                             + DEBUG_LOOKING_IN + cls.getName()
199                             + DEBUG_LOOKING_FOR + method.getName() + toString(paramSignature));
200                 }
201             }
202         }
203         throw new NoSuchMethodException(ERR_METHOD_NOT_IN_EXACT_INTERFACE + method.getName());
204     }
205 
206     /** Look for an signature matching any superclasses to the argument objects passed in.
207      **/
208     private Object invokeSubclassedSignature(
209             final Invoker invoker,
210             final Object object,
211             final Method method,
212             final List<Class<?>> paramSignature,
213             final Object[] objArr) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
214 
215         for (BaseContext cxt : contexts) {
216 
217             final Class cls = cxt.getClass();
218             for (Method m : cls.getMethods()) {
219 
220                 if (m.getName().equals(method.getName())) {
221 
222                     final Class[] cArr = m.getParameterTypes();
223                     boolean assignableFrom = true;
224                     for (int k = 0; assignableFrom && k < cArr.length; ++k) {
225                         assignableFrom = paramSignature.get(k) == null || cArr[k].isAssignableFrom(paramSignature.get(k));
226                     }
227                     if (assignableFrom) {
228                         if(LOG.isTraceEnabled()){
229                             LOG.trace( DEBUG_FOUND
230                                 + DEBUG_LOOKING_IN + cls.getName()
231                                 + DEBUG_LOOKING_FOR + method.getName() + toString(paramSignature));
232                         }
233 
234                         addToCache(method, paramSignature, m, cxt);
235                         return invoker.invoke(m, cxt, objArr);
236 
237                     }else{
238                         if(LOG.isTraceEnabled()){
239                             LOG.trace( DEBUG_NOT_FOUND
240                                 + DEBUG_LOOKING_IN + cls.getName()
241                                 + DEBUG_LOOKING_FOR + method.getName() + toString(paramSignature));
242                         }
243                     }
244                 }
245             }
246         }
247         final NoSuchMethodException e = new NoSuchMethodException(ERR_METHOD_NOT_IN_INTERFACE + method.getName());
248         LOG.error("",e);
249         throw e;
250     }
251 
252 
253 
254     /** Check the cache incase this method has already been called once.
255      ***/
256     private Method checkCache(final Method method, final List<Class<?>> paramSignature){
257 
258         try{
259             cacheGate.readLock().lock();
260 
261             Method cachedMethod = null;
262             final Map<List<Class<?>>,Method> map = methodCache.get(method);
263             if(map != null){
264                 cachedMethod = map.get(paramSignature);
265             }
266             return cachedMethod;
267 
268         }finally{
269             cacheGate.readLock().unlock();
270         }
271     }
272 
273     /** Get from the cache the BaseContext the method comes from.
274      * Presumed that checkCache(..) has been called and was successfull.
275      **/
276     private BaseContext getContextFromCache(final Method method, final List<Class<?>> paramSignature){
277 
278         try{
279             cacheGate.readLock().lock();
280 
281             final Map<List<Class<?>>,BaseContext> map = contextCache.get(method);
282             assert map != null;
283 
284             final BaseContext cachedContext = map.get(paramSignature);
285             assert cachedContext != null;
286 
287             return cachedContext;
288 
289         }finally{
290             cacheGate.readLock().unlock();
291         }
292     }
293 
294     /** Add to the cache the methodTo and contextTo to use for calls for methodFrom and paramSignature.
295      ***/
296     private void addToCache(
297             final Method methodFrom,
298             final List<Class<?>> paramSignature,
299             final Method methodTo,
300             final BaseContext contextTo){
301 
302         LOG.trace( DEBUG_ADD_TO_CACHE + methodTo);
303 
304         try{
305             cacheGate.writeLock().lock();
306 
307             Map<List<Class<?>>,Method> methodMap = methodCache.get(methodFrom);
308             if(methodMap == null){
309                 methodMap = new HashMap<List<Class<?>>,Method>();
310                 methodCache.put(methodFrom, methodMap);
311             }
312             methodMap.put(paramSignature, methodTo);
313 
314             Map<List<Class<?>>,BaseContext> contextMap = contextCache.get(methodFrom);
315             if(contextMap == null){
316                 contextMap = new HashMap<List<Class<?>>,BaseContext>();
317                 contextCache.put(methodFrom, contextMap);
318             }
319             contextMap.put(paramSignature, contextTo);
320 
321         }finally{
322             cacheGate.writeLock().unlock();
323         }
324     }
325 
326     /** Get a string representation of paramSignature.
327      **/
328     private String toString(final List<Class<?>> paramSignature){
329 
330         final StringBuilder sb = new StringBuilder();
331         for (Class cls : paramSignature) {
332             sb.append(cls == null ? "null" : cls.getSimpleName() + ", ");
333         }
334         return sb.toString();
335     }
336 
337    // Inner classes -------------------------------------------------
338 
339     private static interface Invoker{
340         /** A method has been found in a BaseContext.
341          * Invoke it.
342          **/
343         Object invoke(final Method method, final BaseContext cxt, final Object[] objArr)
344                 throws IllegalAccessException, InvocationTargetException;
345     }
346 
347     private static class FairDinkumInvoker implements Invoker{
348         public Object invoke(
349                 final Method method,
350                 final BaseContext cxt,
351                 final Object[] objArr)
352                     throws IllegalAccessException, InvocationTargetException{
353 
354             method.setAccessible(true);
355             return method.invoke(cxt, objArr);
356         }
357     }
358 
359     private static class CactusInvoker implements Invoker{
360         public Object invoke(
361                 final Method method,
362                 final BaseContext cxt,
363                 final Object[] objArr)
364                     throws IllegalAccessException, InvocationTargetException{
365 
366             return null;
367         }
368     }
369 }