/*
* JBoss, the OpenSource J2EE webOS
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package org.jboss.tm;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.transaction.Status;
import javax.transaction.TransactionManager;
import javax.transaction.Transaction;
import javax.transaction.NotSupportedException;
import javax.transaction.SystemException;
import javax.transaction.RollbackException;
import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.InvalidTransactionException;
import org.jboss.logging.Logger;
/**
* Our TransactionManager implementation.
*
* @author Rickard Öberg
* @author Marc Fleury
* @author Ole Husgaard
* @author Jason Dillon
* @version $Revision: 1.3.2.8 $
*/
public class TxManager
implements TransactionManager,
TransactionPropagationContextImporter,
TransactionPropagationContextFactory
{
// Constants -----------------------------------------------------
// Attributes ----------------------------------------------------
/** Instance logger. */
private Logger log = Logger.getLogger(this.getClass());
/** True if trace messages should be logged. */
private boolean trace = log.isTraceEnabled();
/**
* Default timeout in milliseconds.
* Must be >= 1000!
*/
private long timeOut = 5 * 60 * 1000;
// The following two fields are ints (not longs) because
// volatile 64Bit types are broken (i.e. access is not atomic) in most VMs, and we
// don't want to lock just for a statistic. Additionaly,
// it will take several years on a highly loaded system to
// exceed the int range. Note that we might loose an
// increment every now and then, since the ++ operation is
// not atomic on volatile data types.
/** A count of the transactions that have been committed */
private volatile int commitCount;
/** A count of the transactions that have been rolled back */
private volatile int rollbackCount;
// Static --------------------------------------------------------
/**
* The singleton instance.
*/
private static TxManager singleton = new TxManager();
/**
* Get a reference to the singleton instance.
*/
public static TxManager getInstance()
{
return singleton;
}
// Constructors --------------------------------------------------
/**
* Private constructor for singleton. Use getInstance() to obtain
* a reference to the singleton.
*/
private TxManager()
{
//make sure TxCapsule can be used
TransactionImpl.defaultXidFactory();
}
// Public --------------------------------------------------------
/**
* Begin a new transaction.
* The new transaction will be associated with the calling thread.
*/
public void begin()
throws NotSupportedException, SystemException
{
ThreadInfo ti = getThreadInfo();
TransactionImpl current = ti.tx;
if (current != null)
{
if (current.isDone())
disassociateThread(ti);
else
throw new NotSupportedException
("Transaction already active, cannot nest transactions.");
}
long timeout = (ti.timeout == 0) ? timeOut : ti.timeout;
TransactionImpl tx = new TransactionImpl(timeout);
associateThread(ti, tx);
globalIdTx.put(tx.getGlobalId(), tx);
if (trace)
log.trace("began tx: " + tx);
}
/**
* Commit the transaction associated with the currently running thread.
*/
public void commit()
throws RollbackException,
HeuristicMixedException,
HeuristicRollbackException,
SecurityException,
IllegalStateException,
SystemException
{
ThreadInfo ti = getThreadInfo();
TransactionImpl current = ti.tx;
if (current != null)
{
current.commit();
disassociateThread(ti);
if (trace)
log.trace("commited tx: " + current);
}
else
throw new IllegalStateException("No transaction.");
}
/**
* Return the status of the transaction associated with the currently
* running thread, or Status.STATUS_NO_TRANSACTION if no
* active transaction is currently associated.
*/
public int getStatus() throws SystemException
{
ThreadInfo ti = getThreadInfo();
TransactionImpl current = ti.tx;
if (current != null)
{
if (current.isDone())
disassociateThread(ti);
else
return current.getStatus();
}
return Status.STATUS_NO_TRANSACTION;
}
/**
* Return the transaction currently associated with the invoking thread,
* or null if no active transaction is currently associated.
*/
public Transaction getTransaction() throws SystemException
{
ThreadInfo ti = getThreadInfo();
TransactionImpl current = ti.tx;
if (current != null && current.isDone())
{
current = null;
disassociateThread(ti);
}
return current;
}
/**
* Resume a transaction.
*
* Note: This will not enlist any resources involved in this
* transaction. According to JTA1.0.1 specification section 3.2.3,
* that is the responsibility of the application server.
*/
public void resume(Transaction transaction)
throws InvalidTransactionException,
IllegalStateException,
SystemException
{
if (transaction != null && !(transaction instanceof TransactionImpl))
throw new RuntimeException("Not a TransactionImpl, but a " +
transaction.getClass().getName());
ThreadInfo ti = getThreadInfo();
TransactionImpl current = ti.tx;
if (current != null)
{
if (current.isDone())
current = ti.tx = null;
else
throw new IllegalStateException("Already associated with a tx");
}
if (current != transaction)
{
associateThread(ti, (TransactionImpl)transaction);
}
if (trace)
log.trace("resumed tx: " + ti.tx);
}
/**
* Suspend the transaction currently associated with the current
* thread, and return it.
*
* Note: This will not delist any resources involved in this
* transaction. According to JTA1.0.1 specification section 3.2.3,
* that is the responsibility of the application server.
*/
public Transaction suspend() throws SystemException
{
ThreadInfo ti = getThreadInfo();
TransactionImpl current = ti.tx;
if (current != null)
{
ti.tx = null;
if (trace)
log.trace("suspended tx: " + current);
if (current.isDone())
current = null;
}
return current;
}
/**
* Roll back the transaction associated with the currently running thread.
*/
public void rollback()
throws IllegalStateException, SecurityException, SystemException
{
ThreadInfo ti = getThreadInfo();
TransactionImpl current = ti.tx;
if (current != null)
{
if (!current.isDone())
{
current.rollback();
if (trace)
log.trace("rolled back tx: " + current);
return;
}
disassociateThread(ti);
}
throw new IllegalStateException("No transaction.");
}
/**
* Mark the transaction associated with the currently running thread
* so that the only possible outcome is a rollback.
*/
public void setRollbackOnly()
throws IllegalStateException, SystemException
{
ThreadInfo ti = getThreadInfo();
TransactionImpl current = ti.tx;
if (current != null)
{
if (!current.isDone())
{
current.setRollbackOnly();
if (trace)
log.trace("tx marked for rollback only: " + current);
return;
}
ti.tx = null;
}
throw new IllegalStateException("No transaction.");
}
/**
* Set the transaction timeout for new transactions started by the
* calling thread.
*/
public void setTransactionTimeout(int seconds)
throws SystemException
{
getThreadInfo().timeout = 1000 * seconds;
if (trace)
log.trace("tx timeout is now: " + seconds + "s");
}
/**
* Set the default transaction timeout for new transactions.
* This default value is used if setTransactionTimeout()
* was never called, or if it was called with a value of 0.
*/
public void setDefaultTransactionTimeout(int seconds)
{
timeOut = 1000L * seconds;
if (trace)
log.trace("default tx timeout is now: " + seconds + "s");
}
/**
* Get the default transaction timeout.
*
* @return Default transaction timeout in seconds.
*/
public int getDefaultTransactionTimeout()
{
return (int) (timeOut / 1000);
}
/**
* The following 2 methods are here to provide association and
* disassociation of the thread.
*/
public Transaction disassociateThread()
{
return disassociateThread(getThreadInfo());
}
private Transaction disassociateThread(ThreadInfo ti) {
TransactionImpl current = ti.tx;
ti.tx=null;
current.disassociateCurrentThread();
return current;
}
public void associateThread(Transaction transaction)
{
if (transaction != null && !(transaction instanceof TransactionImpl))
throw new RuntimeException("Not a TransactionImpl, but a " +
transaction.getClass().getName());
// Associate with the thread
TransactionImpl transactionImpl = (TransactionImpl) transaction;
ThreadInfo ti = getThreadInfo();
ti.tx = transactionImpl;
transactionImpl.associateCurrentThread();
}
private void associateThread(ThreadInfo ti, TransactionImpl transaction)
{
// Associate with the thread
ti.tx = transaction;
transaction.associateCurrentThread();
}
// Implements TransactionPropagationContextImporter ---------------
/**
* Import a transaction propagation context into this TM.
* The TPC is loosely typed, as we may (at a later time) want to
* import TPCs that come from other transaction domains without
* offloading the conversion to the client.
*
* @param tpc The transaction propagation context that we want to
* import into this TM. Currently this is an instance
* of GlobalId. At some later time this may be an instance
* of a transaction propagation context from another
* transaction domain like
* org.omg.CosTransactions.PropagationContext.
*
* @return A transaction representing this transaction propagation
* context, or null if this TPC cannot be imported.
*/
public Transaction importTransactionPropagationContext(Object tpc)
{
if (tpc instanceof GlobalId)
{
GlobalId id = (GlobalId) tpc;
return (Transaction) globalIdTx.get(id);
}
log.warn("Cannot import transaction propagation context: " + tpc);
return null;
}
// Implements TransactionPropagationContextFactory ---------------
/**
* Return a TPC for the current transaction.
*/
public Object getTransactionPropagationContext()
{
return getTransactionPropagationContext(getThreadInfo().tx);
}
/**
* Return a TPC for the argument transaction.
*/
public Object getTransactionPropagationContext(Transaction tx)
{
// If no transaction or unknown transaction class, return null.
if (tx == null)
return null;
if (!(tx instanceof TransactionImpl))
{
log.warn("Cannot export transaction propagation context: " + tx);
return null;
}
return ((TransactionImpl) tx).getGlobalId();
}
/**
* Return the number of active transactions
*/
public int getTransactionCount()
{
return globalIdTx.size();
}
/** A count of the transactions that have been committed */
public long getCommitCount()
{
return commitCount;
}
/** A count of the transactions that have been rolled back */
public long getRollbackCount()
{
return rollbackCount;
}
// Package protected ---------------------------------------------
/**
* Release the given TransactionImpl.
*/
void releaseTransactionImpl(TransactionImpl tx)
{
globalIdTx.remove(tx.getGlobalId());
}
/**
* Increment the commit count
*/
void incCommitCount()
{
++commitCount;
}
/**
* Increment the rollback count
*/
void incRollbackCount()
{
++rollbackCount;
}
// Protected -----------------------------------------------------
// Private -------------------------------------------------------
/**
* This keeps track of the thread association with transactions
* and timeout values.
* In some cases terminated transactions may not be cleared here.
*/
private ThreadLocal threadTx = new ThreadLocal();
/**
* This map contains the active transactions as values.
* The keys are the GlobalIds of the transactions.
*/
private Map globalIdTx = Collections.synchronizedMap(new HashMap());
/**
* Return the ThreadInfo for the calling thread, and create if not
* found.
*/
private ThreadInfo getThreadInfo()
{
ThreadInfo ret = (ThreadInfo) threadTx.get();
if (ret == null)
{
ret = new ThreadInfo();
ret.timeout = timeOut;
threadTx.set(ret);
}
return ret;
}
// Inner classes -------------------------------------------------
/**
* A simple aggregate of a thread-associated timeout value
* and a thread-associated transaction.
*/
static class ThreadInfo
{
long timeout;
TransactionImpl tx;
}
}