1
2
3
4
5
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
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);
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
142 private Object invoke(Method theMethod, Object[] theArgs, IMethodAdapter theAdapter)
143 throws Throwable {
144
145
146
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
156
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();
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 }