package org.mockejb.interceptor;

import java.io.Serializable;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

import net.sf.cglib.reflect.FastClass;
import net.sf.cglib.reflect.FastMethod;

/**
 * Requests the interceptors from the AspectSystem and 
 * initiates the call to the interceptor chain.
 * Allows to setup the custom context (properties) that is passed down to interceptors.  
 * Clients can reuse the same object of this class for all method
 * calls (provided that that the custom context is the same).
 * 
 * @author Alexander Ananiev
 */
public class InterceptorInvoker implements Serializable {
    
    
    private transient AspectSystem aspectSystem = AspectSystemFactory.getAspectSystem();
    
    private Map context = new HashMap();    
    
    /**
     * Calls AspectSystem to find the interceptors for the given invokedMethod and
     * targetMethod, creates the 
     * invocationContext and proceeds to calling the first interceptor.
     * @param proxyObj dynamic proxy or the object enhanced by CGLIB. 
     * @param proxyMethod method invoked by the client on the proxy. Normally, this is an interface method
     * (the declaring class is the interface).
     * @param targetObj object to call, e.g., EJB implementation object
     * @param targetMethod method to call on the target object, e.g., method of the EJB implementation clsess
     * @param paramVals method parameters
     * 
     * @return return value
     */
    public Object invoke( Object proxyObj, Method proxyMethod, 
            Object targetObj, Method targetMethod, Object[] paramVals ) throws Exception {
        
        List interceptorList = 
            aspectSystem.findInterceptors( proxyMethod, targetMethod );

        // add the method calling interceptor
        interceptorList.add( new CglibMethodInvoker( ) );
        
        InvocationContext invocationContext = new InvocationContext(  interceptorList, proxyObj,
                proxyMethod, targetObj, targetMethod, paramVals, context );
        
        invocationContext.proceed();
        
        return invocationContext.getReturnObject();
    }
    
    /**
     * Sets the custom context. 
     * Custome context is the is a piece of data made available 
     * to all interceptors 
     * @param key key for this context's data
     * @param data context data
     */
    public void setContext( String key, Object data ){
        context.put( key, data );                
    }

    /**
     * Returns the context associated with the provided key
     * or null if the key is not found.
     * @param key context key
     * @return context data
     */
    public Object getContext( String key ) {
        return context.get( key );
    }
    
    /**
     * Calls the object's method using Cglib.
     */
    static public class CglibMethodInvoker implements Interceptor {
        
        public void intercept( InvocationContext invocationContext )  throws Exception {
            
          
            try {   // this try is to convert Throwable to Exception        
                Method targetMethod = invocationContext.getTargetMethod();    
                FastClass fastClass = FastClass.create( targetMethod.getDeclaringClass() ); 
                FastMethod fastMethod = fastClass.getMethod( targetMethod );
                
                Object returnObj;
                
                try {
                    returnObj = fastMethod.invoke( invocationContext.getTargetObject(), 
                        invocationContext.getParamVals());
                }
                //We need to re-throw the cause of the exception, 
                //  we don't want to show that the reflection is used.
                catch( InvocationTargetException ite ){
                    throw ite.getTargetException();
                }

                invocationContext.setReturnObject( returnObj );
            }
            // convert throwable to Error or Exception
            // this allows us not to use throwable as part of a method signature
            
            catch( Throwable throwable) {
                if ( throwable instanceof Error ) {
                    throw (Error)throwable;
                }
                else if ( throwable instanceof Exception ){
                    throw (Exception)throwable;
                }
            }
        }
    }   // end of CglibMethodInvoker

    
}