package org.jboss.cache.interceptors;

import org.jboss.cache.InvocationContext;
import org.jboss.cache.commands.ReplicableCommand;
import org.jboss.cache.commands.ReversibleCommand;
import org.jboss.cache.commands.VisitableCommand;
import org.jboss.cache.commands.tx.CommitCommand;
import org.jboss.cache.commands.tx.OptimisticPrepareCommand;
import org.jboss.cache.commands.tx.PrepareCommand;
import org.jboss.cache.commands.tx.RollbackCommand;
import org.jboss.cache.commands.write.ClearDataCommand;
import org.jboss.cache.commands.write.MoveCommand;
import org.jboss.cache.commands.write.PutDataMapCommand;
import org.jboss.cache.commands.write.PutForExternalReadCommand;
import org.jboss.cache.commands.write.PutKeyValueCommand;
import org.jboss.cache.commands.write.RemoveKeyCommand;
import org.jboss.cache.commands.write.RemoveNodeCommand;
import org.jboss.cache.interceptors.base.CommandInterceptor;
import org.jboss.cache.transaction.GlobalTransaction;

import javax.transaction.Transaction;

/**
 * Always at the end of the chain, directly in front of the cache. Simply calls into the cache using reflection.
 * If the call resulted in a modification, add the Modification to the end of the modification list
 * keyed by the current transaction.
 * <p/>
 * Although always added to the end of an optimistically locked chain as well, calls should not make it down to
 * this interceptor unless it is a call the OptimisticNodeInterceptor knows nothing about.
 *
 * @author Bela Ban
 * @version $Id: CallInterceptor.java 6120 2008-06-30 11:58:59Z manik.surtani@jboss.com $
 */
public class CallInterceptor extends CommandInterceptor
{
   @Override
   public Object visitPrepareCommand(InvocationContext ctx, PrepareCommand command) throws Throwable
   {
      if (trace) log.trace("Suppressing invocation of method handlePrepareCommand.");
      return null;
   }

   @Override
   public Object visitOptimisticPrepareCommand(InvocationContext ctx, OptimisticPrepareCommand command) throws Throwable
   {
      if (trace) log.trace("Suppressing invocation of method handleOptimisticPrepareCommand.");
      return null;
   }

   @Override
   public Object visitCommitCommand(InvocationContext ctx, CommitCommand command) throws Throwable
   {
      if (trace) log.trace("Suppressing invocation of method handleCommitCommand.");
      return null;
   }

   @Override
   public Object visitRollbackCommand(InvocationContext ctx, RollbackCommand command) throws Throwable
   {
      if (trace) log.trace("Suppressing invocation of method handleRollbackCommand.");
      return null;
   }

   @Override
   public Object handleDefault(InvocationContext ctx, VisitableCommand command) throws Throwable
   {
      if (trace) log.trace("Executing command: " + command + ".");
      return invokeCommand(ctx, command);
   }

   private Object invokeCommand(InvocationContext ctx, ReplicableCommand command)
         throws Throwable
   {
      Object retval;
      try
      {
         retval = command.perform(ctx);
      }
      catch (Throwable t)
      {
         Transaction tx = ctx.getTransaction();
         if (ctx.isValidTransaction())
         {
            tx.setRollbackOnly();
         }
         throw t;
      }
      return retval;
   }

   @Override
   public Object visitPutDataMapCommand(InvocationContext ctx, PutDataMapCommand command) throws Throwable
   {
      return handleAlterCacheMethod(ctx, command);
   }

   @Override
   public Object visitPutKeyValueCommand(InvocationContext ctx, PutKeyValueCommand command) throws Throwable
   {
      return handleAlterCacheMethod(ctx, command);
   }

   @Override
   public Object visitPutForExternalReadCommand(InvocationContext ctx, PutForExternalReadCommand command) throws Throwable
   {
      return handleAlterCacheMethod(ctx, command);
   }

   @Override
   public Object visitRemoveNodeCommand(InvocationContext ctx, RemoveNodeCommand command) throws Throwable
   {
      return handleAlterCacheMethod(ctx, command);
   }

   @Override
   public Object visitClearDataCommand(InvocationContext ctx, ClearDataCommand command) throws Throwable
   {
      return handleAlterCacheMethod(ctx, command);
   }

   @Override
   public Object visitRemoveKeyCommand(InvocationContext ctx, RemoveKeyCommand command) throws Throwable
   {
      return handleAlterCacheMethod(ctx, command);
   }

   @Override
   public Object visitMoveCommand(InvocationContext ctx, MoveCommand command) throws Throwable
   {
      return handleAlterCacheMethod(ctx, command);
   }

   /**
    * only add the modification to the modification list if we are using pessimistic locking.
    * Optimistic locking calls *should* not make it this far down the interceptor chain, but just
    * in case a method has been invoked that the OptimisticNodeInterceptor knows nothing about, it will
    * filter down here.
    */
   private Object handleAlterCacheMethod(InvocationContext ctx, ReversibleCommand command)
         throws Throwable
   {
      Object result = invokeCommand(ctx, command);
      if (ctx.isValidTransaction() && !configuration.isNodeLockingOptimistic())
      {
         GlobalTransaction gtx = ctx.getGlobalTransaction();
         if (gtx == null)
         {
            if (log.isDebugEnabled())
            {
               log.debug("didn't find GlobalTransaction for " + ctx.getTransaction() + "; won't add modification to transaction list");
            }
         }
         else
         {
            ctx.getTransactionEntry().addModification(command);
         }
      }
      return result;
   }
}
