package org.jboss.cache.interceptors;

import org.jboss.cache.GlobalTransaction;
import org.jboss.cache.TreeCache;
import org.jgroups.blocks.MethodCall;

import javax.transaction.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Collections;

/**
 * Handles local transaction 2PC
 * @author Bela Ban
 * @version $Id: TransactionInterceptor.java,v 1.4.2.2 2004/09/23 18:42:34 bwang00 Exp $
 */
public class TransactionInterceptor extends Interceptor {
   TransactionManager tx_mgr=null;

   /** List<Transaction> that we have registered for */
   List               transactions=Collections.synchronizedList(new ArrayList());


   public void setCache(TreeCache cache) {
      super.setCache(cache);
      tx_mgr=cache.getTransactionManager();
   }

   public Object invoke(MethodCall m) throws Throwable {
      Transaction tx=null;
      GlobalTransaction gtx=null;

      if(tx_mgr != null && (tx=tx_mgr.getTransaction()) != null) {
         if(!transactions.contains(tx)) {
            transactions.add(tx);
            gtx=cache.getCurrentTransaction(tx);
            if(gtx == null)
               throw new Exception("failed to get global transaction");
            try {
               // tx.registerSynchronization(new SynchronizationHandler(gtx, cache));

               OrderedSynchronizationHandler handler=OrderedSynchronizationHandler.getInstance(tx);
               SynchronizationHandler myHandler=new SynchronizationHandler(gtx, cache);
               handler.registerAtTail(myHandler); // needs to be invoked last on TX commit
            }
            catch(Exception e) {
               log.error("registration for tx=" + tx + " with transaction manager failed, running without TX", e);
            }
         }
         else
            gtx=cache.getGlobalTransactionFromLocalTransaction(tx);
      }

      Object retval=super.invoke(m);

      if(tx != null) {
         if(retval != null && retval instanceof Exception) {
            tx.setRollbackOnly();
            throw (Exception)retval;
         }
         else {
            if(TreeCache.isCrudMethod(m.getMethod())) {
               // if method is a CRUD (Create/Remove/Update/Delete) method: add it to the modification
               // list, otherwise skip (e.g. get() is not added)
               // add the modification to the TX's modification list. this is used to later
               // (on TX commit) send all modifications done in this TX to all members
               cache.addModification(gtx, m);
            }
         }
      }
      return retval;
   }



   class SynchronizationHandler implements Synchronization {
      GlobalTransaction gtx=null;
      TreeCache cache=null;


      SynchronizationHandler(GlobalTransaction gtx, TreeCache cache) {
         this.gtx=gtx;
         this.cache=cache;
      }


      /**
       * This method is invoked before the start of the commit or rollback
       * process. We don't do anything because this method handles only the case of cache_mode
       * being LOCAL
       */
      public void beforeCompletion() {
      }


      /**
       * Depending on the status (OK or FAIL), call commit() or rollback() on the CacheLoader
       *
       * @param status
       */
      public void afterCompletion(int status) {
         switch(status) {
            case Status.STATUS_COMMITTED:
               cache.commit(gtx);
               break;

            case Status.STATUS_MARKED_ROLLBACK: // this one is probably not needed
            case Status.STATUS_ROLLEDBACK:
               if(log.isDebugEnabled())
                  log.debug("afterCompletion(): rolling back transaction");
               cache.rollback(gtx); // roll back locally
               break;
            default:
               cache.rollback(gtx); // roll back locally
               throw new IllegalStateException("Illegal status: " + status);
         }
      }
   }

}
