package org.infinispan.transaction.xa; import javax.transaction.xa.XAException; import javax.transaction.xa.XAResource; import javax.transaction.xa.Xid; import org.infinispan.transaction.impl.AbstractEnlistmentAdapter; import org.infinispan.transaction.xa.recovery.RecoveryManager; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; /** * This acts both as an local {@link org.infinispan.transaction.xa.CacheTransaction} and implementor of an {@link * javax.transaction.xa.XAResource} that will be called by tx manager on various tx stages. * * @author Mircea.Markus@jboss.com * @since 4.0 */ public class TransactionXaAdapter extends AbstractEnlistmentAdapter implements XAResource { private static final Log log = LogFactory.getLog(TransactionXaAdapter.class); private static boolean trace = log.isTraceEnabled(); /** * It is really useful only if TM and client are in separate processes and TM fails. This is because a client might * call tm.begin and then the TM (running separate process) crashes. In this scenario the TM won't ever call * XAResource.rollback, so these resources would be held there forever. By knowing the timeout the RM can proceed * releasing the resources associated with given tx. */ private int txTimeout; private final XaTransactionTable txTable; /** * XAResource is associated with a transaction between enlistment (XAResource.start()) XAResource.end(). It's only the * boundary methods (prepare, commit, rollback) that need to be "stateless". * Reefer to section 3.4.4 from JTA spec v.1.1 */ private final LocalXaTransaction localTransaction; private volatile RecoveryManager.RecoveryIterator recoveryIterator; public TransactionXaAdapter(LocalXaTransaction localTransaction, XaTransactionTable txTable) { super(localTransaction); this.txTable = txTable; this.localTransaction = localTransaction; } public TransactionXaAdapter(XaTransactionTable txTable) { super(); this.txTable = txTable; localTransaction = null; } /** * This can be call for any transaction object. See Section 3.4.6 (Resource Sharing) from JTA spec v1.1. */ @Override public int prepare(Xid externalXid) throws XAException { return txTable.prepare(externalXid); } /** * Same comment as for {@link #prepare(javax.transaction.xa.Xid)} applies for commit. */ @Override public void commit(Xid externalXid, boolean isOnePhase) throws XAException { txTable.commit(externalXid, isOnePhase); } /** * Same comment as for {@link #prepare(javax.transaction.xa.Xid)} applies for commit. */ @Override public void rollback(Xid externalXid) throws XAException { txTable.rollback(externalXid); } @Override public void start(Xid externalXid, int i) throws XAException { txTable.start(externalXid, localTransaction); } @Override public void end(Xid externalXid, int i) throws XAException { txTable.end(this.localTransaction); } @Override public void forget(Xid externalXid) throws XAException { txTable.forget(externalXid); } @Override public int getTransactionTimeout() throws XAException { if (trace) log.trace("start called"); return txTimeout; } /** * the only situation in which it returns true is when the other xa resource pertains to the same cache, on * the same node. */ @Override public boolean isSameRM(XAResource xaResource) throws XAException { return isIsSameRM(xaResource); } private boolean isIsSameRM(XAResource xaResource) { if (!(xaResource instanceof TransactionXaAdapter)) { return false; } TransactionXaAdapter other = (TransactionXaAdapter) xaResource; //there is only one enlistment manager per cache and this is more efficient that equals. return this.txTable == other.txTable; } @Override public Xid[] recover(int flag) throws XAException { if (!txTable.isRecoveryEnabled()) { log.recoveryIgnored(); return RecoveryManager.RecoveryIterator.NOTHING; } if (trace) log.trace("recover called: " + flag); if (isFlag(flag, TMSTARTRSCAN)) { recoveryIterator = txTable.recoveryManager.getPreparedTransactionsFromCluster(); if (trace) log.tracef("Fetched a new recovery iterator: %s", recoveryIterator); } if (isFlag(flag, TMENDRSCAN)) { if (trace) log.trace("Flushing the iterator"); return recoveryIterator.all(); } else { //as per the spec: "TMNOFLAGS this flag must be used when no other flags are specified." if (!isFlag(flag, TMSTARTRSCAN) && !isFlag(flag, TMNOFLAGS)) throw new IllegalArgumentException( "TMNOFLAGS this flag must be used when no other flags are specified." + " Received " + flag); return recoveryIterator.hasNext() ? recoveryIterator.next() : RecoveryManager.RecoveryIterator.NOTHING; } } @Override public boolean setTransactionTimeout(int i) throws XAException { this.txTimeout = i; return true; } @Override public String toString() { return "TransactionXaAdapter{" + "localTransaction=" + localTransaction + '}'; } public LocalXaTransaction getLocalTransaction() { return localTransaction; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TransactionXaAdapter that = (TransactionXaAdapter) o; if (localTransaction != null ? !localTransaction.equals(that.localTransaction) : that.localTransaction != null) return false; //also include the enlistment manager in comparison - needed when same tx spans multiple caches. return txTable == that.txTable; } private boolean isFlag(int value, int flag) { return (value & flag) != 0; } }