InvocationContext.java |
package org.mockejb.interceptor; import java.util.*; import java.lang.reflect.*; /** * Performs the invocation of interceptors in their order in the * interceptor list. * Each interceptor is called in turn until we get to the target object. * At this point, the target object's method is called using reflection. * Also keeps the invocation's custom context (properties). * To be thread safe, clients should create a new object of this * class for each method call. * * @author Alexander Ananiev */ public class InvocationContext { private transient ListIterator iter; // TODO: context should be Threadlocal?? private Map contextProperties = new HashMap(); private List interceptorList; private Object proxyObj; private Object targetObj; private Method targetMethod; private Method proxyMethod; private Object[] paramVals; private Object returnObject; private Throwable thrownThrowable; /** * Creates a new instance of the InvocationContext. * * @param interceptorList interceptors that will be invoked before the target method * @param proxyObj object that was intercepted, * most likely it is the dynamic proxy object. Can be null. * @param proxyMethod method invoked on the proxy. The declaring class of the method is the * interface's class. * @param targetObj target object being called. * @param targetMethod method being called. * @param paramVals parameter values */ public InvocationContext( List interceptorList, Object proxyObj, Method proxyMethod, Object targetObj, Method targetMethod, Object[] paramVals ){ this( interceptorList, proxyObj, proxyMethod, targetObj, targetMethod, paramVals, null ); } /** * Creates a new instance of the InvocationContext. * * @param interceptorList interceptors that will be invoked before the target method * @param proxyObj object that was intercepted, * most likely it is the dynamic proxy object. Can be null if the object is not known. * @param proxyMethod method invoked on the proxy. The declaring class of the method is the * interface's class. * @param targetObj target object being called. * @param targetMethod method being called. * @param paramVals parameter values * @param contextProperties any additional context info for the interceptors * */ public InvocationContext( List interceptorList, Object proxyObj, Method proxyMethod, Object targetObj, Method targetMethod, Object[] paramVals, Map contextProperties ){ this.proxyObj=proxyObj; this.proxyMethod = proxyMethod; this.targetObj = targetObj; this.targetMethod = targetMethod; this.paramVals = paramVals; setInterceptorList( interceptorList ); if ( contextProperties != null ) this.contextProperties = new HashMap( contextProperties ); } /** * Sets the list of interceptors * @param interceptorList list to set */ public void setInterceptorList( final List interceptorList ){ verifyInterceptors( interceptorList ); this.interceptorList = interceptorList; reset(); } public List getInterceptorList(){ return this.interceptorList; } /** * Returns the iterator currently in use to traverse the * interceptor list. Clients can use the returned iterator * to find out their place in the call chain. * * @return list iterator */ public ListIterator getInterceptorIterator(){ return iter; } /** * Makes sure that interceptors implement the Interceptor interface. * Otherwise, throws IllegalArgumentException. * */ private void verifyInterceptors( List interceptorList ){ if ( interceptorList == null ){ throw new IllegalArgumentException( "Interceptor list can't be null" ); } Iterator i = interceptorList.iterator(); while( i.hasNext() ){ Object interceptor = i.next(); if ( ! ( interceptor instanceof Interceptor ) ){ throw new IllegalArgumentException( "Object "+interceptor+ " in the interceptor list does not implement interceptor interface"); } } } /** * Resets the interceptor iterator. * @deprecated * */ public void reset(){ iter = interceptorList.listIterator(); } /** * Clears the context properties and resets the interceptor iterator. * */ public void clear(){ reset(); contextProperties.clear(); } /** * Calls the next interceptor in the list. If this is the end of the list, * calls the given method of the target object using reflection if the target * object is not null. * "proceed" name is consistent with the "proceed" keyword used by AspectJ for "around" * advices. * Use "getReturnObject" to get the return value for this invocation. */ public void proceed() throws Exception { // Check if we're at the head of the chain if ( ! iter.hasPrevious() ) { // recreate iterator in case if the list changed reset(); } if ( iter.hasNext() ) { Interceptor nextInterceptor = (Interceptor) iter.next(); try { nextInterceptor.intercept( this ); } // Record throwable catch( Throwable throwable) { // store it for the record thrownThrowable = throwable; // Convert into exception if ( throwable instanceof Error ) { throw (Error)throwable; } else if ( throwable instanceof Exception ){ throw (Exception)throwable; } } //in any event we need to restore the iterator to return where we were finally { iter.previous(); } } } /** * Returns the proxy object. This is a dynamic proxy * object implementing an interface or a CGLIB-enhanced class * @return intercepted object */ public Object getProxyObject( ) { return proxyObj; } /** * Returns the target object of the invocation. * This is the target object being called in response to the * call of the proxy (interface). * @return target object */ public Object getTargetObject( ) { return targetObj; } /** * Returns the target method of the invocation. * This is the target method being called in response to the * call to the proxy's method. For example, "find" method * of the Entity business interface is the intercepted method, * whereas "ejbFind" method of the entity implementation class * is the target method. * * @return method */ public Method getTargetMethod( ) { return targetMethod; } /** * @deprecated Use getProxyObject instead * @return proxy object */ public Object getInterceptedObject( ) { return getProxyObject(); } /** * @deprecated Use getProxyMethod instead */ public Method getInterceptedMethod( ) { return getProxyMethod(); } /** * Returns the proxy method, the method that was called on the proxy. * For example, "find" method * of the Entity business interface is the proxy method, * and "ejbFind" method of the entity implementation class * is the target method. * @return proxy method */ public Method getProxyMethod( ) { return proxyMethod; } public Object[] getParamVals(){ return paramVals; } /** * Returns the return value of the invocation. Normally, * this is a return value of the target method, however interceptors * can change it. * @return Object or null if the method has void type or * if the method threw exception */ public Object getReturnObject( ) { return returnObject; } /** * Sets the return value of the invocation. This allows interceptors * to change the current return value. * @param returnObject return object to set * */ public void setReturnObject( final Object returnObject ) { this.returnObject = returnObject; } /** * Returns the throwable thrown by the target method or by one of the * interceptors. * * @return throwable or null if no exceptions were thrown during the invocation * */ public Object getThrownThrowable( ) { return thrownThrowable; } /** * Sets the throwable thrown by the invoked method * @param throwable */ public void setThrownThrowable( Throwable throwable ) { this.thrownThrowable = throwable; } /** * Adds the invocation context property. * Context property is a piece of data made available * to all interceptors. Interceptors can add/modify the context properties during the call. * @param key key for this contextProperties's data * @param data contextProperties data */ public void setContext( String key, Object data ){ contextProperties.put( key, data ); } /** * Returns the custome context's property value associated with the provided key * or throws IllegalStateException if the key is not found * @param key contextProperties key * @return contextProperties data */ public Object getPropertyValue( String key ) { if ( ! contextProperties.containsKey( key ) ) throw new IllegalStateException("Key "+key+" is not found in the invocation context"); return contextProperties.get( key ); } /** * Returns the context property value associated with the provided key * or null if the key is not found * @param key contextProperties key * @return contextProperties data */ public Object getOptionalPropertyValue( String key ) { return contextProperties.get( key ); } /** * Calls the object's method using reflection. * This method takes <code>InvocationTargetException</code> out of the * stack in case of exception. This allows exception handlers not to deal with * reflection-specific exceptions. * @param targetObj target object being called * @param method method being called * @param paramVals parameter values * @return value returned by the given method */ protected Object invokeMethod( Object targetObj, Method method, Object[] paramVals ) throws Throwable { Object returnObj; if ( targetObj == null ){ throw new IllegalStateException("TargetObject is null during an attempt to call "+ method+"\nOne of the interceptors should have handled target object invocation."); } try { returnObj = method.invoke( targetObj, paramVals); } // We need to re-throw the cause of the exception, // we don't want to give up the fact that the reflection is used. catch( InvocationTargetException ite ){ throw ite.getTargetException(); } return returnObj; } public String toString(){ // TODO: add concat values return targetMethod.toString(); } }