| 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();
}
}