001: package org.mockejb.interceptor;
002:
003: import java.util.*;
004: import java.lang.reflect.*;
005:
006: /**
007: * Performs the invocation of interceptors in their order in the
008: * interceptor list.
009: * Each interceptor is called in turn until we get to the target object.
010: * At this point, the target object's method is called using reflection.
011: * Also keeps the invocation's custom context (properties).
012: * To be thread safe, clients should create a new object of this
013: * class for each method call.
014: *
015: * @author Alexander Ananiev
016: */
017: public class InvocationContext {
018:
019: private transient ListIterator iter;
020:
021: // TODO: context should be Threadlocal??
022: private Map contextProperties = new HashMap();
023: private List interceptorList;
024:
025: private Object proxyObj;
026: private Object targetObj;
027: private Method targetMethod;
028: private Method proxyMethod;
029: private Object[] paramVals;
030:
031: private Object returnObject;
032: private Throwable thrownThrowable;
033:
034: /**
035: * Creates a new instance of the InvocationContext.
036: *
037: * @param interceptorList interceptors that will be invoked before the target method
038: * @param proxyObj object that was intercepted,
039: * most likely it is the dynamic proxy object. Can be null.
040: * @param proxyMethod method invoked on the proxy. The declaring class of the method is the
041: * interface's class.
042: * @param targetObj target object being called.
043: * @param targetMethod method being called.
044: * @param paramVals parameter values
045: */
046: public InvocationContext(List interceptorList, Object proxyObj,
047: Method proxyMethod, Object targetObj, Method targetMethod,
048: Object[] paramVals) {
049:
050: this (interceptorList, proxyObj, proxyMethod, targetObj,
051: targetMethod, paramVals, null);
052:
053: }
054:
055: /**
056: * Creates a new instance of the InvocationContext.
057: *
058: * @param interceptorList interceptors that will be invoked before the target method
059: * @param proxyObj object that was intercepted,
060: * most likely it is the dynamic proxy object. Can be null if the object is not known.
061: * @param proxyMethod method invoked on the proxy. The declaring class of the method is the
062: * interface's class.
063: * @param targetObj target object being called.
064: * @param targetMethod method being called.
065: * @param paramVals parameter values
066: * @param contextProperties any additional context info for the interceptors
067: *
068: */
069: public InvocationContext(List interceptorList, Object proxyObj,
070: Method proxyMethod, Object targetObj, Method targetMethod,
071: Object[] paramVals, Map contextProperties) {
072:
073: this .proxyObj = proxyObj;
074: this .proxyMethod = proxyMethod;
075: this .targetObj = targetObj;
076: this .targetMethod = targetMethod;
077: this .paramVals = paramVals;
078:
079: setInterceptorList(interceptorList);
080: if (contextProperties != null)
081: this .contextProperties = new HashMap(contextProperties);
082:
083: }
084:
085: /**
086: * Sets the list of interceptors
087: * @param interceptorList list to set
088: */
089: public void setInterceptorList(final List interceptorList) {
090:
091: verifyInterceptors(interceptorList);
092: this .interceptorList = interceptorList;
093: reset();
094:
095: }
096:
097: public List getInterceptorList() {
098:
099: return this .interceptorList;
100:
101: }
102:
103: /**
104: * Returns the iterator currently in use to traverse the
105: * interceptor list. Clients can use the returned iterator
106: * to find out their place in the call chain.
107: *
108: * @return list iterator
109: */
110: public ListIterator getInterceptorIterator() {
111: return iter;
112: }
113:
114: /**
115: * Makes sure that interceptors implement the Interceptor interface.
116: * Otherwise, throws IllegalArgumentException.
117: *
118: */
119: private void verifyInterceptors(List interceptorList) {
120:
121: if (interceptorList == null) {
122: throw new IllegalArgumentException(
123: "Interceptor list can't be null");
124: }
125:
126: Iterator i = interceptorList.iterator();
127: while (i.hasNext()) {
128: Object interceptor = i.next();
129: if (!(interceptor instanceof Interceptor)) {
130: throw new IllegalArgumentException(
131: "Object "
132: + interceptor
133: + " in the interceptor list does not implement interceptor interface");
134: }
135: }
136:
137: }
138:
139: /**
140: * Resets the interceptor iterator.
141: * @deprecated
142: *
143: */
144: public void reset() {
145: iter = interceptorList.listIterator();
146: }
147:
148: /**
149: * Clears the context properties and resets the interceptor iterator.
150: *
151: */
152: public void clear() {
153: reset();
154: contextProperties.clear();
155: }
156:
157: /**
158: * Calls the next interceptor in the list. If this is the end of the list,
159: * calls the given method of the target object using reflection if the target
160: * object is not null.
161: * "proceed" name is consistent with the "proceed" keyword used by AspectJ for "around"
162: * advices.
163: * Use "getReturnObject" to get the return value for this invocation.
164: */
165: public void proceed() throws Exception {
166:
167: // Check if we're at the head of the chain
168: if (!iter.hasPrevious()) {
169: // recreate iterator in case if the list changed
170: reset();
171: }
172:
173: if (iter.hasNext()) {
174: Interceptor nextInterceptor = (Interceptor) iter.next();
175: try {
176: nextInterceptor.intercept(this );
177: }
178: // Record throwable
179: catch (Throwable throwable) {
180: // store it for the record
181: thrownThrowable = throwable;
182:
183: // Convert into exception
184: if (throwable instanceof Error) {
185: throw (Error) throwable;
186: } else if (throwable instanceof Exception) {
187: throw (Exception) throwable;
188: }
189: }
190: //in any event we need to restore the iterator to return where we were
191: finally {
192: iter.previous();
193: }
194: }
195:
196: }
197:
198: /**
199: * Returns the proxy object. This is a dynamic proxy
200: * object implementing an interface or a CGLIB-enhanced class
201: * @return intercepted object
202: */
203: public Object getProxyObject() {
204: return proxyObj;
205: }
206:
207: /**
208: * Returns the target object of the invocation.
209: * This is the target object being called in response to the
210: * call of the proxy (interface).
211: * @return target object
212: */
213: public Object getTargetObject() {
214: return targetObj;
215: }
216:
217: /**
218: * Returns the target method of the invocation.
219: * This is the target method being called in response to the
220: * call to the proxy's method. For example, "find" method
221: * of the Entity business interface is the intercepted method,
222: * whereas "ejbFind" method of the entity implementation class
223: * is the target method.
224: *
225: * @return method
226: */
227: public Method getTargetMethod() {
228: return targetMethod;
229: }
230:
231: /**
232: * @deprecated Use getProxyObject instead
233: * @return proxy object
234: */
235: public Object getInterceptedObject() {
236: return getProxyObject();
237: }
238:
239: /**
240: * @deprecated Use getProxyMethod instead
241: */
242: public Method getInterceptedMethod() {
243: return getProxyMethod();
244: }
245:
246: /**
247: * Returns the proxy method, the method that was called on the proxy.
248: * For example, "find" method
249: * of the Entity business interface is the proxy method,
250: * and "ejbFind" method of the entity implementation class
251: * is the target method.
252: * @return proxy method
253: */
254: public Method getProxyMethod() {
255: return proxyMethod;
256: }
257:
258: public Object[] getParamVals() {
259: return paramVals;
260: }
261:
262: /**
263: * Returns the return value of the invocation. Normally,
264: * this is a return value of the target method, however interceptors
265: * can change it.
266: * @return Object or null if the method has void type or
267: * if the method threw exception
268: */
269: public Object getReturnObject() {
270: return returnObject;
271: }
272:
273: /**
274: * Sets the return value of the invocation. This allows interceptors
275: * to change the current return value.
276: * @param returnObject return object to set
277: *
278: */
279: public void setReturnObject(final Object returnObject) {
280: this .returnObject = returnObject;
281: }
282:
283: /**
284: * Returns the throwable thrown by the target method or by one of the
285: * interceptors.
286: *
287: * @return throwable or null if no exceptions were thrown during the invocation
288: *
289: */
290: public Object getThrownThrowable() {
291: return thrownThrowable;
292: }
293:
294: /**
295: * Sets the throwable thrown by the invoked method
296: * @param throwable
297: */
298: public void setThrownThrowable(Throwable throwable) {
299: this .thrownThrowable = throwable;
300: }
301:
302: /**
303: * Adds the invocation context property.
304: * Context property is a piece of data made available
305: * to all interceptors. Interceptors can add/modify the context properties during the call.
306: * @param key key for this contextProperties's data
307: * @param data contextProperties data
308: */
309: public void setContext(String key, Object data) {
310: contextProperties.put(key, data);
311: }
312:
313: /**
314: * Returns the custome context's property value associated with the provided key
315: * or throws IllegalStateException if the key is not found
316: * @param key contextProperties key
317: * @return contextProperties data
318: */
319: public Object getPropertyValue(String key) {
320: if (!contextProperties.containsKey(key))
321: throw new IllegalStateException("Key " + key
322: + " is not found in the invocation context");
323:
324: return contextProperties.get(key);
325: }
326:
327: /**
328: * Returns the context property value associated with the provided key
329: * or null if the key is not found
330: * @param key contextProperties key
331: * @return contextProperties data
332: */
333: public Object getOptionalPropertyValue(String key) {
334:
335: return contextProperties.get(key);
336: }
337:
338: /**
339: * Calls the object's method using reflection.
340: * This method takes <code>InvocationTargetException</code> out of the
341: * stack in case of exception. This allows exception handlers not to deal with
342: * reflection-specific exceptions.
343: * @param targetObj target object being called
344: * @param method method being called
345: * @param paramVals parameter values
346: * @return value returned by the given method
347: */
348: protected Object invokeMethod(Object targetObj, Method method,
349: Object[] paramVals) throws Throwable {
350:
351: Object returnObj;
352:
353: if (targetObj == null) {
354: throw new IllegalStateException(
355: "TargetObject is null during an attempt to call "
356: + method
357: + "\nOne of the interceptors should have handled target object invocation.");
358: }
359:
360: try {
361: returnObj = method.invoke(targetObj, paramVals);
362: }
363: // We need to re-throw the cause of the exception,
364: // we don't want to give up the fact that the reflection is used.
365: catch (InvocationTargetException ite) {
366: throw ite.getTargetException();
367: }
368:
369: return returnObj;
370: }
371:
372: public String toString() {
373: // TODO: add concat values
374: return targetMethod.toString();
375: }
376:
377: }
|