//$Id: BijectionInterceptor.java 9561 2008-11-14 03:26:13Z dan.j.allen $
package org.jboss.seam.core;

import java.util.concurrent.locks.ReentrantLock;

import org.jboss.seam.Component;
import org.jboss.seam.CyclicDependencyException;
import org.jboss.seam.annotations.intercept.AroundInvoke;
import org.jboss.seam.annotations.intercept.AroundTimeout;
import org.jboss.seam.annotations.intercept.Interceptor;
import org.jboss.seam.intercept.AbstractInterceptor;
import org.jboss.seam.intercept.InvocationContext;
import org.jboss.seam.util.Exceptions;

/**
 * Before invoking the component, inject all dependencies. After
 * invoking, outject dependencies back into their context.
 * 
 * @author Gavin King
 * @author Shane Bryzak
 */
@Interceptor
public class BijectionInterceptor extends AbstractInterceptor
{
   private static final long serialVersionUID = 4686458105931528659L;
   
   private boolean injected;
   
   private boolean injecting;
   
   private int clients = 0;
   
   private ReentrantLock lock = new ReentrantLock();
   
   @Override
   public void setComponent(Component component)
   {
      super.setComponent(component);
   }
      
   @AroundInvoke
   @AroundTimeout
   public Object aroundInvoke(InvocationContext invocation) throws Exception
   {
      Component component = getComponent();
      boolean enforceRequired = !component.isLifecycleMethod( invocation.getMethod() );      
      
      try
      {    
         lock.lock();
         try
         {
            if (!injected)
            {              
               if (injecting)
               {
                  throw new CyclicDependencyException();
               }

               injecting = true;
               try
               {
                  component.inject(invocation.getTarget(), enforceRequired);
               }
               finally
               {
                  injecting = false;
               }
               injected = true;
            }
            
            clients++;
         }
         finally
         {
            lock.unlock();
         }
                           
         Object result = invocation.proceed();
            
         lock.lock();
         try
         {
            if (clients == 1)
            {
               try
               {                     
                  component.outject( invocation.getTarget(), enforceRequired );
               }
               finally
               {
                  // Avoid an extra lock by disinjecting here instead of the finally block
                  if (injected)
                  {
                     injected = false;
                     clients--;
                     component.disinject( invocation.getTarget() );
                  }
               }   
            }
         }
         finally
         {
            lock.unlock();
         }
         
         return result;
      }
      catch (Exception e)
      {
         Exception root = e;
         while (Exceptions.getCause(root) != null)
         {
            root = Exceptions.getCause(root);
         }
         if (root instanceof CyclicDependencyException)
         {
            CyclicDependencyException cyclicDependencyException = (CyclicDependencyException) root;
            cyclicDependencyException.addInvocation(getComponent().getName(), invocation.getMethod());
         }
         throw e;
      }
      finally
      {            
         if (injected)
         {
            lock.lock();
            try
            {
               clients--;
               
               if (clients == 0)
               {
                  injected = false;
                  component.disinject( invocation.getTarget() );     
               }
            }
            finally
            {
               lock.unlock();
            }
         }
      }
   }

   public boolean isInterceptorEnabled()
   {
      return getComponent().needsInjection() || getComponent().needsOutjection();
   }
   
}
