View Javadoc

1   /*
2    * Copyright (c) 2004-2005, University Health Network.  All rights reserved. Distributed under the BSD 
3    * license (see http://opensource.org/licenses/bsd-license.php).
4    *  
5    * Created on 24-Nov-2004
6    */
7   package ca.uhn.cache.proxy;
8   
9   import java.lang.reflect.InvocationHandler;
10  import java.lang.reflect.InvocationTargetException;
11  import java.lang.reflect.Method;
12  import java.util.ArrayList;
13  import java.util.Arrays;
14  import java.util.Collection;
15  import java.util.HashSet;
16  import java.util.Iterator;
17  import java.util.List;
18  import java.util.Set;
19  
20  import EDU.oswego.cs.dl.util.concurrent.Executor;
21  import EDU.oswego.cs.dl.util.concurrent.PooledExecutor;
22  
23  import ca.uhn.cache.IDataItem;
24  import ca.uhn.cache.IQuery;
25  import ca.uhn.cache.IQueryResult;
26  import ca.uhn.cache.ISemanticCache;
27  import ca.uhn.cache.exception.CacheException;
28  import ca.uhn.cache.exception.DataSourceException;
29  import ca.uhn.cache.impl.QueryResult;
30  import ca.uhn.cache.util.QueryProcessor;
31  import ca.uhn.cache.util.QueryResultUtil;
32  
33  /***
34   * InvocationHandler used to make a Proxy cache the results of method calls.
35   * 
36   * INCOMPLETE AND UNTESTED.  This is a draft implementation.  We don't need it right now, 
37   * so we'll come back to it as needed.   
38   *    
39   * @author <a href="mailto:bryan.tripp@uhn.on.ca">Bryan Tripp</a>
40   * @version $Revision: 1.1 $ updated on $Date: 2005/01/24 22:53:24 $ by $Author: bryan_tripp $
41   */
42  public class CachingInvocationHandler implements InvocationHandler {
43  
44      private Object myTarget;
45      private IMethodAdapter[] myAdapters;
46      private ISemanticCache myCache;
47      private Executor myExecutor;
48      
49      /***
50       * @param theTarget an object that has a query method that we want to cache results of  
51       * @param theAdapters an adapter corresponding to each cached method 
52       * @param theCache the cache
53       */
54      public CachingInvocationHandler(Object theTarget, IMethodAdapter[] theAdapters, ISemanticCache theCache) {
55          myTarget = theTarget;
56          myAdapters = theAdapters;
57          myCache = theCache;
58          myExecutor = new PooledExecutor();
59      }
60      
61      private IMethodAdapter getAdapter(Method theMethod) {
62          IMethodAdapter result = null;
63          
64          for (int i = 0; i < myAdapters.length && result == null; i++) {
65              if (adapts(myAdapters[i], theMethod)) {
66                  result = myAdapters[i];
67              }
68          }
69  
70          return result;
71      }
72      
73      private boolean adapts(IMethodAdapter theAdapter, Method theMethod) {
74          boolean result = theAdapter.getMethodName().equals(theMethod.getName()) 
75              && theAdapter.getArgTypes().length == theMethod.getParameterTypes().length;
76          
77          for (int i = 0; i < theMethod.getParameterTypes().length && result; i++) {
78              if ( !(theMethod.getParameterTypes()[i].equals(theAdapter.getArgTypes()[i])) ) {
79                  result = false;
80              }
81          }
82          
83          return result;
84      }
85  
86      /*** 
87       * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])
88       */
89      public Object invoke(Object theProxy, Method theMethod, Object[] theArgs) throws Throwable {
90          IMethodAdapter adapter = getAdapter(theMethod);
91          if (adapter == null) {
92              return theMethod.invoke(myTarget, theArgs);
93          } else {
94              //TODO: also need invoke() impl for listening method adapters 
95              return invoke(theMethod, theArgs, adapter);
96          }
97      }
98      
99      private Object invoke(Method theMethod, Object[] theArgs, IListeningMethodAdapter theAdapter) throws Throwable {
100         IListeningMethodAdapter.ResultListener client = theAdapter.wrapListener(theArgs);
101         
102         IQuery query = theAdapter.getParamsFromArgs(theArgs);
103         
104         try {
105             QueryProcessor processor = new QueryProcessor(query, myCache, theAdapter.getMaxGroups(), myExecutor);
106             IQuery[] remainderQueries = processor.getRemainderQueries();
107             
108             for (int i = 0; i < remainderQueries.length; i++) {
109                 Object[] args = theAdapter.getArgs(theArgs, remainderQueries[i]);
110                 ListeningResult source = new ListeningResult(theAdapter);
111                 Object[] listeningArgs = theAdapter.replaceListener(args, source);
112                 
113                 theMethod.invoke(myTarget, listeningArgs); //drop return value 
114                 processor.setSourceResult(remainderQueries[i], source);
115             }
116             
117             IQueryResult unfiltered = processor.getCombinedResult();
118             IQueryResult result = QueryResultUtil.filter(unfiltered, query);
119             
120             for (Iterator it = result.iterator(); it.hasNext(); ) {
121                 Object item = ((IDataItem) it.next()).getValue();
122                 
123                 if (theAdapter.includeInResults(item, theArgs)) {
124                     client.resultAvailable(item);                    
125                 }
126             }
127             
128         } catch (CacheException e) {
129             throw new DataSourceException("Problem in cache", e) {};
130         } catch (IllegalArgumentException e) {
131             throw new DataSourceException("Problem in cache", e) {};
132         } catch (IllegalAccessException e) {
133             throw new DataSourceException("Problem in cache", e) {};
134         } catch (InvocationTargetException e) {
135             throw new DataSourceException("Problem in cache", e) {};
136         }
137         
138         return null;
139     }
140     
141     //TODO: throw an exception declared by the method if possible? 
142     private Object invoke(Method theMethod, Object[] theArgs, IMethodAdapter theAdapter) 
143             throws Throwable {
144         
145         //TODO enhance the IQueryResult interface to keep track of any exceptions that were thrown during 
146         //the execution of the "execute" method.
147 
148         IQueryResult result = null;
149         IQuery query = theAdapter.getParamsFromArgs(theArgs);
150         
151         try {
152             QueryProcessor processor = new QueryProcessor(query, myCache, theAdapter.getMaxGroups(), myExecutor);
153             IQuery[] remainderQueries = processor.getRemainderQueries();
154             
155             //we run them separately because the results must be associated with their remainder query 
156             //  in the cache, not the larger original query 
157             for (int i = 0; i < remainderQueries.length; i++) {
158                 addResults(remainderQueries[i], theMethod, theArgs, theAdapter, processor);
159             }
160             
161             IQueryResult unfiltered = processor.getCombinedResult();
162             result = QueryResultUtil.filter(unfiltered, query);
163         } catch (CacheException e) {
164             throw new DataSourceException("Problem in cache", e) {};
165         }
166         
167         Collection toReturn = filterByMethodArgs(result, theAdapter, theArgs);
168 
169         Object retVal = null;
170         if (Object[].class.isAssignableFrom(theMethod.getReturnType())) {
171             retVal = toReturn.toArray(); //TODO: get array prototype            
172         } else if (Collection.class.isAssignableFrom(theMethod.getReturnType())) {
173             retVal = theMethod.getReturnType().newInstance();
174             ((Collection) retVal).addAll(toReturn);
175         }
176         
177         return retVal;
178     }
179     
180     private Collection filterByMethodArgs(IQueryResult theQueryResult, IMethodAdapter theAdapter, Object[] theArgs) {
181         List result = new ArrayList();
182         
183         Iterator it = theQueryResult.iterator();
184         while (it.hasNext()) {
185             IDataItem item = (IDataItem) it.next();
186             if (theAdapter.includeInResults(item, theArgs)) {
187                 result.add(item);                    
188             }
189         }
190         
191         return result;
192     }
193     
194     private void addResults(final IQuery theQuery, final Method theMethod, final Object[] theOriginalArgs, 
195             final IMethodAdapter theAdapter, final QueryProcessor theProcessor) throws DataSourceException {
196         
197         final Object[] newArgs = theAdapter.getArgs(theOriginalArgs, theQuery);
198         
199         Runnable r = new Runnable() {
200             public void run() {
201                 try {
202                     Object methodReurn = theMethod.invoke(myTarget, newArgs);
203                     Set data = getData(methodReurn, theAdapter.getDataInspector());
204                     QueryResult result = new QueryResult(data);
205                     theProcessor.setSourceResult(theQuery, result);
206                 } catch (IllegalArgumentException e) {
207                     theProcessor.declareException("Problem querying original source", e);
208                 } catch (IllegalAccessException e) {
209                     theProcessor.declareException("Problem querying original source", e);
210                 } catch (InvocationTargetException e) {
211                     theProcessor.declareException("Problem querying original source", e);
212                 }                
213             }
214         };
215         
216         try {
217             myExecutor.execute(r);
218         } catch (InterruptedException e) {
219             throw new DataSourceException(e) {};
220         }
221     }
222     
223     private static Set getData(Object theMethodReturn, IDataInspector theInspector) {
224         Collection rawData = null;
225         Collection wrappedData = new ArrayList();
226         
227         if (theMethodReturn instanceof Object[]) {
228             rawData = Arrays.asList((Object[]) theMethodReturn);
229         } else if (theMethodReturn instanceof Collection) {
230             rawData = (Collection) theMethodReturn;
231         } else {
232             throw new IllegalArgumentException(theMethodReturn + " not handled as return value");
233         }
234         
235         for (Iterator it = rawData.iterator(); it.hasNext(); ) {
236             IDataItem item = theInspector.wrap(it.next());
237             wrappedData.add(item);
238         }
239         
240         return new HashSet(wrappedData);
241     }
242     
243     /***
244      * Extends QueryResult to implement the ResultListener interface.  
245      * 
246      * @author <a href="mailto:bryan.tripp@uhn.on.ca">Bryan Tripp</a>
247      * @version $Revision: 1.1 $ updated on $Date: 2005/01/24 22:53:24 $ by $Author: bryan_tripp $
248      */
249     private class ListeningResult extends QueryResult implements IListeningMethodAdapter.ResultListener {
250 
251         private IMethodAdapter myMethodAdapter;
252         
253         public ListeningResult(IMethodAdapter theAdapter) {
254             myMethodAdapter = theAdapter;
255         }
256         
257         /*** 
258          * @see ca.uhn.cache.proxy.IListeningMethodAdapter.ResultListener#resultAvailable(java.lang.Object)
259          */
260         public void resultAvailable(Object theResult) {
261             IDataItem item = myMethodAdapter.getDataInspector().wrap(theResult);
262             this.add(item);
263         }
264         
265     }
266     
267 }