Package org.jboss.cache.interceptors

Source Code of org.jboss.cache.interceptors.TxInterceptor$LocalSynchronizationHandler

/*
* JBoss, Home of Professional Open Source
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package org.jboss.cache.interceptors;

import org.jboss.cache.CacheException;
import org.jboss.cache.Fqn;
import org.jboss.cache.InvocationContext;
import org.jboss.cache.Node;
import org.jboss.cache.ReplicationException;
import org.jboss.cache.config.Configuration;
import org.jboss.cache.config.Option;
import org.jboss.cache.lock.NodeLock;
import org.jboss.cache.marshall.MethodCall;
import org.jboss.cache.marshall.MethodCallFactory;
import org.jboss.cache.marshall.MethodDeclarations;
import org.jboss.cache.optimistic.DataVersion;
import org.jboss.cache.transaction.GlobalTransaction;
import org.jboss.cache.transaction.OptimisticTransactionEntry;
import org.jboss.cache.transaction.TransactionEntry;
import org.jgroups.Address;

import javax.transaction.Status;
import javax.transaction.Synchronization;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
* This interceptor is the new default at the head of all interceptor chains,
* and makes transactional attributes available to all interceptors in the chain.
* This interceptor is also responsible for registering for synchronisation on
* transaction completion.
*
* @author <a href="mailto:manik@jboss.org">Manik Surtani (manik@jboss.org)</a>
* @author <a href="mailto:stevew@jofti.com">Steve Woodcock (stevew@jofti.com)</a>
*/
public class TxInterceptor extends BaseTransactionalContextInterceptor implements TxInterceptorMBean
{
   private final static Object NULL = new Object();

   /**
    * List <Transaction>that we have registered for
    */
   private Map transactions = new ConcurrentHashMap(16);
   private Map rollbackTransactions = new ConcurrentHashMap(16);
   private long m_prepares = 0;
   private long m_commits = 0;

   private long m_rollbacks = 0;

   public TxInterceptor()
   {
      initLogger();
   }

   @SuppressWarnings("unchecked")
   protected Object handleOptimisticPrepareMethod(InvocationContext ctx, GlobalTransaction gtx, List modifications, Map data, Address address, boolean onePhaseCommit) throws Throwable
   {
      Object result = null;
      boolean scrubTxsOnExit = false;

      // this is a prepare, commit, or rollback.
      if (log.isDebugEnabled()) log.debug("Got gtx from invocation context " + ctx.getGlobalTransaction());
      try
      {
         if (ctx.getGlobalTransaction().isRemote())
         {
            result = handleRemotePrepare(ctx, modifications, onePhaseCommit);
            scrubTxsOnExit = true;
            if (configuration.getExposeManagementStatistics() && getStatisticsEnabled())
            {
               m_prepares++;
            }
         }
         else
         {
            if (trace) log.trace("received my own message (discarding it)");
            result = null;
         }
      }
      catch (Throwable e)
      {
         throwIfNeeded(ctx, e);
      }
      finally
      {
         scrubOnExist(ctx, scrubTxsOnExit);
      }
      return result;
   }

   protected Object handlePrepareMethod(InvocationContext ctx, GlobalTransaction gtx, List modification, Address coordinator, boolean onePhaseCommit) throws Throwable
   {
      return handleOptimisticPrepareMethod(ctx, gtx, modification, null, coordinator, onePhaseCommit);
   }

   @SuppressWarnings("Unchecked")
   protected Object handleCommitMethod(InvocationContext ctx, GlobalTransaction globalTransaction) throws Throwable
   {
      Object result = null;
      boolean scrubTxsOnExit = false;
      try
      {
         if (trace)
         {
            log.trace("(" + cache.getLocalAddress() + ") call on method [" + ctx.getMethodCall() + "]");
         }
         if (ctx.getGlobalTransaction().isRemote())
         {
            result = handleRemoteCommitRollback(ctx);
            scrubTxsOnExit = true;
         }
         else
         {
            if (trace) log.trace("received my own message (discarding it)");
            result = null;
         }
      }
      catch (Throwable throwable)
      {
         throwIfNeeded(ctx, throwable);
      }
      finally
      {
         scrubOnExist(ctx, scrubTxsOnExit);
      }
      return result;
   }


   protected Object handleRollbackMethod(InvocationContext ctx, GlobalTransaction globalTransaction) throws Throwable
   {
      return handleCommitMethod(ctx, globalTransaction);
   }

   protected Object handleLockMethod(InvocationContext ctx, Fqn fqn, NodeLock.LockType lockType, boolean recursive) throws Throwable
   {
      return handleNonTxMethod(ctx);
   }

   protected Object handleRemoveDataVersionedMethod(InvocationContext ctx, GlobalTransaction gtx, Fqn fqn, boolean createUndoOps, DataVersion dv) throws Throwable
   {
      return handleNonTxMethod(ctx);
   }

   protected Object handleRemoveKeyVersionedMethod(InvocationContext ctx, GlobalTransaction gtx, Fqn fqn, Object key, boolean createUndoOps, DataVersion dv) throws Throwable
   {
      return handleNonTxMethod(ctx);
   }

   protected Object handleRemoveNodeVersionedMethod(InvocationContext ctx, GlobalTransaction gtx, Fqn fqn, boolean createUndoOps, DataVersion dv) throws Throwable
   {
      return handleNonTxMethod(ctx);
   }

   protected Object handleDataGravitationCleanupMethod(InvocationContext ctx, Fqn primary, Fqn backup) throws Throwable
   {
      return handleNonTxMethod(ctx);
   }

   protected Object handlePutForExternalReadVersionedMethod(InvocationContext ctx, GlobalTransaction gtx, Fqn fqn, Object key, Object value, DataVersion dv) throws Throwable
   {
      return handleNonTxMethod(ctx);
   }

   protected Object handlePutKeyValueVersionedMethod(InvocationContext ctx, GlobalTransaction gtx, Fqn fqn, Object key, Object value, boolean createUndoOps, DataVersion dv) throws Throwable
   {
      return handleNonTxMethod(ctx);
   }

   protected Object handlePutDataVersionedMethod(InvocationContext ctx, GlobalTransaction globalTransaction, Fqn fqn, Map map, Boolean createUndoOps, DataVersion dataVersion) throws Throwable
   {
      return handleNonTxMethod(ctx);
   }

   protected Object handlePutDataEraseVersionedMethod(InvocationContext ctx, GlobalTransaction gtx, Fqn fqn, Map data, boolean createUndoOps, boolean eraseContent, DataVersion dv) throws Throwable
   {
      return handleNonTxMethod(ctx);
   }

   protected Object handleExistsMethod(InvocationContext ctx, Fqn fqn) throws Throwable
   {
      return handleNonTxMethod(ctx);
   }

   protected Object handleEvictVersionedNodeMethod(InvocationContext ctx, Fqn fqn, DataVersion dataVersion) throws Throwable
   {
      return handleNonTxMethod(ctx);
   }

   protected Object handleEvictMethod(InvocationContext ctx, Fqn fqn) throws Throwable
   {
      return handleNonTxMethod(ctx);
   }

   protected Object handleRemoveDataMethod(InvocationContext ctx, GlobalTransaction tx, Fqn fqn, boolean createUndoOps) throws Throwable
   {
      return handleNonTxMethod(ctx);
   }

   protected Object handleRemoveKeyMethod(InvocationContext ctx, GlobalTransaction tx, Fqn fqn, Object key, boolean createUndoOps) throws Throwable
   {
      return handleNonTxMethod(ctx);
   }

   protected Object handleRemoveNodeMethod(InvocationContext ctx, GlobalTransaction tx, Fqn fqn, boolean createUndoOps) throws Throwable
   {
      return handleNonTxMethod(ctx);
   }

   protected Object handleGetDataMapMethod(InvocationContext ctx, Fqn fqn) throws Throwable
   {
      return handleNonTxMethod(ctx);
   }

   protected Object handleGetKeysMethod(InvocationContext ctx, Fqn fqn) throws Throwable
   {
      return handleNonTxMethod(ctx);
   }

   protected Object handlePrintMethod(InvocationContext ctx, Fqn fqn) throws Throwable
   {
      return handleNonTxMethod(ctx);
   }

   protected Object handleReleaseAllLocksMethod(InvocationContext ctx, Fqn fqn) throws Throwable
   {
      return handleNonTxMethod(ctx);
   }

   protected Object handleGetChildrenNamesMethod(InvocationContext ctx, Fqn fqn) throws Throwable
   {
      return handleNonTxMethod(ctx);
   }

   protected Object handleGetNodeMethod(InvocationContext ctx, Fqn fqn) throws Throwable
   {
      return handleNonTxMethod(ctx);
   }

   protected Object handleGetKeyValueMethod(InvocationContext ctx, Fqn fqn, Object key, boolean sendNodeEvent) throws Throwable
   {
      return handleNonTxMethod(ctx);
   }

   protected Object handleAddChildMethod(InvocationContext ctx, GlobalTransaction tx, Fqn parentFqn, Object childName, Node cn, boolean createUndoOps) throws Throwable
   {
      return handleNonTxMethod(ctx);
   }

   protected Object handleMoveMethod(InvocationContext ctx, Fqn from, Fqn to) throws Throwable
   {
      return handleNonTxMethod(ctx);
   }

   protected Object handlePutKeyValueMethod(InvocationContext ctx, GlobalTransaction gtx, Fqn fqn, Object key, Object value, boolean createUndoOps) throws Throwable
   {
      return handleNonTxMethod(ctx);
   }

   protected Object handlePutForExternalReadMethod(InvocationContext ctx, GlobalTransaction tx, Fqn fqn, Object key, Object value) throws Throwable
   {
      return handleNonTxMethod(ctx);
   }

   protected Object handlePutDataMethod(InvocationContext ctx, GlobalTransaction tx, Fqn fqn, Map data, boolean createUndoOps) throws Throwable
   {
      return handleNonTxMethod(ctx);
   }

   protected Object handlePutDataEraseMethod(InvocationContext ctx, GlobalTransaction gt, Fqn fqn, Map newData, boolean createUndoOps, boolean eraseContents) throws Throwable
   {
      return handleNonTxMethod(ctx);
   }

   private boolean throwIfNeeded(InvocationContext ctx, Throwable e) throws Throwable
   {
      Option optionOverride = ctx.getOptionOverrides();
      boolean shouldRethtrow = optionOverride == null || !optionOverride.isFailSilently();
      if (!shouldRethtrow)
      {
         if (trace)
            log.trace("There was a problem handling this request, but failSilently was set, so suppressing exception", e);
      }
      throw e;
   }

   /**
    * we should scrub txs after every call to prevent race conditions
    * basically any other call coming in on the same thread and hijacking any running tx's
    * was highlighted in JBCACHE-606
    */
   private void scrubOnExist(InvocationContext ctx, boolean scrubTxsOnExit)
   {
      if (scrubTxsOnExit)
      {
         setTransactionalContext(null, null, ctx);
      }
   }

   public long getPrepares()
   {
      return m_prepares;
   }

   public long getCommits()
   {
      return m_commits;
   }

   public long getRollbacks()
   {
      return m_rollbacks;
   }

   public void resetStatistics()
   {
      m_prepares = 0;
      m_commits = 0;
      m_rollbacks = 0;
   }

   public Map<String, Object> dumpStatistics()
   {
      Map<String, Object> retval = new HashMap<String, Object>(3);
      retval.put("Prepares", m_prepares);
      retval.put("Commits", m_commits);
      retval.put("Rollbacks", m_rollbacks);
      return retval;
   }

   // --------------------------------------------------------------

   private Object handleRemotePrepare(InvocationContext ctx, List<MethodCall> modifications, boolean onePhase) throws Throwable
   {
      GlobalTransaction gtx = ctx.getGlobalTransaction();
      // Is there a local transaction associated with GTX ?
      Transaction ltx = txTable.getLocalTransaction(gtx);

      Transaction currentTx = txManager.getTransaction();
      Object retval = null;

      try
      {
         if (ltx == null)
         {
            if (currentTx != null) txManager.suspend();
            ltx = createLocalTxForGlobalTx(gtx, ctx);// creates new LTX and associates it with a GTX
            if (log.isDebugEnabled())
            {
               log.debug("Started new local TX as result of remote PREPARE: local TX=" + ltx + " (Status=" + ltx.getStatus() + "), global TX=" + gtx);
            }
         }
         else
         {
            //this should be valid
            if (!isValid(ltx)) throw new CacheException("Transaction " + ltx + " not in correct state to be prepared");

            //associate this thread with this ltx if this ltx is NOT the current tx.
            if (currentTx == null || !ltx.equals(currentTx))
            {
               txManager.suspend();
               txManager.resume(ltx);
            }
         }


         if (trace)
         {
            log.trace("Resuming existing transaction " + ltx + ", global TX=" + gtx);
         }

         // at this point we have a non-null ltx

         // Asssociate the local TX with the global TX. Create new
         // entry for TX in txTable, the modifications
         // below will need this entry to add their modifications
         // under the GlobalTx key
         TransactionEntry entry;
         if (txTable.get(gtx) == null)
         {
            // create a new transaction entry

            entry = configuration.isNodeLockingOptimistic() ? new OptimisticTransactionEntry(ltx) : new TransactionEntry(ltx);
            log.debug("creating new tx entry");
            txTable.put(gtx, entry);
            if (trace) log.trace("TxTable contents: " + txTable);
         }
         else
         {
            entry = txTable.get(gtx);
         }

         setTransactionalContext(ltx, gtx, ctx);
         // register a sync handler for this tx.
         registerHandler(ltx, new RemoteSynchronizationHandler(gtx, ltx), ctx, entry);

         if (configuration.isNodeLockingOptimistic())
         {
            retval = handleOptimisticPrepare(ctx, gtx, modifications, onePhase, ltx);
         }
         else
         {
            retval = handlePessimisticPrepare(ctx, ctx.getMethodCall(), gtx, modifications, onePhase, ltx);
         }
      }
      finally
      {
         txManager.suspend();// suspends ltx - could be null
         // resume whatever else we had going.
         if (currentTx != null) txManager.resume(currentTx);
         if (log.isDebugEnabled()) log.debug("Finished remote prepare " + gtx);
      }

      return retval;
   }
   //   handler methods.
   // --------------------------------------------------------------

   /**
    * Tests if we already have a tx running.  If so, register a sync handler for this method invocation.
    * if not, create a local tx if we're using opt locking.
    *
    * @return
    * @throws Throwable
    */
   private Object handleNonTxMethod(InvocationContext ctx) throws Throwable
   {
      Object result;
      try
      {
         MethodCall m = ctx.getMethodCall();
         Transaction tx = ctx.getTransaction();
         // if there is no current tx and we're using opt locking, we need to use an implicit tx.
         boolean implicitTransaction = configuration.isNodeLockingOptimistic() && tx == null;
         if (implicitTransaction)
         {
            tx = createLocalTx();
            // we need to attach this tx to the InvocationContext.
            ctx.setTransaction(tx);
         }
         if (tx != null)
            attachGlobalTransaction(ctx, tx, m);

         GlobalTransaction gtx = ctx.getGlobalTransaction();

         try
         {
            result = nextInterceptor(ctx);
            if (implicitTransaction)
            {
               copyInvocationScopeOptionsToTxScope(ctx);
               copyForcedCacheModeToTxScope(ctx);
               txManager.commit();
            }
         }
         catch (Throwable t)
         {
            if (implicitTransaction)
            {
               log.warn("Rolling back, exception encountered", t);
               result = t;
               try
               {
                  setTransactionalContext(tx, gtx, ctx);
                  txManager.rollback();
               }
               catch (Throwable th)
               {
                  log.warn("Roll back failed encountered", th);
               }
            }
            else
            {
               throw t;
            }
         }
         return result;
      }
      catch (Throwable throwable)
      {
         throwIfNeeded(ctx, throwable);
         return null;
      }
   }

   /**
    * @param ctx
    */
   private void copyForcedCacheModeToTxScope(InvocationContext ctx)
   {
      Option optionOverride = ctx.getOptionOverrides();
      if (optionOverride != null
            && (optionOverride.isForceAsynchronous() || optionOverride.isForceSynchronous()))
      {
         TransactionEntry entry = txTable.get(ctx.getGlobalTransaction());
         if (entry != null)
         {
            if (optionOverride.isForceAsynchronous())
               entry.setForceAsyncReplication(true);
            else
               entry.setForceSyncReplication(true);
         }
      }
   }

   private MethodCall attachGlobalTransaction(InvocationContext ctx, Transaction tx, MethodCall m) throws Exception
   {
      if (trace)
      {
         log.trace(" local transaction exists - registering global tx if not present for " + Thread.currentThread());
      }
      if (trace)
      {
         GlobalTransaction tempGtx = txTable.get(tx);
         log.trace("Associated gtx in txTable is " + tempGtx);
      }

      // register a sync handler for this tx - only if the gtx is not remotely initiated.
      GlobalTransaction gtx = registerTransaction(tx, ctx);
      if (gtx != null)
      {
         m = replaceGtx(m, gtx);
      }
      else
      {
         // get the current gtx from the txTable.
         gtx = txTable.get(tx);
      }

      // make sure we attach this gtx to the invocation context.
      ctx.setGlobalTransaction(gtx);

      return m;
   }

   /**
    * This is called by invoke() if we are in a remote gtx's prepare() phase.
    * Finds the appropriate tx, suspends any existing txs, registers a sync handler
    * and passes up the chain.
    * <p/>
    * Resumes any existing txs before returning.
    *
    * @return
    * @throws Throwable
    */
   private Object handleOptimisticPrepare(InvocationContext ctx, GlobalTransaction gtx, List<MethodCall> modifications, boolean onePhase, Transaction ltx) throws Throwable
   {
      Object retval;
      if (log.isDebugEnabled()) log.debug("Handling optimistic remote prepare " + gtx);
      replayModifications(modifications, ctx, true);
      retval = nextInterceptor(ctx);
      // JBCACHE-361 Confirm that the transaction is ACTIVE
      if (!isActive(ltx))
      {
         throw new ReplicationException("prepare() failed -- " +
               "local transaction status is not STATUS_ACTIVE;" +
               " is " + ltx.getStatus());
      }
      return retval;
   }

   private Object handlePessimisticPrepare(InvocationContext ctx, MethodCall m, GlobalTransaction gtx, List<MethodCall> modifications, boolean commit, Transaction ltx) throws Exception
   {
      boolean success = true;
      Object retval;
      try
      {
         // now pass up the prepare method itself.
         try
         {
            replayModifications(modifications, ctx, false);
            if (m.isOnePhaseCommitPrepareMehod())
            {
               if (trace)
                  log.trace("Using one-phase prepare.  Not propagating the prepare call up the stack until called to do so by the sync handler.");
            }
            else
            {
               nextInterceptor(ctx);
            }

            // JBCACHE-361 Confirm that the transaction is ACTIVE
            if (!isActive(ltx))
            {
               throw new ReplicationException("prepare() failed -- " +
                     "local transaction status is not STATUS_ACTIVE;" +
                     " is " + ltx.getStatus());
            }
         }
         catch (Throwable th)
         {
            log.error("prepare method invocation failed", th);
            retval = th;
            success = false;
            if (retval instanceof Exception)
            {
               throw (Exception) retval;
            }
         }
      }
      finally
      {

         if (trace)
         {
            log.trace("Are we running a 1-phase commit? " + commit);
         }
         // 4. If commit == true (one-phase-commit): commit (or rollback) the TX; this will cause
         //    {before/after}Completion() to be called in all registered interceptors: the TransactionInterceptor
         //    will then commit/rollback against the cache

         if (commit)
         {
            try
            {
               //                    invokeOnePhaseCommitMethod(gtx, modifications.size() > 0, success);
               if (success)
               {
                  ltx.commit();
               }
               else
               {
                  ltx.rollback();
               }
            }
            catch (Throwable t)
            {
               log.error("Commit/rollback failed.", t);
               if (success)
               {
                  // try another rollback...
                  try
                  {
                     log.info("Attempting anotehr rollback");
                     //invokeOnePhaseCommitMethod(gtx, modifications.size() > 0, false);
                     ltx.rollback();
                  }
                  catch (Throwable t2)
                  {
                     log.error("Unable to rollback", t2);
                  }
               }
            }
            finally
            {
               transactions.remove(ltx);// JBAS-298
            }
         }
      }
      return null;
   }

   private Object replayModifications(List<MethodCall> modifications, InvocationContext ctx, boolean injectDataVersions)
   {
      Object retval = null;
      MethodCall originalMethodCall = ctx.getMethodCall();
      Option originalOption = ctx.getOptionOverrides();

      if (modifications != null)
      {
         for (MethodCall modification : modifications)
         {
            try
            {
               if (injectDataVersions && !MethodDeclarations.isDataGravitationMethod(modification.getMethodId()))
               {
                  Object[] origArgs = modification.getArgs();
                  // there may be instances (e.g., data gravitation calls) where a data version is not passed in or not even relevant.
                  // make sure we are aware of this.
                  Option o = null;
                  if (origArgs[origArgs.length - 1] instanceof DataVersion)
                  {
                     o = new Option();
                     o.setDataVersion((DataVersion) origArgs[origArgs.length - 1]);
                  }
                  // modify the call to the non-dataversioned counterpart since we've popped out the data version
                  Object[] args = new Object[origArgs.length - 1];
                  System.arraycopy(origArgs, 0, args, 0, args.length);

                  ctx.setMethodCall(MethodCallFactory.create(MethodDeclarations.getUnversionedMethodId(modification.getMethodId()), args));
                  if (o != null) ctx.setOptionOverrides(o);
               }
               else
               {
                  ctx.setMethodCall(modification);
               }

               retval = nextInterceptor(ctx);

               if (!isActive(ctx.getTransaction()))
               {
                  throw new ReplicationException("prepare() failed -- " + "local transaction status is not STATUS_ACTIVE; is " + ctx.getTransaction().getStatus());
               }
            }
            catch (Throwable t)
            {
               log.error("method invocation failed", t);
               retval = t;
            }
            finally
            {
               // reset any options
               if (injectDataVersions) ctx.setOptionOverrides(originalOption);
               ctx.setMethodCall(originalMethodCall);
            }
            if (retval != null && retval instanceof Exception)
            {
               if (retval instanceof RuntimeException)
                  throw (RuntimeException) retval;
               else
                  throw new RuntimeException((Exception) retval);
            }
         }
      }
      // need to pass up the prepare as well and return value from that
      return retval;
   }

   /**
    * Handles a commit or a rollback for a remote gtx.  Called by invoke().
    *
    * @return
    * @throws Throwable
    */
   private Object handleRemoteCommitRollback(InvocationContext ctx) throws Throwable
   {
      Transaction ltx;
      GlobalTransaction gtx = ctx.getGlobalTransaction();
      MethodCall m = ctx.getMethodCall();

      try
      {
         ltx = getLocalTxForGlobalTx(gtx);
      }
      catch (IllegalStateException e)
      {
         if (m.getMethodId() == MethodDeclarations.rollbackMethod_id)
         {
            log.warn("No local transaction for this remotely originating rollback.  Possibly rolling back before a prepare call was broadcast?");
            return null;
         }
         else
         {
            throw e;
         }
      }

      // disconnect if we have a current tx associated
      Transaction currentTx = txManager.getTransaction();
      boolean resumeCurrentTxOnCompletion = false;
      try
      {
         if (!ltx.equals(currentTx))
         {
            currentTx = txManager.suspend();
            resumeCurrentTxOnCompletion = true;
            txManager.resume(ltx);
            // make sure we set this in the ctx
            ctx.setTransaction(ltx);
         }
         if (log.isDebugEnabled()) log.debug(" executing " + m + "() with local TX " + ltx + " under global tx " + gtx);

         // pass commit up the chain
         // nextInterceptor(ctx);
         // commit or rollback the tx.
         if (m.getMethodId() == MethodDeclarations.commitMethod_id)
         {
            txManager.commit();
            if (configuration.getExposeManagementStatistics() && getStatisticsEnabled())
            {
               m_commits++;
            }
         }
         else
         {
            txManager.rollback();
            if (configuration.getExposeManagementStatistics() && getStatisticsEnabled())
            {
               m_rollbacks++;
            }
         }
      }
      finally
      {
         //resume the old transaction if we suspended it
         if (resumeCurrentTxOnCompletion)
         {
            if (trace) log.trace("Resuming suspended transaction " + currentTx);
            txManager.suspend();
            if (currentTx != null)
            {
               txManager.resume(currentTx);
               ctx.setTransaction(currentTx);
            }
         }

         // remove from local lists.
         transactions.remove(ltx);

         // this tx has completed.  Clean up in the tx table.
         txTable.remove(gtx);
         txTable.remove(ltx);
      }

      if (log.isDebugEnabled()) log.debug("Finished remote commit/rollback method for " + gtx);

      return null;
   }

   private Transaction getLocalTxForGlobalTx(GlobalTransaction gtx) throws IllegalStateException
   {
      Transaction ltx = txTable.getLocalTransaction(gtx);
      if (ltx != null)
      {
         if (log.isDebugEnabled()) log.debug("Found local TX=" + ltx + ", global TX=" + gtx);
      }
      else
      {
         throw new IllegalStateException(" found no local TX for global TX " + gtx);
      }
      return ltx;
   }

   /**
    * Handles a commit or a rollback.  Called by the synch handler.  Simply tests that we are in the correct tx and
    * passes the meth call up the interceptor chain.
    *
    * @return
    * @throws Throwable
    */
   private Object handleCommitRollback(InvocationContext ctx, MethodCall call) throws Throwable
   {
      //GlobalTransaction gtx = findGlobalTransaction(m.getArgs());
      GlobalTransaction gtx = ctx.getGlobalTransaction();
      Object result;

      // this must have a local transaction associated if a prepare has been
      // callled before
      //Transaction ltx = getLocalTxForGlobalTx(gtx);

      //        Transaction currentTx = txManager.getTransaction();

      //if (!ltx.equals(currentTx)) throw new IllegalStateException(" local transaction " + ltx + " transaction does not match running tx " + currentTx);

      MethodCall originalCall = ctx.getMethodCall();
      ctx.setMethodCall(call);
      try
      {
         result = nextInterceptor(ctx);
      }
      finally
      {
         ctx.setMethodCall(originalCall);
      }

      if (log.isDebugEnabled()) log.debug("Finished local commit/rollback method for " + gtx);
      return result;
   }

   // --------------------------------------------------------------
   //   Transaction phase runners
   // --------------------------------------------------------------

   /**
    * creates a commit() MethodCall and feeds it to handleCommitRollback();
    *
    * @param gtx
    */
   protected void runCommitPhase(InvocationContext ctx, GlobalTransaction gtx, Transaction tx, List modifications, List clModifications, boolean onePhaseCommit)
   {
      // set the hasMods flag in the invocation ctx.  This should not be replicated, just used locally by the interceptors.
      ctx.setTxHasMods(modifications != null && modifications.size() > 0);
      ctx.setCacheLoaderHasMods(clModifications != null && clModifications.size() > 0);
      try
      {
         MethodCall commitMethod;
         if (onePhaseCommit)
         {
            // running a 1-phase commit.
            if (configuration.isNodeLockingOptimistic())
            {
               commitMethod = MethodCallFactory.create(MethodDeclarations.optimisticPrepareMethod_id,
                     gtx, modifications, null, cache.getLocalAddress(), true);
            }
            else
            {
               commitMethod = MethodCallFactory.create(MethodDeclarations.prepareMethod_id,
                     gtx, modifications, cache.getLocalAddress(),
                     true);
            }
         }
         else
         {
            commitMethod = MethodCallFactory.create(MethodDeclarations.commitMethod_id, gtx);
         }

         if (trace)
         {
            log.trace(" running commit for " + gtx);
         }

         handleCommitRollback(ctx, commitMethod);
      }
      catch (Throwable e)
      {
         log.warn("Commit failed.  Clearing stale locks.");
         try
         {
            cleanupStaleLocks(gtx);
         }
         catch (RuntimeException re)
         {
            log.error("Unable to clear stale locks", re);
            throw re;
         }
         catch (Throwable e2)
         {
            log.error("Unable to clear stale locks", e2);
            throw new RuntimeException(e2);
         }
         if (e instanceof RuntimeException)
            throw (RuntimeException) e;
         else
            throw new RuntimeException("Commit failed.", e);
      }
   }


   private void cleanupStaleLocks(GlobalTransaction gtx) throws Throwable
   {
      TransactionEntry entry = txTable.get(gtx);
      if (entry != null)
      {
         entry.releaseAllLocksLIFO(gtx);
      }
   }

   /**
    * creates a rollback() MethodCall and feeds it to handleCommitRollback();
    *
    * @param gtx
    */
   protected void runRollbackPhase(InvocationContext ctx, GlobalTransaction gtx, Transaction tx, List modifications)
   {
      //Transaction ltx = null;
      try
      {
         ctx.setTxHasMods(modifications != null && modifications.size() > 0);
         // JBCACHE-457
         //            MethodCall rollbackMethod = MethodCall(CacheImpl.rollbackMethod, new Object[]{gtx, hasMods ? true : false});
         MethodCall rollbackMethod = MethodCallFactory.create(MethodDeclarations.rollbackMethod_id, gtx);
         if (trace)
         {
            log.trace(" running rollback for " + gtx);
         }

         //JBCACHE-359 Store a lookup for the gtx so a listener
         // callback can find it
         //ltx = getLocalTxForGlobalTx(gtx);
         rollbackTransactions.put(tx, gtx);

         handleCommitRollback(ctx, rollbackMethod);
      }
      catch (Throwable e)
      {
         log.warn("Rollback had a problem", e);
      }
      finally
      {
         if (tx != null) rollbackTransactions.remove(tx);
      }
   }

   /**
    * Handles a local prepare - invoked by the sync handler.  Tests if the current tx matches the gtx passed in to the
    * method call and passes the prepare() call up the chain.
    *
    * @return
    * @throws Throwable
    */
   protected Object runPreparePhase(InvocationContext ctx, GlobalTransaction gtx, List modifications) throws Throwable
   {
      // build the method call
      MethodCall prepareMethod;
      //        if (cache.getCacheModeInternal() != CacheImpl.REPL_ASYNC)
      //        {
      // running a 2-phase commit.
      if (configuration.isNodeLockingOptimistic())
      {
         prepareMethod = MethodCallFactory.create(MethodDeclarations.optimisticPrepareMethod_id, gtx, modifications, null, cache.getLocalAddress(), false);
      }
      else if (configuration.getCacheMode() != Configuration.CacheMode.REPL_ASYNC)
      {
         prepareMethod = MethodCallFactory.create(MethodDeclarations.prepareMethod_id,
               gtx, modifications, cache.getLocalAddress(),
               false);// don't commit or rollback - wait for call
      }
      //}
      else
      {
         // this is a REPL_ASYNC call - do 1-phase commit.  break!
         if (trace) log.trace("This is a REPL_ASYNC call (1 phase commit) - do nothing for beforeCompletion()");
         return null;
      }

      // passes a prepare call up the local interceptor chain.  The replication interceptor
      // will do the broadcasting if needed.  This is so all requests (local/remote) are
      // treated the same
      Object result;

      // Is there a local transaction associated with GTX ?
      Transaction ltx = ctx.getTransaction();

      //if ltx is not null and it is already running
      if (txManager.getTransaction() != null && ltx != null && txManager.getTransaction().equals(ltx))
      {
         MethodCall originalCall = ctx.getMethodCall();
         ctx.setMethodCall(prepareMethod);
         try
         {
            result = nextInterceptor(ctx);
         }
         finally
         {
            ctx.setMethodCall(originalCall);
         }
      }
      else
      {
         log.warn("Local transaction does not exist or does not match expected transaction " + gtx);
         throw new CacheException(" local transaction " + ltx + " does not exist or does not match expected transaction " + gtx);
      }
      return result;
   }

   // --------------------------------------------------------------
   //   Private helper methods
   // --------------------------------------------------------------


   /**
    * Creates a gtx (if one doesnt exist), a sync handler, and registers the tx.
    *
    * @param tx
    * @return
    * @throws Exception
    */
   private GlobalTransaction registerTransaction(Transaction tx, InvocationContext ctx) throws Exception
   {
      GlobalTransaction gtx;
      if (isValid(tx) && transactions.put(tx, NULL) == null)
      {
         gtx = cache.getCurrentTransaction(tx, true);
         if (gtx.isRemote())
         {
            // should be no need to register a handler since this a remotely initiated gtx
            if (trace)
            {
               log.trace("is a remotely initiated gtx so no need to register a tx for it");
            }
         }
         else
         {
            if (trace)
            {
               log.trace("Registering sync handler for tx " + tx + ", gtx " + gtx);
            }
            // see the comment in the LocalSyncHandler for the last isOriginLocal param.
            LocalSynchronizationHandler myHandler = new LocalSynchronizationHandler(gtx, tx, !ctx.isOriginLocal());
            registerHandler(tx, myHandler, ctx, txTable.get(gtx));
         }
      }
      else if ((gtx = (GlobalTransaction) rollbackTransactions.get(tx)) != null)
      {
         if (trace) log.trace("Transaction " + tx + " is already registered and is rolling back.");
      }
      else
      {
         if (trace) log.trace("Transaction " + tx + " is already registered.");

      }
      return gtx;
   }

   /**
    * Registers a sync hander against a tx.
    *
    * @param tx
    * @param handler
    * @throws Exception
    */
   private void registerHandler(Transaction tx, Synchronization handler, InvocationContext ctx, TransactionEntry entry) throws Exception
   {
      OrderedSynchronizationHandler orderedHandler = entry.getOrderedSynchronizationHandler(); //OrderedSynchronizationHandler.getInstance(tx);

      if (trace) log.trace("registering for TX completion: SynchronizationHandler(" + handler + ")");

      orderedHandler.registerAtHead(handler);// needs to be invoked first on TX commit

      cache.getNotifier().notifyTransactionRegistered(tx, ctx);
   }

   /**
    * Replaces the global transaction in a method call with a new global transaction passed in.
    */
   private MethodCall replaceGtx(MethodCall m, GlobalTransaction gtx)
   {
      Class[] argClasses = m.getMethod().getParameterTypes();
      Object[] args = m.getArgs();

      for (int i = 0; i < argClasses.length; i++)
      {
         if (argClasses[i].equals(GlobalTransaction.class))
         {
            if (!gtx.equals(args[i]))
            {
               args[i] = gtx;
               m.setArgs(args);
            }
            break;
         }
      }
      return m;
   }

   /**
    * Creates and starts a local tx
    *
    * @return
    * @throws Exception
    */
   private Transaction createLocalTx() throws Exception
   {
      if (trace)
      {
         log.trace("Creating transaction for thread " + Thread.currentThread());
      }
      Transaction localTx;
      if (txManager == null) throw new Exception("Failed to create local transaction; TransactionManager is null");
      txManager.begin();
      localTx = txManager.getTransaction();
      return localTx;
   }

   /**
    * Creates a new local transaction for a given global transaction.
    *
    * @param gtx
    * @return
    * @throws Exception
    */
   private Transaction createLocalTxForGlobalTx(GlobalTransaction gtx, InvocationContext ctx) throws Exception
   {
      Transaction localTx = createLocalTx();
      txTable.put(localTx, gtx);
      // attach this to the context
      ctx.setTransaction(localTx);
      if (trace) log.trace("Created new tx for gtx " + gtx);
      return localTx;
   }

   // ------------------------------------------------------------------------
   // Synchronization classes
   // ------------------------------------------------------------------------

   // this controls the whole transaction

   private class RemoteSynchronizationHandler implements Synchronization
   {
      Transaction tx = null;
      GlobalTransaction gtx = null;
      //      CacheSPI cache = null;
      List modifications = null;
      TransactionEntry entry = null;
      protected InvocationContext ctx; // the context for this call.


      RemoteSynchronizationHandler(GlobalTransaction gtx, Transaction tx)
      {
         this.gtx = gtx;
         this.tx = tx;
      }

      public void beforeCompletion()
      {
         if (trace) log.trace("Running beforeCompletion on gtx " + gtx);
         entry = txTable.get(gtx);
         if (entry == null)
         {
            log.error("Transaction has a null transaction entry - beforeCompletion() will fail.");
            log.error("TxTable contents: " + txTable);
            throw new IllegalStateException("cannot find transaction entry for " + gtx);
         }

         modifications = entry.getModifications();
         ctx = cache.getInvocationContext();
         if (ctx.isOptionsUninitialised() && entry.getOption() != null) ctx.setOptionOverrides(entry.getOption());

         assertCanContinue();

         ctx.setOriginLocal(false);
      }

      // this should really not be done here -
      // it is supposed to be post commit not actually run the commit
      public void afterCompletion(int status)
      {
         // could happen if a rollback is called and beforeCompletion() doesn't get called.
         if (ctx == null)
         {
            ctx = cache.getInvocationContext();
         }

         entry = txTable.get(gtx);

         if (ctx.isOptionsUninitialised() && entry != null && entry.getOption() != null)
         {
            // use the options from the transaction entry instead
            ctx.setOptionOverrides(entry.getOption());
         }

         try
         {
            assertCanContinue();
            setTransactionalContext(tx, gtx, ctx);

            try
            {
               if (txManager.getTransaction() != null && !txManager.getTransaction().equals(tx)) txManager.resume(tx);
            }
            catch (Exception e)
            {
               log.error("afterCompletion error: " + status, e);
            }

            if (trace) log.trace("calling aftercompletion for " + gtx);

            List cacheLoaderModifications = null;
            // set any transaction wide options as current for this thread.
            if (entry != null)
            {
               modifications = entry.getModifications();
               cacheLoaderModifications = entry.getCacheLoaderModifications();
               ctx.setOptionOverrides(entry.getOption());
            }
            if (tx != null) transactions.remove(tx);

            switch (status)
            {
               case Status.STATUS_COMMITTED:

                  // if this is optimistic or sync repl
                  boolean onePhaseCommit = !configuration.isNodeLockingOptimistic() && configuration.getCacheMode() == Configuration.CacheMode.REPL_ASYNC;
                  if (log.isDebugEnabled()) log.debug("Running commit phase.  One phase? " + onePhaseCommit);
                  runCommitPhase(ctx, gtx, tx, modifications, cacheLoaderModifications, onePhaseCommit);
                  log.debug("Finished commit phase");
                  break;
               case Status.STATUS_UNKNOWN:
                  log.warn("Received JTA STATUS_UNKNOWN in afterCompletion()!  XA resources may not be in sync.  The app should manually clean up resources at this point.");
               case Status.STATUS_MARKED_ROLLBACK:
               case Status.STATUS_ROLLEDBACK:
                  log.debug("Running rollback phase");
                  runRollbackPhase(ctx, gtx, tx, modifications);
                  log.debug("Finished rollback phase");
                  break;

               default:
                  throw new IllegalStateException("illegal status: " + status);
            }
         }
         finally
         {
            // clean up the tx table
            txTable.remove(gtx);
            txTable.remove(tx);
            setTransactionalContext(null, null, ctx);
            cleanupInternalState();
         }
      }

      private void assertCanContinue()
      {
         if (!cache.getCacheStatus().allowInvocations() && (ctx.getOptionOverrides() == null || !ctx.getOptionOverrides().isSkipCacheStatusCheck()))
            throw new IllegalStateException("Cache not in STARTED state!");
      }

      /**
       * Cleans out (nullifies) member variables held by the sync object for easier gc.  Could be (falsely) seen as a mem
       * leak if the TM implementation hangs on to the synchronizations for an unnecessarily long time even after the tx
       * completes.  See JBCACHE-1007.
       */
      private void cleanupInternalState()
      {
         tx = null;
         gtx = null;
         modifications = null;
         if (entry != null) entry.reset();
         entry = null;
      }

      public String toString()
      {
         return "TxInterceptor.RemoteSynchronizationHandler(gtx=" + gtx + ", tx=" + getTxAsString() + ")";
      }

      protected String getTxAsString()
      {
         // JBCACHE-1114 -- don't call toString() on tx or it can lead to stack overflow
         if (tx == null)
            return null;

         return tx.getClass().getName() + "@" + System.identityHashCode(tx);
      }
   }

   private class LocalSynchronizationHandler extends RemoteSynchronizationHandler
   {
      private boolean localRollbackOnly = true;
      // a VERY strange situation where a tx has remote origins, but since certain buddy group org methods perform local
      // cleanups even when remotely triggered, and optimistic locking is used, you end up with an implicit local tx.
      // This is STILL remotely originating though and this needs to be made explicit here.
      // this can be checked by inspecting the InvocationContext.isOriginLocal() at the time of registering the sync.
      private boolean remoteLocal = false;
      private Option originalOptions, transactionalOptions;

      /**
       * A Synchronization for locally originating txs.
       * <p/>
       * a VERY strange situation where a tx has remote origins, but since certain buddy group org methods perform local
       * cleanups even when remotely triggered, and optimistic locking is used, you end up with an implicit local tx.
       * This is STILL remotely originating though and this needs to be made explicit here.
       * this can be checked by inspecting the InvocationContext.isOriginLocal() at the time of registering the sync.
       *
       * @param gtx
       * @param tx
       * @param remoteLocal
       */
      LocalSynchronizationHandler(GlobalTransaction gtx, Transaction tx, boolean remoteLocal)
      {
         super(gtx, tx);
         this.remoteLocal = remoteLocal;
      }

      @Override
      public void beforeCompletion()
      {
         super.beforeCompletion();
         ctx.setOriginLocal(!remoteLocal); // this is the LOCAL sync handler after all!
         // fetch the modifications before the transaction is committed
         // (and thus removed from the txTable)
         setTransactionalContext(tx, gtx, ctx);
         if (!entry.existModifications())
         {
            if (trace) log.trace("No modifications in this tx.  Skipping beforeCompletion()");
            return;
         }

         // set any transaction wide options as current for this thread.
         originalOptions = ctx.getOptionOverrides();
         transactionalOptions = entry.getOption();
         ctx.setOptionOverrides(transactionalOptions);

         try
         {
            switch (tx.getStatus())
            {
               // if we are active or preparing then we can go ahead
               case Status.STATUS_ACTIVE:
               case Status.STATUS_PREPARING:
                  // run a prepare call.
                  Object result = runPreparePhase(ctx, gtx, modifications);

                  if (result instanceof Throwable)
                  {
                     if (log.isDebugEnabled())
                        log.debug("Transaction needs to be rolled back - the cache returned an instance of Throwable for this prepare call (tx=" + tx + " and gtx=" + gtx + ")", (Throwable) result);
                     tx.setRollbackOnly();
                     throw (Throwable) result;
                  }
                  break;
               default:
                  throw new CacheException("transaction " + tx + " in status " + tx.getStatus() + " unable to start transaction");
            }
         }
         catch (Throwable t)
         {
            if (log.isWarnEnabled()) log.warn("Caught exception, will now set transaction to roll back", t);
            try
            {
               tx.setRollbackOnly();
            }
            catch (SystemException se)
            {
               throw new RuntimeException("setting tx rollback failed ", se);
            }
            if (t instanceof RuntimeException)
               throw (RuntimeException) t;
            else
               throw new RuntimeException("", t);
         }
         finally
         {
            localRollbackOnly = false;
            setTransactionalContext(null, null, ctx);
            ctx.setOptionOverrides(originalOptions);
         }
      }

      @Override
      public void afterCompletion(int status)
      {
         // could happen if a rollback is called and beforeCompletion() doesn't get called.
         if (ctx == null) ctx = cache.getInvocationContext();
         ctx.setLocalRollbackOnly(localRollbackOnly);
         ctx.setOptionOverrides(transactionalOptions);
         ctx.setTransaction(tx);
         ctx.setGlobalTransaction(gtx);
         try
         {
            super.afterCompletion(status);
         }
         finally
         {
            ctx.setOptionOverrides(originalOptions);
         }
      }

      public String toString()
      {
         return "TxInterceptor.LocalSynchronizationHandler(gtx=" + gtx + ", tx=" + getTxAsString() + ")";
      }
   }
}
TOP

Related Classes of org.jboss.cache.interceptors.TxInterceptor$LocalSynchronizationHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.