/**
* Alipay.com Inc.
* Copyright (c) 2004-2012 All Rights Reserved.
*/
package com.alipay.zdal.datasource.tm;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import org.apache.log4j.Logger;
import com.alipay.zdal.datasource.resource.spi.work.Work;
import com.alipay.zdal.datasource.resource.spi.work.WorkCompletedException;
import com.alipay.zdal.datasource.resource.spi.work.WorkException;
import com.alipay.zdal.datasource.resource.util.timeout.Timeout;
import com.alipay.zdal.datasource.resource.util.timeout.TimeoutFactory;
import com.alipay.zdal.datasource.resource.util.timeout.TimeoutTarget;
import com.alipay.zdal.datasource.tm.integrity.TransactionIntegrity;
import com.alipay.zdal.datasource.transaction.HeuristicMixedException;
import com.alipay.zdal.datasource.transaction.HeuristicRollbackException;
import com.alipay.zdal.datasource.transaction.RollbackException;
import com.alipay.zdal.datasource.transaction.Status;
import com.alipay.zdal.datasource.transaction.Synchronization;
import com.alipay.zdal.datasource.transaction.SystemException;
import com.alipay.zdal.datasource.transaction.Transaction;
/**
* Our <code>Transaction</code> implementation.
*
* @see TxManager
*
*
* @author ����
* @version $Id: TransactionImpl.java, v 0.1 2014-1-6 ����05:47:58 Exp $
*/
public class TransactionImpl implements Transaction, TimeoutTarget {
// Constants -----------------------------------------------------
/**
* Code meaning "no heuristics seen",
* must not be XAException.XA_HEURxxx
*/
private static final int HEUR_NONE = XAException.XA_RETRY;
// Resource states
private final static int RS_NEW = 0; // not yet enlisted
private final static int RS_ENLISTED = 1; // enlisted
private final static int RS_SUSPENDED = 2; // suspended
private final static int RS_ENDED = 3; // not associated
private final static int RS_VOTE_READONLY = 4; // voted read-only
private final static int RS_VOTE_OK = 5; // voted ok
private final static int RS_FORGOT = 6; // RM has forgotten
// private static final ReentrantLock lock = new ReentrantLock();
// Attributes ----------------------------------------------------
/** Class logger, we don't want a new logger with every transaction. */
private static Logger log = Logger.getLogger(TransactionImpl.class);
/** The ID of this transaction. */
private final XidImpl xid;
/** The global id */
private final GlobalId gid;
private final HashSet threads = new HashSet(1);
private final Map transactionLocalMap = Collections.synchronizedMap(new HashMap());
private Throwable cause;
/**
* The synchronizations to call back.
*/
private Synchronization[] sync = new Synchronization[3];
/**
* Size of allocated synchronization array.
*/
private int syncAllocSize = 3;
/**
* Count of synchronizations for this transaction.
*/
private int syncCount = 0;
/**
* A list of the XAResources that have participated in this transaction.
*/
private ArrayList resources = new ArrayList(3);
/**
* The XAResource used in the last resource gambit
*/
private Resource lastResource;
/**
* Flags that it is too late to enlist new resources.
*/
private boolean resourcesEnded = false;
/**
* Last branch id used.
*/
private long lastBranchId = 0;
/**
* Status of this transaction.
*/
private int status;
/**
* The heuristics status of this transaction.
*/
private int heuristicCode = HEUR_NONE;
/**
* The time when this transaction was started.
*/
private final long start;
/**
* The timeout handle for this transaction.
*/
private Timeout timeout;
/**
* Timeout in millisecs
*/
private final long timeoutPeriod;
/**
* Mutex for thread-safety. This should only be changed in the
* <code>lock()</code> and <code>unlock()</code> methods.
*/
private Thread locked = null;
/**
* The lock depth
*/
private int lockDepth = 0;
/** Any current work associated with the transaction */
private Work work;
/**
* Flags that we are done with this transaction and that it can be reused.
*/
private boolean done = false;
// Static --------------------------------------------------------
/**
* Factory for Xid instances of specified class.
* This is set from the <code>TransactionManagerService</code>
* MBean.
*/
static XidFactoryMBean xidFactory = new XidFactory();
// static TransactionManagerService txManagerService;
/** The timeout factory */
// static TimeoutFactory timeoutFactory = TimeoutFactory.getSingleton();
/**
* This static code is only present for testing purposes so a
* tm can be usable without a lot of setup.
*/
static void defaultXidFactory() {
// if (xidFactory == null) {
// xidFactory = new XidFactory();
// }
}
// Constructors --------------------------------------------------
TransactionImpl(long timeout) {
xid = xidFactory.newXid();
gid = xid.getTrulyGlobalId();
status = Status.STATUS_ACTIVE;
start = System.currentTimeMillis();
this.timeout = TimeoutFactory.createTimeout(start + timeout, this);
this.timeoutPeriod = timeout;
if (log.isDebugEnabled()) {
log.debug("Created new instance for tx=" + toString());
}
}
TransactionImpl(GlobalId gid, long timeout) {
this.gid = gid;
xid = xidFactory.newXid();
status = Status.STATUS_ACTIVE;
start = System.currentTimeMillis();
this.timeout = TimeoutFactory.createTimeout(start + timeout, this);
this.timeoutPeriod = timeout;
if (log.isDebugEnabled()) {
log.debug("Created new instance for tx=" + toString());
}
}
// Implements TimeoutTarget --------------------------------------
/**
* Called when our timeout expires.
*/
public void timedOut(Timeout timeout) {
lock();
try {
log.warn("Transaction " + toString() + " timed out." + " status="
+ getStringStatus(status));
if (this.timeout == null)
return; // Don't race with timeout cancellation.
this.timeout = null;
switch (status) {
case Status.STATUS_ROLLEDBACK:
case Status.STATUS_COMMITTED:
case Status.STATUS_NO_TRANSACTION:
break; // Transaction done.
case Status.STATUS_ROLLING_BACK:
break; // Will be done shortly.
case Status.STATUS_COMMITTING:
// This is _very_ bad:
// We are in the second commit phase, and have decided
// to commit, but now we get a timeout and should rollback.
// So we end up with a mixed decision.
gotHeuristic(null, XAException.XA_HEURMIX);
status = Status.STATUS_MARKED_ROLLBACK;
break; // commit will fail
case Status.STATUS_PREPARED:
// This is bad:
// We are done with the first phase, and are persistifying
// our decision. Fortunately this case is currently never
// hit, as we do not release the lock between the two phases.
case Status.STATUS_ACTIVE:
status = Status.STATUS_MARKED_ROLLBACK;
// fall through..
case Status.STATUS_MARKED_ROLLBACK:
// don't rollback for now, this messes up with the TxInterceptor.
interruptThreads();
break;
case Status.STATUS_PREPARING:
status = Status.STATUS_MARKED_ROLLBACK;
break; // commit will fail
default:
log.warn("Unknown status at timeout, tx=" + toString());
break;
}
} finally {
unlock();
}
}
// Implements Transaction ----------------------------------------
public void commit() throws RollbackException, HeuristicMixedException,
HeuristicRollbackException, java.lang.SecurityException,
java.lang.IllegalStateException, SystemException {
lock();
try {
if (log.isDebugEnabled())
log.debug("Committing, tx=" + this + ", status=" + getStringStatus(status));
beforePrepare();
if (status == Status.STATUS_ACTIVE) {
switch (getCommitStrategy()) {
case 0:
// Zero phase commit is really fast ;-)
if (log.isDebugEnabled())
log.debug("Zero phase commit " + this + ": No resources.");
status = Status.STATUS_COMMITTED;
break;
case 1:
// One phase commit
if (log.isDebugEnabled())
log.debug("One phase commit " + this + ": One resource.");
commitResources(true);
break;
default:
// Two phase commit
if (log.isDebugEnabled())
log.debug("Two phase commit " + this + ": Many resources.");
if (!prepareResources()) {
boolean commitDecision = status == Status.STATUS_PREPARED
&& (heuristicCode == HEUR_NONE || heuristicCode == XAException.XA_HEURCOM);
// TODO: Save decision to stable storage for recovery
// after system crash.
if (commitDecision)
commitResources(false);
} else
status = Status.STATUS_COMMITTED; // all was read-only
}
}
if (status != Status.STATUS_COMMITTED) {
Throwable causedByThrowable = cause;
rollbackResources();
completeTransaction();
// throw jboss rollback exception with the saved off cause
throw new JBossRollbackException("Unable to commit, tx=" + toString() + " status="
+ getStringStatus(status), causedByThrowable);
}
completeTransaction();
checkHeuristics();
if (log.isDebugEnabled()) {
log.debug("Committed OK, tx=" + this);
}
} finally {
unlock();
}
}
public void rollback() throws java.lang.IllegalStateException, java.lang.SecurityException,
SystemException {
lock();
try {
if (log.isDebugEnabled())
log.debug("rollback(): Entered, tx=" + toString() + " status="
+ getStringStatus(status));
checkWork();
switch (status) {
case Status.STATUS_ACTIVE:
status = Status.STATUS_MARKED_ROLLBACK;
// fall through..
case Status.STATUS_MARKED_ROLLBACK:
endResources();
rollbackResources();
completeTransaction();
// Cannot throw heuristic exception, so we just have to
// clear the heuristics without reporting.
heuristicCode = HEUR_NONE;
break;
case Status.STATUS_PREPARING:
// Set status to avoid race with prepareResources().
status = Status.STATUS_MARKED_ROLLBACK;
break; // commit() will do rollback.
default:
throw new IllegalStateException("Cannot rollback(), " + "tx=" + toString()
+ " status=" + getStringStatus(status));
}
} finally {
Thread.interrupted();// clear timeout that did an interrupt
unlock();
}
}
public boolean delistResource(XAResource xaRes, int flag)
throws java.lang.IllegalStateException,
SystemException {
if (xaRes == null)
throw new IllegalArgumentException("null xaRes tx=" + this);
if (flag != XAResource.TMSUCCESS && flag != XAResource.TMSUSPEND
&& flag != XAResource.TMFAIL)
throw new IllegalArgumentException("Bad flag: " + flag + " tx=" + this);
lock();
try {
if (log.isDebugEnabled())
log.debug("delistResource(): Entered, tx=" + toString() + " status="
+ getStringStatus(status));
Resource resource = findResource(xaRes);
if (resource == null)
throw new IllegalArgumentException("xaRes not enlisted " + xaRes);
switch (status) {
case Status.STATUS_ACTIVE:
case Status.STATUS_MARKED_ROLLBACK:
break;
case Status.STATUS_PREPARING:
throw new IllegalStateException("Already started preparing. " + this);
case Status.STATUS_ROLLING_BACK:
throw new IllegalStateException("Already started rolling back. " + this);
case Status.STATUS_PREPARED:
throw new IllegalStateException("Already prepared. " + this);
case Status.STATUS_COMMITTING:
throw new IllegalStateException("Already started committing. " + this);
case Status.STATUS_COMMITTED:
throw new IllegalStateException("Already committed. " + this);
case Status.STATUS_ROLLEDBACK:
throw new IllegalStateException("Already rolled back. " + this);
case Status.STATUS_NO_TRANSACTION:
throw new IllegalStateException("No transaction. " + this);
case Status.STATUS_UNKNOWN:
throw new IllegalStateException("Unknown state " + this);
default:
throw new IllegalStateException("Illegal status: " + getStringStatus(status)
+ " tx=" + this);
}
try {
return resource.delistResource(xaRes, flag);
} catch (XAException xae) {
logXAException(xae);
status = Status.STATUS_MARKED_ROLLBACK;
cause = xae;
return false;
}
} finally {
unlock();
}
}
public boolean enlistResource(XAResource xaRes) throws RollbackException,
java.lang.IllegalStateException, SystemException {
if (xaRes == null)
throw new IllegalArgumentException("null xaRes tx=" + this);
lock();
try {
if (log.isDebugEnabled())
log.debug("enlistResource(): Entered, tx=" + toString() + " status="
+ getStringStatus(status) + " xaRes=" + xaRes);
switch (status) {
case Status.STATUS_ACTIVE:
case Status.STATUS_PREPARING:
break;
case Status.STATUS_PREPARED:
throw new IllegalStateException("Already prepared. " + this);
case Status.STATUS_COMMITTING:
throw new IllegalStateException("Already started committing. " + this);
case Status.STATUS_COMMITTED:
throw new IllegalStateException("Already committed. " + this);
case Status.STATUS_MARKED_ROLLBACK:
throw new RollbackException("Already marked for rollback " + this);
case Status.STATUS_ROLLING_BACK:
throw new RollbackException("Already started rolling back. " + this);
case Status.STATUS_ROLLEDBACK:
throw new RollbackException("Already rolled back. " + this);
case Status.STATUS_NO_TRANSACTION:
throw new IllegalStateException("No transaction. " + this);
case Status.STATUS_UNKNOWN:
throw new IllegalStateException("Unknown state " + this);
default:
throw new IllegalStateException("Illegal status: " + getStringStatus(status)
+ " tx=" + this);
}
if (resourcesEnded)
throw new IllegalStateException("Too late to enlist resources " + this);
// Add resource
try {
Resource resource = findResource(xaRes);
// Existing resource
if (resource != null) {
if (resource.isEnlisted()) {
if (log.isDebugEnabled())
log.debug("Already enlisted: tx=" + toString() + " status="
+ getStringStatus(status) + " xaRes=" + xaRes);
return true; // already enlisted
}
if (resource.isDelisted(xaRes))
// this is a resource that returns false on all calls to
// isSameRM. Further, the last resource enlisted has
// already been delisted, so it is time to enlist it again.
resource = null;
else
return resource.startResource();
}
resource = findResourceManager(xaRes);
if (resource != null) {
// The xaRes is new. We register the xaRes with the Xid
// that the RM has previously seen from this transaction,
// and note that it has the same RM.
resource = addResource(xaRes, resource.getXid(), resource);
return resource.startResource();
}
// New resource and new RM: Create a new transaction branch.
resource = addResource(xaRes, createXidBranch(), null);
return resource.startResource();
} catch (XAException xae) {
logXAException(xae);
cause = xae;
return false;
}
} finally {
unlock();
}
}
public int getStatus() throws SystemException {
if (done)
return Status.STATUS_NO_TRANSACTION;
return status;
}
public void registerSynchronization(Synchronization s) throws RollbackException,
java.lang.IllegalStateException,
SystemException {
if (s == null)
throw new IllegalArgumentException("Null synchronization " + this);
lock();
try {
if (log.isDebugEnabled()) {
log.debug("registerSynchronization(): Entered, " + "tx=" + toString() + " status="
+ getStringStatus(status));
}
switch (status) {
case Status.STATUS_ACTIVE:
case Status.STATUS_PREPARING:
break;
case Status.STATUS_PREPARED:
throw new IllegalStateException("Already prepared. " + this);
case Status.STATUS_COMMITTING:
throw new IllegalStateException("Already started committing. " + this);
case Status.STATUS_COMMITTED:
throw new IllegalStateException("Already committed. " + this);
case Status.STATUS_MARKED_ROLLBACK:
throw new RollbackException("Already marked for rollback " + this);
case Status.STATUS_ROLLING_BACK:
throw new RollbackException("Already started rolling back. " + this);
case Status.STATUS_ROLLEDBACK:
throw new RollbackException("Already rolled back. " + this);
case Status.STATUS_NO_TRANSACTION:
throw new IllegalStateException("No transaction. " + this);
case Status.STATUS_UNKNOWN:
throw new IllegalStateException("Unknown state " + this);
default:
throw new IllegalStateException("Illegal status: " + getStringStatus(status)
+ " tx=" + this);
}
if (syncCount == syncAllocSize) {
// expand table
syncAllocSize = 2 * syncAllocSize;
Synchronization[] sy = new Synchronization[syncAllocSize];
System.arraycopy(sync, 0, sy, 0, syncCount);
sync = sy;
}
sync[syncCount++] = s;
} finally {
unlock();
}
}
public void setRollbackOnly() throws java.lang.IllegalStateException, SystemException {
lock();
try {
if (log.isDebugEnabled())
log.debug("setRollbackOnly(): Entered, tx=" + toString() + " status="
+ getStringStatus(status));
switch (status) {
case Status.STATUS_ACTIVE:
case Status.STATUS_PREPARING:
case Status.STATUS_PREPARED:
status = Status.STATUS_MARKED_ROLLBACK;
// fall through..
case Status.STATUS_MARKED_ROLLBACK:
case Status.STATUS_ROLLING_BACK:
break;
case Status.STATUS_COMMITTING:
throw new IllegalStateException("Already started committing. " + this);
case Status.STATUS_COMMITTED:
throw new IllegalStateException("Already committed. " + this);
case Status.STATUS_ROLLEDBACK:
throw new IllegalStateException("Already rolled back. " + this);
case Status.STATUS_NO_TRANSACTION:
throw new IllegalStateException("No transaction. " + this);
case Status.STATUS_UNKNOWN:
throw new IllegalStateException("Unknown state " + this);
default:
throw new IllegalStateException("Illegal status: " + getStringStatus(status)
+ " tx=" + this);
}
} finally {
unlock();
}
}
// Public --------------------------------------------------------
public int getAssociatedThreadCount() {
lock();
try {
return threads.size();
} finally {
unlock();
}
}
public Set getAssociatedThreads() {
lock();
try {
return Collections.unmodifiableSet(threads);
} finally {
unlock();
}
}
@Override
public int hashCode() {
return xid.hashCode();
}
@Override
public String toString() {
return "TransactionImpl:" + xidFactory.toString(xid);
}
@Override
public boolean equals(Object obj) {
if (obj != null && obj instanceof TransactionImpl)
return getLocalIdValue() == (((TransactionImpl) obj).getLocalIdValue());
return false;
}
/**
* Returns the local id of this transaction. The local id is used as
* a transaction propagation context within the JBoss server, and
* in the TxManager for mapping local transaction ids to transactions.
*/
public long getLocalIdValue() {
return xid.getLocalIdValue();
}
/**
* Returns the local id of this transaction. The local id is used as
* a transaction propagation context within the JBoss server, and
* in the TxManager for mapping local transaction ids to transactions.
*/
public LocalId getLocalId() {
return xid.getLocalId();
}
/**
* Returns the global id of this transaction. Ths global id is used in
* the TxManager, which keeps a map from global ids to transactions.
*/
public GlobalId getGlobalId() {
return gid;
}
/**
* Returns the xid of this transaction.
*/
public XidImpl getXid() {
return xid;
}
// Package protected ---------------------------------------------
void associateCurrentThread() {
Thread.interrupted();
lock();
try {
threads.add(Thread.currentThread());
} finally {
unlock();
}
}
void disassociateCurrentThread() {
// Just a tidyup, no need to synchronize
if (done) {
threads.remove(Thread.currentThread());
} else {
// Removing the association for an active transaction
lock();
try {
threads.remove(Thread.currentThread());
} finally {
unlock();
}
}
Thread.interrupted();
}
/**
* Lock this instance.
*/
synchronized void lock() {
if (done)
throw new IllegalStateException("Transaction has terminated " + this);
Thread currentThread = Thread.currentThread();
if (locked != null && locked != currentThread) {
if (log.isDebugEnabled()) {
log.debug("Lock contention, tx=" + toString() + " otherThread=" + locked);
}
//DEBUG Thread.currentThread().dumpStack();
while (locked != null && locked != currentThread) {
try {
// Wakeup happens when:
// - notify() is called from unlock()
// - notifyAll is called from instanceDone()
wait();
} catch (InterruptedException ex) {
// ignore
}
if (done)
throw new IllegalStateException("Transaction has now terminated " + this);
}
}
locked = currentThread;
++lockDepth;
}
/**
* Unlock this instance.
*/
synchronized void unlock() {
Thread currentThread = Thread.currentThread();
if (locked == null || locked != currentThread) {
log.warn("Unlocking, but not locked, tx=" + toString() + " otherThread=" + locked,
new Throwable("[Stack trace]"));
} else {
if (--lockDepth == 0) {
locked = null;
notify();
}
}
}
/**
* Prepare an external transaction
*
* @return XAResource.XA_RDONLY or XAResource.XA_OK
*/
int prepare() throws HeuristicMixedException, HeuristicRollbackException, RollbackException {
lock();
try {
if (log.isDebugEnabled())
log.debug("Preparing, tx=" + this + ", status=" + getStringStatus(status));
checkWork();
beforePrepare();
if (status == Status.STATUS_ACTIVE) {
switch (getCommitStrategy()) {
case 0:
// Nothing to do
if (log.isDebugEnabled()) {
log.debug("Prepare tx=" + this + ": No resources.");
}
status = Status.STATUS_COMMITTED;
completeTransaction();
return XAResource.XA_RDONLY;
default: {
// Two phase commit
if (log.isDebugEnabled())
log.debug("Prepare tx=" + this + ": Many resources.");
if (prepareResources()) {
if (log.isDebugEnabled()) {
log.debug("Prepared tx=" + this + ": All readonly.");
}
status = Status.STATUS_COMMITTED;
completeTransaction();
return XAResource.XA_RDONLY;
} else
break;/* else {
boolean commitDecision =
status == Status.STATUS_PREPARED &&
(heuristicCode == HEUR_NONE ||
heuristicCode == XAException.XA_HEURCOM);
// TODO: Save decision to stable storage for recovery after system crash.
}*/
}
}
}
if (status != Status.STATUS_PREPARED) {
// save off the cause throwable as Instance done resets it to null
Throwable causedByThrowable = cause;
rollbackResources();
completeTransaction();
// throw jboss rollback exception with the saved off cause
throw new JBossRollbackException("Unable to prepare, tx=" + toString() + " status="
+ getStringStatus(status), causedByThrowable);
}
// We are ok to commit
return XAResource.XA_OK;
} finally {
unlock();
}
}
/**
* Commit an external transaction
*
* @param onePhase whether the commit is one or two phase
*/
void commit(boolean onePhase) throws RollbackException, HeuristicMixedException,
HeuristicRollbackException, SystemException {
checkWork();
// One phase commit optimization
if (onePhase) {
commit();
return;
}
// Two phase
lock();
try {
if (log.isDebugEnabled())
log.debug("Committing two phase, tx=" + this + ", status="
+ getStringStatus(status));
switch (status) {
case Status.STATUS_PREPARING:
throw new IllegalStateException("Still preparing. " + this);
case Status.STATUS_ROLLING_BACK:
throw new IllegalStateException("Already started rolling back. " + this);
case Status.STATUS_ROLLEDBACK:
instanceDone();
checkHeuristics();
throw new IllegalStateException("Already rolled back. " + this);
case Status.STATUS_COMMITTING:
throw new IllegalStateException("Already started committing. " + this);
case Status.STATUS_COMMITTED:
instanceDone();
checkHeuristics();
throw new IllegalStateException("Already committed. " + this);
case Status.STATUS_NO_TRANSACTION:
throw new IllegalStateException("No transaction. " + this);
case Status.STATUS_UNKNOWN:
throw new IllegalStateException("Unknown state " + this);
case Status.STATUS_MARKED_ROLLBACK:
endResources();
rollbackResources();
completeTransaction();
checkHeuristics();
throw new RollbackException("Already marked for rollback " + this);
case Status.STATUS_PREPARED:
break;
default:
throw new IllegalStateException("Illegal status: " + getStringStatus(status)
+ " tx=" + this);
}
commitResources(false);
if (status != Status.STATUS_COMMITTED) {
Throwable causedByThrowable = cause;
rollbackResources();
completeTransaction();
// throw jboss rollback exception with the saved off cause
throw new JBossRollbackException("Unable to commit, tx=" + toString() + " status="
+ getStringStatus(status), causedByThrowable);
}
completeTransaction();
checkHeuristics();
if (log.isDebugEnabled())
log.debug("Committed OK, tx=" + this);
} finally {
unlock();
}
}
/**
* Get the work
*
* @return the work
*/
Work getWork() {
return work;
}
/**
* Set the work
*
* @param work the work
* @throws WorkCompletedException with error code WorkException.TX_CONCURRENT_WORK_DISALLOWED
* when work is already present for the xid or whose completion is in progress, only
* the global part of the xid must be used for this check. Or with error code
* WorkException.TX_RECREATE_FAILED if it is unable to recreate the transaction context
*/
void setWork(Work work) throws WorkCompletedException {
lock();
try {
if (work == null) {
this.work = null;
return;
}
if (status == Status.STATUS_NO_TRANSACTION || status == Status.STATUS_UNKNOWN)
throw new WorkCompletedException("The transaction is not active " + this + ": "
+ getStringStatus(status),
WorkException.TX_RECREATE_FAILED);
else if (status != Status.STATUS_ACTIVE)
throw new WorkCompletedException("Too late to start work " + this + ": "
+ getStringStatus(status),
WorkException.TX_CONCURRENT_WORK_DISALLOWED);
else if (this.work != null)
throw new WorkCompletedException("Already have work " + this + ": " + this.work,
WorkException.TX_CONCURRENT_WORK_DISALLOWED);
this.work = work;
} finally {
unlock();
}
}
/**
* Getter for property done.
*/
boolean isDone() {
return done;
}
// Private -------------------------------------------------------
/**
* Before prepare
*/
private void beforePrepare() throws HeuristicMixedException, HeuristicRollbackException,
RollbackException {
checkIntegrity();
doBeforeCompletion();
if (log.isDebugEnabled())
log.debug("Before completion done, tx=" + this + ", status=" + getStringStatus(status));
endResources();
}
/**
* Check the integrity of the transaction
*/
private void checkIntegrity() throws HeuristicMixedException, HeuristicRollbackException,
RollbackException {
// Spec defined checks for the transaction in a valid state
checkBeforeStatus();
TransactionIntegrity integrity = TxManager.getInstance().getTransactionIntegrity();
if (integrity != null) {
// Extra integrity checks
unlock();
try {
integrity.checkTransactionIntegrity(this);
} finally {
lock();
}
// Recheck the transaction state
checkBeforeStatus();
}
}
/**
* Check the before status
*/
private void checkBeforeStatus() throws HeuristicMixedException, HeuristicRollbackException,
RollbackException {
switch (status) {
case Status.STATUS_PREPARING:
throw new IllegalStateException("Already started preparing. " + this);
case Status.STATUS_PREPARED:
throw new IllegalStateException("Already prepared. " + this);
case Status.STATUS_ROLLING_BACK:
throw new IllegalStateException("Already started rolling back. " + this);
case Status.STATUS_ROLLEDBACK:
instanceDone();
checkHeuristics();
throw new IllegalStateException("Already rolled back." + this);
case Status.STATUS_COMMITTING:
throw new IllegalStateException("Already started committing. " + this);
case Status.STATUS_COMMITTED:
instanceDone();
checkHeuristics();
throw new IllegalStateException("Already committed. " + this);
case Status.STATUS_NO_TRANSACTION:
throw new IllegalStateException("No transaction. " + this);
case Status.STATUS_UNKNOWN:
throw new IllegalStateException("Unknown state " + this);
case Status.STATUS_MARKED_ROLLBACK:
endResources();
rollbackResources();
completeTransaction();
checkHeuristics();
throw new RollbackException("Already marked for rollback " + this);
case Status.STATUS_ACTIVE:
break;
default:
throw new IllegalStateException("Illegal status: " + getStringStatus(status)
+ " tx=" + this);
}
}
/**
* Complete the transaction
*/
private void completeTransaction() {
cancelTimeout();
doAfterCompletion();
instanceDone();
}
/**
* Interrupt all threads involved with transaction
* This is called on timeout
*/
private void interruptThreads() {
TxManager manager = TxManager.getInstance();
if (manager.isInterruptThreads()) {
HashSet clone = (HashSet) threads.clone();
threads.clear();
for (Iterator i = clone.iterator(); i.hasNext();) {
Thread thread = (Thread) i.next();
try {
thread.interrupt();
} catch (Throwable ignored) {
if (log.isDebugEnabled())
log.debug("Ignored error interrupting thread: " + thread, ignored);
}
}
}
}
/**
* Return a string representation of the given status code.
*/
private String getStringStatus(int status) {
switch (status) {
case Status.STATUS_PREPARING:
return "STATUS_PREPARING";
case Status.STATUS_PREPARED:
return "STATUS_PREPARED";
case Status.STATUS_ROLLING_BACK:
return "STATUS_ROLLING_BACK";
case Status.STATUS_ROLLEDBACK:
return "STATUS_ROLLEDBACK";
case Status.STATUS_COMMITTING:
return "STATUS_COMMITING";
case Status.STATUS_COMMITTED:
return "STATUS_COMMITED";
case Status.STATUS_NO_TRANSACTION:
return "STATUS_NO_TRANSACTION";
case Status.STATUS_UNKNOWN:
return "STATUS_UNKNOWN";
case Status.STATUS_MARKED_ROLLBACK:
return "STATUS_MARKED_ROLLBACK";
case Status.STATUS_ACTIVE:
return "STATUS_ACTIVE";
default:
return "STATUS_UNKNOWN(" + status + ")";
}
}
/**
* Return a string representation of the given XA error code.
*/
private String getStringXAErrorCode(int errorCode) {
switch (errorCode) {
case XAException.XA_HEURCOM:
return "XA_HEURCOM";
case XAException.XA_HEURHAZ:
return "XA_HEURHAZ";
case XAException.XA_HEURMIX:
return "XA_HEURMIX";
case XAException.XA_HEURRB:
return "XA_HEURRB";
case XAException.XA_NOMIGRATE:
return "XA_NOMIGRATE";
case XAException.XA_RBCOMMFAIL:
return "XA_RBCOMMFAIL";
case XAException.XA_RBDEADLOCK:
return "XA_RBDEADLOCK";
case XAException.XA_RBINTEGRITY:
return "XA_RBINTEGRITY";
case XAException.XA_RBOTHER:
return "XA_RBOTHER";
case XAException.XA_RBPROTO:
return "XA_RBPROTO";
case XAException.XA_RBROLLBACK:
return "XA_RBROLLBACK";
case XAException.XA_RBTIMEOUT:
return "XA_RBTIMEOUT";
case XAException.XA_RBTRANSIENT:
return "XA_RBTRANSIENT";
case XAException.XA_RDONLY:
return "XA_RDONLY";
case XAException.XA_RETRY:
return "XA_RETRY";
case XAException.XAER_ASYNC:
return "XAER_ASYNC";
case XAException.XAER_DUPID:
return "XAER_DUPID";
case XAException.XAER_INVAL:
return "XAER_INVAL";
case XAException.XAER_NOTA:
return "XAER_NOTA";
case XAException.XAER_OUTSIDE:
return "XAER_OUTSIDE";
case XAException.XAER_PROTO:
return "XAER_PROTO";
case XAException.XAER_RMERR:
return "XAER_RMERR";
case XAException.XAER_RMFAIL:
return "XAER_RMFAIL";
default:
return "XA_UNKNOWN(" + errorCode + ")";
}
}
private void logXAException(XAException xae) {
log.warn("XAException: tx=" + toString() + " errorCode="
+ getStringXAErrorCode(xae.errorCode), xae);
// if (txManagerService != null)
// txManagerService.formatXAException(xae, log);
}
/**
* Mark this transaction as non-existing.
*/
private synchronized void instanceDone() {
TxManager manager = TxManager.getInstance();
if (status == Status.STATUS_COMMITTED)
manager.incCommitCount();
else
manager.incRollbackCount();
// Clear tables refering to external objects.
// Even if a client holds on to this instance forever, the objects
// that we have referenced may be garbage collected.
sync = null;
resources = null;
transactionLocalMap.clear();
threads.clear();
// Garbage collection
manager.releaseTransactionImpl(this);
// Set the status
status = Status.STATUS_NO_TRANSACTION;
// Notify all threads waiting for the lock.
notifyAll();
// set the done flag
done = true;
}
/**
* Cancel the timeout.
* This will release the lock while calling out.
*/
private void cancelTimeout() {
if (timeout != null) {
unlock();
try {
timeout.cancel();
} catch (Exception e) {
if (log.isDebugEnabled())
log.debug("failed to cancel timeout " + this, e);
} finally {
lock();
}
timeout = null;
}
}
/**
* Return the resource for the given XAResource
*/
private Resource findResource(XAResource xaRes) {
// A linear search may seem slow, but please note that
// the number of XA resources registered with a transaction
// are usually low.
// Note: This searches backwards intentionally! It ensures that
// if this resource was enlisted multiple times, then the last one
// will be returned. All others should be in the state RS_ENDED.
// This allows ResourceManagers that always return false from isSameRM
// to be enlisted and delisted multiple times.
for (int idx = resources.size() - 1; idx >= 0; --idx) {
Resource resource = (Resource) resources.get(idx);
if (xaRes == resource.getXAResource())
return resource;
}
return null;
}
private Resource findResourceManager(XAResource xaRes) throws XAException {
for (int i = 0; i < resources.size(); ++i) {
Resource resource = (Resource) resources.get(i);
if (resource.isResourceManager(xaRes))
return resource;
}
return null;
}
/**
* Add a resource, expanding tables if needed.
*
* @param xaRes The new XA resource to add. It is assumed that the
* resource is not already in the table of XA resources.
* @param branchXid The Xid for the transaction branch that is to
* be used for associating with this resource.
* @param sameRMResource The resource of the first
* XA resource having the same resource manager as
* <code>xaRes</code>, or <code>null</code> if <code>xaRes</code>
* is the first resource seen with this resource manager.
*
* @return the new resource
*/
private Resource addResource(XAResource xaRes, Xid branchXid, Resource sameRMResource) {
Resource resource = new Resource(xaRes, branchXid, sameRMResource);
resources.add(resource);
// Remember the first resource that wants the last resource gambit
if (lastResource == null && xaRes instanceof LastResource)
lastResource = resource;
return resource;
}
/**
* End Tx association for all resources.
*/
private void endResources() {
for (int idx = 0; idx < resources.size(); ++idx) {
Resource resource = (Resource) resources.get(idx);
try {
resource.endResource();
} catch (XAException xae) {
logXAException(xae);
status = Status.STATUS_MARKED_ROLLBACK;
cause = xae;
}
}
resourcesEnded = true; // Too late to enlist new resources.
}
/**
* Call synchronization <code>beforeCompletion()</code>.
* This will release the lock while calling out.
*/
private void doBeforeCompletion() {
unlock();
try {
for (int i = 0; i < syncCount; i++) {
try {
if (log.isDebugEnabled())
log.debug("calling sync " + i + ", " + sync[i] + " tx=" + this);
sync[i].beforeCompletion();
} catch (Throwable t) {
if (log.isDebugEnabled())
log.debug("failed before completion " + sync[i], t);
status = Status.STATUS_MARKED_ROLLBACK;
// save the cause off so the user can inspect it
cause = t;
break;
}
}
} finally {
lock();
}
}
/**
* Call synchronization <code>afterCompletion()</code>.
* This will release the lock while calling out.
*/
private void doAfterCompletion() {
// Assert: Status indicates: Too late to add new synchronizations.
unlock();
try {
for (int i = 0; i < syncCount; i++) {
try {
sync[i].afterCompletion(status);
} catch (Throwable t) {
if (log.isDebugEnabled())
log.debug("failed after completion " + sync[i], t);
}
}
} finally {
lock();
}
}
/**
* We got another heuristic.
*
* Promote <code>heuristicCode</code> if needed and tell
* the resource to forget the heuristic.
* This will release the lock while calling out.
*
* @param resource The resource of the XA resource that got a
* heurictic in our internal tables, or <code>null</code>
* if the heuristic came from here.
* @param code The heuristic code, one of
* <code>XAException.XA_HEURxxx</code>.
*/
private void gotHeuristic(Resource resource, int code) {
switch (code) {
case XAException.XA_HEURMIX:
heuristicCode = XAException.XA_HEURMIX;
break;
case XAException.XA_HEURRB:
if (heuristicCode == HEUR_NONE)
heuristicCode = XAException.XA_HEURRB;
else if (heuristicCode == XAException.XA_HEURCOM
|| heuristicCode == XAException.XA_HEURHAZ)
heuristicCode = XAException.XA_HEURMIX;
break;
case XAException.XA_HEURCOM:
if (heuristicCode == HEUR_NONE)
heuristicCode = XAException.XA_HEURCOM;
else if (heuristicCode == XAException.XA_HEURRB
|| heuristicCode == XAException.XA_HEURHAZ)
heuristicCode = XAException.XA_HEURMIX;
break;
case XAException.XA_HEURHAZ:
if (heuristicCode == HEUR_NONE)
heuristicCode = XAException.XA_HEURHAZ;
else if (heuristicCode == XAException.XA_HEURCOM
|| heuristicCode == XAException.XA_HEURRB)
heuristicCode = XAException.XA_HEURMIX;
break;
default:
throw new IllegalArgumentException();
}
if (resource != null)
resource.forget();
}
/**
* Check for heuristics, clear and throw exception if any found.
*/
private void checkHeuristics() throws HeuristicMixedException, HeuristicRollbackException {
switch (heuristicCode) {
case XAException.XA_HEURHAZ:
heuristicCode = HEUR_NONE;
if (log.isDebugEnabled())
log.debug("Throwing HeuristicMixedException, tx=" + this + "status="
+ getStringStatus(status));
throw new HeuristicMixedException();
case XAException.XA_HEURMIX:
heuristicCode = HEUR_NONE;
if (log.isDebugEnabled())
log.debug("Throwing HeuristicMixedException, tx=" + this + "status="
+ getStringStatus(status));
throw new HeuristicMixedException();
case XAException.XA_HEURRB:
heuristicCode = HEUR_NONE;
if (log.isDebugEnabled())
log.debug("Throwing HeuristicRollbackException, tx=" + this + "status="
+ getStringStatus(status));
throw new HeuristicRollbackException();
case XAException.XA_HEURCOM:
heuristicCode = HEUR_NONE;
// Why isn't HeuristicCommitException used in JTA ?
// And why define something that is not used ?
// For now we just have to ignore this failure, even if it happened
// on rollback.
if (log.isDebugEnabled())
log.debug("NOT Throwing HeuristicCommitException, tx=" + this + "status="
+ getStringStatus(status));
return;
}
}
/**
* Prepare all enlisted resources.
* If the first phase of the commit process results in a decision
* to commit the <code>status</code> will be
* <code>Status.STATUS_PREPARED</code> on return.
* Otherwise the <code>status</code> will be
* <code>Status.STATUS_MARKED_ROLLBACK</code> on return.
* This will release the lock while calling out.
*
* @return True iff all resources voted read-only.
*/
private boolean prepareResources() {
boolean readOnly = true;
status = Status.STATUS_PREPARING;
// Prepare te XAResources
for (int i = 0; i < resources.size(); ++i) {
// Abort prepare on state change.
if (status != Status.STATUS_PREPARING)
return false;
Resource resource = (Resource) resources.get(i);
if (resource.isResourceManager() == false)
continue; // This RM already prepared.
// Ignore the last resource it is done later
if (resource == lastResource)
continue;
try {
int vote = resource.prepare();
if (vote == RS_VOTE_OK)
readOnly = false;
else if (vote != RS_VOTE_READONLY) {
// Illegal vote: rollback.
if (log.isDebugEnabled())
log.debug("illegal vote in prepare resources tx=" + this + " resource="
+ resource, new Exception());
status = Status.STATUS_MARKED_ROLLBACK;
return false;
}
} catch (XAException e) {
readOnly = false;
logXAException(e);
switch (e.errorCode) {
case XAException.XA_HEURCOM:
// Heuristic commit is not that bad when preparing.
// But it means trouble if we have to rollback.
gotHeuristic(resource, e.errorCode);
break;
case XAException.XA_HEURRB:
case XAException.XA_HEURMIX:
case XAException.XA_HEURHAZ:
gotHeuristic(resource, e.errorCode);
if (status == Status.STATUS_PREPARING)
status = Status.STATUS_MARKED_ROLLBACK;
break;
default:
cause = e;
if (status == Status.STATUS_PREPARING)
status = Status.STATUS_MARKED_ROLLBACK;
break;
}
} catch (Throwable t) {
if (log.isDebugEnabled())
log.debug("unhandled throwable in prepareResources " + this, t);
if (status == Status.STATUS_PREPARING)
status = Status.STATUS_MARKED_ROLLBACK;
cause = t;
}
}
// Abort prepare on state change.
if (status != Status.STATUS_PREPARING)
return false;
// Are we doing the last resource gambit?
if (lastResource != null) {
try {
lastResource.prepareLastResource();
lastResource.commit(false);
} catch (XAException e) {
logXAException(e);
switch (e.errorCode) {
case XAException.XA_HEURRB:
case XAException.XA_HEURCOM:
case XAException.XA_HEURMIX:
case XAException.XA_HEURHAZ:
//usually throws an exception, but not for a couple of cases.
gotHeuristic(lastResource, e.errorCode);
if (status == Status.STATUS_PREPARING)
status = Status.STATUS_MARKED_ROLLBACK;
break;
default:
cause = e;
if (status == Status.STATUS_PREPARING)
status = Status.STATUS_MARKED_ROLLBACK;
break;
}
} catch (Throwable t) {
if (log.isDebugEnabled())
log.debug("unhandled throwable in prepareResources " + this, t);
if (status == Status.STATUS_PREPARING)
status = Status.STATUS_MARKED_ROLLBACK;
cause = t;
}
}
if (status == Status.STATUS_PREPARING)
status = Status.STATUS_PREPARED;
else
return false;
return readOnly;
}
/**
* Commit all enlisted resources.
* This will release the lock while calling out.
*/
private void commitResources(boolean onePhase) {
status = Status.STATUS_COMMITTING;
for (int i = 0; i < resources.size(); ++i) {
// Abort commit on state change.
if (status != Status.STATUS_COMMITTING)
return;
Resource resource = (Resource) resources.get(i);
// Ignore the last resource, it is already committed
if (onePhase == false && lastResource == resource)
continue;
try {
resource.commit(onePhase);
} catch (XAException e) {
logXAException(e);
switch (e.errorCode) {
case XAException.XA_HEURRB:
case XAException.XA_HEURCOM:
case XAException.XA_HEURMIX:
case XAException.XA_HEURHAZ:
//usually throws an exception, but not for a couple of cases.
gotHeuristic(resource, e.errorCode);
//May not be correct for HEURCOM
//Two phase commit is committed after prepare is logged.
if (onePhase)
status = Status.STATUS_MARKED_ROLLBACK;
break;
default:
cause = e;
if (onePhase) {
status = Status.STATUS_MARKED_ROLLBACK;
break;
}
//Not much we can do if there is an RMERR in the
//commit phase of 2pc. I guess we try the other rms.
}
} catch (Throwable t) {
if (log.isDebugEnabled())
log.debug("unhandled throwable in commitResources " + this, t);
}
}
if (status == Status.STATUS_COMMITTING)
status = Status.STATUS_COMMITTED;
}
/**
* Rollback all enlisted resources.
* This will release the lock while calling out.
*/
private void rollbackResources() {
status = Status.STATUS_ROLLING_BACK;
for (int i = 0; i < resources.size(); ++i) {
Resource resource = (Resource) resources.get(i);
try {
resource.rollback();
} catch (XAException e) {
logXAException(e);
switch (e.errorCode) {
case XAException.XA_HEURRB:
// Heuristic rollback is not that bad when rolling back.
gotHeuristic(resource, e.errorCode);
continue;
case XAException.XA_HEURCOM:
case XAException.XA_HEURMIX:
case XAException.XA_HEURHAZ:
gotHeuristic(resource, e.errorCode);
continue;
default:
cause = e;
break;
}
} catch (Throwable t) {
if (log.isDebugEnabled())
log.debug("unhandled throwable in rollbackResources " + this, t);
}
}
status = Status.STATUS_ROLLEDBACK;
}
/**
* Create an Xid representing a new branch of this transaction.
*/
private Xid createXidBranch() {
long branchId = ++lastBranchId;
return xidFactory.newBranch(xid, branchId);
}
/**
* Determine the commit strategy
*
* @return 0 for nothing to do, 1 for one phase and 2 for two phase
*/
private int getCommitStrategy() {
int resourceCount = resources.size();
if (resourceCount == 0)
return 0;
if (resourceCount == 1)
return 1;
// first XAResource surely has -1, it's the first!
for (int i = 1; i < resourceCount; ++i) {
Resource resource = (Resource) resources.get(i);
if (resource.isResourceManager()) {
// this one is not the same rm as previous ones,
// there must be at least 2
return 2;
}
}
// all rms are the same one, one phase commit is ok.
return 1;
}
public long getTimeLeftBeforeTimeout(boolean errorRollback) throws RollbackException {
if (errorRollback && status != Status.STATUS_ACTIVE)
throw new RollbackException("Transaction is not active: "
+ TxUtils.getStatusAsString(status));
return (start + timeoutPeriod) - System.currentTimeMillis();
}
Object getTransactionLocalValue(TransactionLocal tlocal) {
return transactionLocalMap.get(tlocal);
}
void putTransactionLocalValue(TransactionLocal tlocal, Object value) {
transactionLocalMap.put(tlocal, value);
}
boolean containsTransactionLocal(TransactionLocal tlocal) {
return transactionLocalMap.containsKey(tlocal);
}
/**
* Check we have no outstanding work
*
* @throws IllegalStateException when there is still work
*/
private void checkWork() {
if (work != null)
throw new IllegalStateException("Work still outstanding " + work + " tx=" + this);
}
// Inner classes -------------------------------------------------
/**
* Represents a resource enlisted in the transaction
*/
private class Resource {
/** The XAResource */
private final XAResource xaResource;
/** The state of the resources */
private int resourceState;
/** The related xa resource from the same resource manager */
private final Resource resourceSameRM;
/** The Xid of this resource */
private final Xid resourceXid;
/**
* Create a new resource
*/
public Resource(XAResource xaResource, Xid resourceXid, Resource resourceSameRM) {
this.xaResource = xaResource;
this.resourceXid = resourceXid;
this.resourceSameRM = resourceSameRM;
resourceState = RS_NEW;
}
/**
* Get the XAResource for this resource
*/
public XAResource getXAResource() {
return xaResource;
}
/**
* Get the Xid for this resource
*/
public Xid getXid() {
return resourceXid;
}
/**
* Is the resource enlisted?
*/
public boolean isEnlisted() {
return resourceState == RS_ENLISTED;
}
/**
* Is this a resource manager
*/
public boolean isResourceManager() {
return resourceSameRM == null;
}
/**
* Is this the resource manager for the passed xa resource
*/
public boolean isResourceManager(XAResource xaRes) throws XAException {
return resourceSameRM == null && xaRes.isSameRM(xaResource);
}
/**
* Is the resource delisted and the XAResource always returns false
* for isSameRM
*/
public boolean isDelisted(XAResource xaRes) throws XAException {
return resourceState == RS_ENDED && xaResource.isSameRM(xaRes) == false;
}
/**
* Call <code>start()</code> on a XAResource and update
* internal state information.
* This will release the lock while calling out.
*
* @return when started, false otherwise
*/
public boolean startResource() throws XAException {
int flags = XAResource.TMJOIN;
if (resourceSameRM == null) {
switch (resourceState) {
case RS_NEW:
flags = XAResource.TMNOFLAGS;
break;
case RS_SUSPENDED:
flags = XAResource.TMRESUME;
break;
default:
if (log.isDebugEnabled())
log.debug("Unhandled resource state: " + resourceState
+ " (not RS_NEW or RS_SUSPENDED, using TMJOIN flags)");
}
}
if (log.isDebugEnabled())
log.debug("startResource(" + xidFactory.toString(resourceXid) + ") entered: "
+ xaResource.toString() + " flags=" + flags);
unlock();
// OSH FIXME: resourceState could be incorrect during this callout.
try {
try {
xaResource.start(resourceXid, flags);
} catch (XAException e) {
throw e;
} catch (Throwable t) {
if (log.isDebugEnabled())
log.debug("unhandled throwable error in startResource", t);
status = Status.STATUS_MARKED_ROLLBACK;
return false;
}
// Now the XA resource is associated with a transaction.
resourceState = RS_ENLISTED;
} finally {
lock();
if (log.isDebugEnabled())
log.debug("startResource(" + xidFactory.toString(resourceXid) + ") leaving: "
+ xaResource.toString() + " flags=" + flags);
}
return true;
}
/**
* Delist the resource unless we already did it
*/
public boolean delistResource(XAResource xaRes, int flag) throws XAException {
if (isDelisted(xaRes)) {
// This RM always returns false on isSameRM. Further,
// the last resource has already been delisted.
log.warn("Resource already delisted. tx=" + this.toString());
return false;
}
endResource(flag);
return true;
}
/**
* End the resource
*/
public void endResource() throws XAException {
if (resourceState == RS_ENLISTED || resourceState == RS_SUSPENDED) {
if (log.isDebugEnabled())
log.debug("endresources(" + xaResource + "): state=" + resourceState);
endResource(XAResource.TMSUCCESS);
}
}
/**
* Call <code>end()</code> on the XAResource and update
* internal state information.
* This will release the lock while calling out.
*
* @param flag The flag argument for the end() call.
*/
private void endResource(int flag) throws XAException {
if (log.isDebugEnabled())
log.debug("endResource(" + xidFactory.toString(resourceXid) + ") entered: "
+ xaResource.toString() + " flag=" + flag);
unlock();
// OSH FIXME: resourceState could be incorrect during this callout.
try {
try {
xaResource.end(resourceXid, flag);
} catch (XAException e) {
throw e;
} catch (Throwable t) {
if (log.isDebugEnabled())
log.debug("unhandled throwable error in endResource", t);
status = Status.STATUS_MARKED_ROLLBACK;
// Resource may or may not be ended after illegal exception.
// We just assume it ended.
resourceState = RS_ENDED;
return;
}
// Update our internal state information
if (flag == XAResource.TMSUSPEND)
resourceState = RS_SUSPENDED;
else {
if (flag == XAResource.TMFAIL)
status = Status.STATUS_MARKED_ROLLBACK;
resourceState = RS_ENDED;
}
} finally {
lock();
if (log.isDebugEnabled())
log.debug("endResource(" + xidFactory.toString(resourceXid) + ") leaving: "
+ xaResource.toString() + " flag=" + flag);
}
}
/**
* Forget the resource
*/
public void forget() {
unlock();
try {
xaResource.forget(resourceXid);
} catch (XAException xae) {
logXAException(xae);
cause = xae;
} finally {
lock();
}
resourceState = RS_FORGOT;
}
/**
* Prepare the resource
*/
public int prepare() throws XAException {
int vote;
unlock();
try {
vote = xaResource.prepare(resourceXid);
} finally {
lock();
}
if (vote == XAResource.XA_OK)
resourceState = RS_VOTE_OK;
else if (vote == XAResource.XA_RDONLY)
resourceState = RS_VOTE_READONLY;
return resourceState;
}
/**
* Prepare the last resource
*/
public void prepareLastResource() throws XAException {
resourceState = RS_VOTE_OK;
}
/**
* Commit the resource
*/
public void commit(boolean onePhase) throws XAException {
if (log.isDebugEnabled())
log.debug("Committing resource " + xaResource + " state=" + resourceState);
if (!onePhase && resourceState != RS_VOTE_OK)
return; // Voted read-only at prepare phase.
if (resourceSameRM != null)
return; // This RM already committed.
unlock();
try {
xaResource.commit(resourceXid, onePhase);
} finally {
lock();
}
}
/**
* Rollback the resource
*/
public void rollback() throws XAException {
if (resourceState == RS_VOTE_READONLY)
return;
// Already forgotten
if (resourceState == RS_FORGOT)
return;
if (resourceSameRM != null)
return; // This RM already rolled back.
unlock();
try {
xaResource.rollback(resourceXid);
} finally {
lock();
}
}
}
}