//$Id: Interceptor.java 10311 2009-04-06 16:14:09Z pete.muir@jboss.org $
package org.jboss.seam.intercept;

import static org.jboss.seam.util.EJB.AROUND_INVOKE;
import static org.jboss.seam.util.EJB.AROUND_TIMEOUT;
import static org.jboss.seam.util.EJB.POST_ACTIVATE;
import static org.jboss.seam.util.EJB.POST_CONSTRUCT;
import static org.jboss.seam.util.EJB.PRE_DESTROY;
import static org.jboss.seam.util.EJB.PRE_PASSIVATE;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

import org.jboss.seam.Component;
import org.jboss.seam.annotations.intercept.AroundInvoke;
import org.jboss.seam.annotations.intercept.AroundTimeout;
import org.jboss.seam.annotations.intercept.InterceptorType;
import org.jboss.seam.annotations.intercept.PostActivate;
import org.jboss.seam.annotations.intercept.PostConstruct;
import org.jboss.seam.annotations.intercept.PrePassivate;
import org.jboss.seam.util.Reflections;

/**
 * Wraps and delegates to a Seam interceptor.
 * 
 * @author Gavin King
 */
public final class Interceptor extends Reflections
{
   private Class<?> userInterceptorClass;
   private Object statelessUserInterceptorInstance;
   private Method aroundInvokeMethod;
   private Method aroundTimeoutMethod;
   private Method postConstructMethod;
   private Method preDestroyMethod;
   private Method postActivateMethod;
   private Method prePassivateMethod;
   private Method componentInjectorMethod;
   private Method annotationInjectorMethod;
   private Method interceptorEnabledMethod;
   private InterceptorType type;
   private Annotation annotation;
   private Component component;
   private boolean optimized;

   private boolean isStateless()
   {
      return userInterceptorClass.isAnnotationPresent(org.jboss.seam.annotations.intercept.Interceptor.class) &&
            userInterceptorClass.getAnnotation(org.jboss.seam.annotations.intercept.Interceptor.class).stateless();
   }
   
   public Object createUserInterceptor()
   {
      if ( isStateless() )
      {
         return statelessUserInterceptorInstance;
      }
      else
      {
         try
         {
            Object userInterceptor = userInterceptorClass.newInstance();
            if (componentInjectorMethod!=null)
            {
               Reflections.invokeAndWrap(componentInjectorMethod, userInterceptor, component);
            }
            if (annotationInjectorMethod!=null) 
            {
               Reflections.invokeAndWrap(annotationInjectorMethod, userInterceptor, annotation);
            }
            return userInterceptor;
         }
         catch (Exception e)
         {
            throw new RuntimeException(e);
         }
      }
   }

   public Class getUserInterceptorClass()
   {
      return userInterceptorClass;
   }
   
   public InterceptorType getType()
   {
      return type;
   }
   
   @Override
   public String toString()
   {
      return "Interceptor(" + userInterceptorClass.getName() + ")";
   }
   
   public Interceptor(Object interceptor, Component component)
   {
      userInterceptorClass = interceptor.getClass();
      statelessUserInterceptorInstance = interceptor;
      this.component = component;
      init();
   }
   
   public Interceptor(Class[] classes, Annotation annotation, Component component) 
   {
      if (classes.length!=1)
      {
         //TODO: remove this silly restriction!
         throw new IllegalArgumentException("Must be exactly one interceptor when used as a meta-annotation");
      }
      userInterceptorClass = classes[0];
      
      try
      {
         statelessUserInterceptorInstance = userInterceptorClass.newInstance();
      }
      catch (Exception e)
      {
         throw new IllegalArgumentException("could not instantiate interceptor", e);
      }
      
      this.annotation = annotation;
      this.component = component;
      
      init();
   }
   

   private void init()
   {
      for ( Method method : userInterceptorClass.getMethods() )
      {
         if ( !method.isAccessible() ) method.setAccessible(true);
         if ( method.isAnnotationPresent(AROUND_INVOKE) || method.isAnnotationPresent(AroundInvoke.class) )
         {
            aroundInvokeMethod = method;
         }
         if ( method.isAnnotationPresent(AROUND_TIMEOUT) || method.isAnnotationPresent(AroundTimeout.class) )
         {
            aroundTimeoutMethod = method;
         }         
         if ( method.isAnnotationPresent(POST_CONSTRUCT) || method.isAnnotationPresent(PostConstruct.class))
         {
            postConstructMethod = method;
         }
         if ( method.isAnnotationPresent(PRE_DESTROY) )
         {
            preDestroyMethod = method;
         }
         if ( method.isAnnotationPresent(PRE_PASSIVATE) || method.isAnnotationPresent(PrePassivate.class) )
         {
            prePassivateMethod = method;
         }
         if ( method.isAnnotationPresent(POST_ACTIVATE) || method.isAnnotationPresent(PostActivate.class) )
         {
            postActivateMethod = method;
         }

         Class[] params = method.getParameterTypes();
         //if there is a method that takes the annotation, call it, to pass initialization info
         if ( annotation!=null && params.length==1 && params[0]==annotation.annotationType() )
         {
            annotationInjectorMethod = method;
            Reflections.invokeAndWrap(method, statelessUserInterceptorInstance, annotation);
         }
         //if there is a method that takes the component, call it
         if ( params.length==1 && params[0]==Component.class )
         {
            componentInjectorMethod = method;
            Reflections.invokeAndWrap(method, statelessUserInterceptorInstance, component);
         }
         // if there is an interceptor enabled method, store it
         if ( "isInterceptorEnabled".equals(method.getName()) && method.getReturnType().equals(boolean.class) )
         {
            interceptorEnabledMethod = method;
         }
         
      }

      type = userInterceptorClass.isAnnotationPresent(org.jboss.seam.annotations.intercept.Interceptor.class) ?
            userInterceptorClass.getAnnotation(org.jboss.seam.annotations.intercept.Interceptor.class).type() :
            InterceptorType.SERVER;
            
      optimized = OptimizedInterceptor.class.isAssignableFrom(userInterceptorClass);
   }
   
   public boolean isOptimized()
   {
      return optimized;
   }
   
   public Object aroundInvoke(InvocationContext invocation, Object userInterceptor) throws Exception
   {
      return aroundInvokeMethod==null ?
            invocation.proceed() :
            Reflections.invoke( aroundInvokeMethod, userInterceptor, invocation );
   }
   
   public Object aroundTimeout(InvocationContext invocation, Object userInterceptor) throws Exception
   {
      return aroundTimeoutMethod==null ?
            invocation.proceed() :
            Reflections.invoke( aroundTimeoutMethod, userInterceptor, invocation );
   }
   
   public Object postConstruct(InvocationContext invocation, Object userInterceptor) throws Exception
   {
      return postConstructMethod==null ?
            invocation.proceed() :
            Reflections.invoke( postConstructMethod, userInterceptor, invocation );
   }
   
   public Object preDestroy(InvocationContext invocation, Object userInterceptor) throws Exception
   {
      return preDestroyMethod==null ?
            invocation.proceed() :
            Reflections.invoke( preDestroyMethod, userInterceptor, invocation );
   }
   
   public Object prePassivate(InvocationContext invocation, Object userInterceptor) throws Exception
   {
      return prePassivateMethod==null ?
            invocation.proceed() :
            Reflections.invoke( prePassivateMethod, userInterceptor, invocation );
   }
   
   public Object postActivate(InvocationContext invocation, Object userInterceptor) throws Exception
   {
      return postActivateMethod==null ?
            invocation.proceed() :
            Reflections.invoke( postActivateMethod, userInterceptor, invocation );
   }
   
   /**
    * Return true if the interceptor should be enabled for the component instance
    * 
    * Should only be called during deployment
    */
   public boolean isInterceptorEnabled()
   {
      if (interceptorEnabledMethod != null)
      {
         // Set up component metadata
         if (componentInjectorMethod!=null)
         {
            Reflections.invokeAndWrap(componentInjectorMethod, statelessUserInterceptorInstance, component);
         }
         if (annotationInjectorMethod!=null) 
         {
            Reflections.invokeAndWrap(annotationInjectorMethod, statelessUserInterceptorInstance, annotation);
         }
         if (isOptimized())
         {
            return ( (OptimizedInterceptor) statelessUserInterceptorInstance ).isInterceptorEnabled(); 
         }
         else
         {
            return ((Boolean) Reflections.invokeAndWrap(interceptorEnabledMethod, statelessUserInterceptorInstance));
         }
      }
      else
      {
         return true;
      }
   }
   
}
