/*
Copyright (c) 2003-2008 ITerative Consulting Pty Ltd. All Rights Reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted
provided that the following conditions are met:
o Redistributions of source code must retain the above copyright notice, this list of conditions and
the following disclaimer.
o Redistributions in binary form must reproduce the above copyright notice, this list of conditions
and the following disclaimer in the documentation and/or other materials provided with the distribution.
o This jcTOOL Helper Class software, whether in binary or source form may not be used within,
or to derive, any other product without the specific prior written permission of the copyright holder
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package GenericDBMS;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
import org.springframework.remoting.httpinvoker.HttpInvokerClientConfiguration;
import org.springframework.remoting.httpinvoker.HttpInvokerRequestExecutor;
import org.springframework.remoting.httpinvoker.SimpleHttpInvokerRequestExecutor;
import org.springframework.remoting.support.RemoteInvocation;
import org.springframework.remoting.support.RemoteInvocationResult;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.UnexpectedRollbackException;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import Framework.AppContextHolder;
import Framework.Constants;
import Framework.ErrorMgr;
import Framework.FrameworkUtils;
import Framework.Task;
import Framework.UUIDGen;
import Framework.UsageException;
import Framework.anchored.Anchorable;
import Framework.anchored.ServiceProxy;
import Framework.remoting.AbstractPluggableBean;
import Framework.remoting.RemoteInvocationAttributeConsumer;
import Framework.remoting.RemoteInvocationAttributeProvider;
import Framework.remoting.RemoteInvocationResultWrapper;
import Framework.remoting.parameters.CopyInputOutput;
import Framework.remoting.parameters.CopyOutput;
import Framework.remoting.parameters.Input;
import Framework.remoting.parameters.InputOutput;
import Framework.remoting.parameters.Output;
/**
* Manages the demarcation of Bean managed transaction equivalent to Forte
*/
public class TransactionMgr {
/**
* A list of the transation types that can be used.
*/
public enum TransactionType {DEPENDENT, INDEPENDENT, NESTED};
/**
* This class should contain static methods, not be instantiated itself.
*/
private TransactionMgr() {}
protected static Logger _log = Logger.getLogger(TransactionMgr.class);
/**
* The transaction id of the next transaction
*/
private static int ID = 0;
/**
* Get the id of the next available transaction id.
*/
protected static synchronized int getNextTransactionId() {
return ++ID;
}
/**
* A holder to the statistics maintained by this transaction manager
*/
static TransactionMgrStats statistics = new TransactionMgrStats();
/**
* Get the statistics for this transaction manager. These statistics are all
*/
public static TransactionMgrStats getStatistics() {
return statistics.clone();
}
/**
* A class that holds the components used to maintain a transaction -- the transaction
* manager and the transactions status
* @author Tim
*
*/
public static class TransactionData {
private transient PlatformTransactionManager txnManager;
private transient TransactionStatus status;
private transient DBConnectionManager dataSource;
private int id;
private boolean rolledBack;
private TransactionType type;
private String distributedTransactionID;
private transient List<Object> distributedInvokers = null;
/**
* The invoker being used for the current distributed transaction
*/
private transient Object currentInvoker = null;
/**
* This flag indicates whether the data source was ever set on this transaction.
* This may be used in the future to add a flag to be returned from a distributed
* transaction that says whether the remote transaction partook in transactional
* activities, and hence needs to be committed or rolled back. This is a possible
* optimisation in the future.
*/
@SuppressWarnings("unused")
private boolean wasUsed = false;
public TransactionData(TransactionType pType) {
this.id = getNextTransactionId();
this.rolledBack = false;
type = pType;
}
public TransactionData(DBConnectionManager pDataSource, TransactionType pType) {
this(pType);
this.setDataSource(pDataSource);
}
public PlatformTransactionManager getManager() {
return this.txnManager;
}
public TransactionStatus getStatus() {
return this.status;
}
public boolean hasRolledBack() {
return this.rolledBack;
}
public DBConnectionManager getDataSource() {
return this.dataSource;
}
public String getDataSourceName() {
return this.dataSource == null ? null : this.dataSource.getDataSourceName();
}
public TransactionType getType() {
return type;
}
public int getId() {
return this.id;
}
/**
* This is a hang-over method from Forte, when you could get for example the transaction
* id off the transaction desc, but not off the transaction handle
* @return
*/
public TransactionData getTransactionDesc() {
return this;
}
public boolean isEqual(Object o) {
return equals(o);
}
/**
* Set the data source associated with the transaction. This will also begin the transaction. If the
* data source has already been set, this method will raise an exception
* @param pDataSource
*/
public void setDataSource(DBConnectionManager pDataSource) {
if (this.dataSource != null) {
throw new IllegalStateException("A data source has already been set on the TransactionStatus");
}
this.wasUsed = true;
// Nested transactions are not committed or rolled back until their outermost transaction is committed
// or rolled back, so in effect they have no use unless they're done at the top level, in which case
// they're effectively INDEPENDENT transactions.
//
// NB: The coding of this class supports Independent, Nested and Dependent transactions as described in the Accessing Databases
// Forte manual. However, these 3 transaction types seem to be analagous to PROPEGATION_REQUIRES_NEW, PROPEGATION_NESTED
// and PROPEGATION_REQUIRED in the Spring TransactionDefinition class. We could, therefore, implement these in this way if the
// proper behaviour (and not just a clone of the Forte behaviour) was required.
if (this.type != TransactionType.NESTED) {
this.dataSource = pDataSource;
DefaultTransactionDefinition tx = new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRED);
this.txnManager = pDataSource.getTransactionManager();
if (_log.isDebugEnabled()) {
_log.debug("Begin Transaction: " + pDataSource + ":" + tx);
}
this.status = this.txnManager.getTransaction(tx);
// If we've been rolled back ahead of time, we must flag this transaction for rollback only
if (this.rolledBack) {
this.status.setRollbackOnly();
}
}
}
public String toString() {
return type + "[" + this.dataSource + ":" + getManager() + "]";
}
}
/**
* The behaviour of transactions in Forte is a little convoluted. The successful case is easy and works as expected, but
* if an exception escapes a transaction block then the whole transaction stack is aborted (ie Transaction.isActive = false
* during the exception handler of the transaction block and afterwards).
* <br>
* Consider the following code fragment: <br>
* <pre>
* begin transaction
* begin
* begin transactaion
* -- something that throws an exception --
* end transaction
* exception
* when err : GenericException do
* -- handle exception, don't rethrow it --
* end
* begin transaction
* -- Some SQL --
* end transaction
* end transaction
* </pre>
* When the first transaction is escaped by the unhandled exception, the whole transaction stack is aborted, not just the
* inner transaction. So the second inner transaction creates a new transaction (the root of the transaction stack) rather
* than a sub-ordinate tranasaction of the outer transaction. This transaction will commit or rollback in its own right. Thus
* what seems like an inner transaction can actually commit when a prior part of the transaction has failed. Of course, if the
* thread of execution ever hits the outer end transaction an exception will be raised.
* <br>
* Note that returning from inside a transaction behaves as if it executes the end of the transactions properly, including
* throwing an exception if there has been a non-caught exception thrown from one of the inner transactions.
* <br>
* Since the transaction manager needs to handle returns from inside transactions by commiting them (or throwing an exception
* as appropriate), there must be a finally block which calls endTransction(). If the transaction stack has been aborted, then
* we need to make a special case of this endTransaction call NOT throwing a UsageException, but any other calls on the
* transaction manager or a second call to the endTransaction method throwing an exception
*/
@SuppressWarnings("serial")
private static class TransactionStack extends LightweightStack<TransactionData> {
private boolean shouldIgnoreEndTransactionFlag = false;
public void setIgnoreEndTransaction() {
shouldIgnoreEndTransactionFlag = true;
}
public boolean shouldIgnoreEndTransaction() {
if (shouldIgnoreEndTransactionFlag) {
shouldIgnoreEndTransactionFlag = false;
return true;
}
return false;
}
}
private static ThreadLocal<TransactionStack> _transactionStorage = new ThreadLocal<TransactionStack>() {
protected synchronized TransactionStack initialValue() {
return new TransactionStack();
}
};
/**
* Begin a transaction. By default, this will be a dependent transaction
*/
public static void beginTransaction() {
beginTransaction(TransactionType.DEPENDENT);
statistics.incrementBeginCount();
}
/**
* Begin a dependent transaction.
*/
public static void beginDependentTransaction() {
beginTransaction(TransactionType.DEPENDENT);
statistics.incrementBeginCount();
}
/**
* Begin a nested transaction.
*/
public static void beginNestedTransaction() {
beginTransaction(TransactionType.NESTED);
statistics.incrementBeginCount();
}
/**
* Begin an independent transaction.
*/
public static void beginIndependentTransaction() {
beginTransaction(TransactionType.INDEPENDENT);
statistics.incrementBeginCount();
}
/**
* Begin a transaction without specifying the associated data source. If the thread is currently participating
* in a transaction, the transaction will be joined again with a nested transaction on the same data source.
* If the thread is not currently within a transaction, beginning the transaction will be deferred until the
* first use of the data source associated with the transaction.
*/
public static void beginTransaction(TransactionType type) {
// Check to see if the task has been cancelled
Task.checkTaskCancelled();
// Now put the transaction definition onto the stack for this thread
LightweightStack<TransactionData> stack = _transactionStorage.get();
if (stack.isEmpty()) {
// This is a new transaction which has not previously been nested. Irrespective of the type,
// it's always created as an independent transaction
stack.push(new TransactionData(TransactionType.INDEPENDENT));
}
else if (type == TransactionType.INDEPENDENT) {
// We're trying to create an independent transaction in a thread that is already participating
// in a transaction. This is illegal in Forte, so raise an exception
UsageException ue = new UsageException("An INDEPENDENT transaction may not be nested");
ue.reasonCode = Constants.SP_ER_INVALIDSTATE;
throw ue;
}
else {
// We need to add a new item to the stack. If the outer transaction has been started, we just
// inherit it's data source. Otherwise, we defer the commencement of the transaction until
// we actually use the data source
TransactionData lastItem = stack.peek();
if (lastItem.getType() == TransactionType.NESTED && type == TransactionType.DEPENDENT) {
// We're trying to create an dependent transaction in a thread that is already participating
// in a nested transaction. This is illegal in Forte, so raise an exception
UsageException ue = new UsageException("A DEPENDENT transaction may not be started from a NESTED transaction");
ue.reasonCode = Constants.SP_ER_INVALIDSTATE;
throw ue;
}
else if (lastItem.getType() == TransactionType.DEPENDENT && type == TransactionType.NESTED) {
// We're trying to create a nested transaction in a thread that is already participating
// in a dependent transaction. This is illegal in Forte, so raise an exception
UsageException ue = new UsageException("A NESTED transaction may not be started from a DEPENDENT transaction");
ue.reasonCode = Constants.SP_ER_INVALIDSTATE;
throw ue;
}
TransactionData data;
if (lastItem.dataSource != null) {
data = new TransactionData(lastItem.getDataSource(), type);
}
else {
// There is no active transaction, push a new one onto the stack
data = new TransactionData(type);
}
if (lastItem.rolledBack) {
// If our previous transaction has been rolled back, we must roll back.
data.rolledBack = true;
}
// Now put the transaction definition onto the stack for this thread
stack.push(data);
}
}
/**
* Set the data source for any transactions active in the stack that currently don't have an associated data source
* @param pDataSource - the data source to use for the transaction. This parameter should not be <pre>null</pre>
*/
static void setTransactionalDataSource(DBConnectionManager pDataSource) {
if (pDataSource == null) {
throw new NullPointerException("pDataSource cannot be null");
}
LightweightStack<TransactionData> stack = _transactionStorage.get();
// We need to set all the datasources to the passed datasource, so we violate the integrity of the stack a little here...
for (TransactionData data : stack) {
if (data.getDataSource() == null) {
data.setDataSource(pDataSource);
}
}
}
public static void beginTransaction(String pDataSource, TransactionDefinition pTransaction) {
beginTransaction(DBConnectionManager.getInstance(pDataSource), pTransaction);
}
public static void beginTransaction(DBConnectionManager pDataSource, TransactionDefinition pTransaction) {
LightweightStack<TransactionData> stack = _transactionStorage.get();
stack.push(new TransactionData(pDataSource, TransactionType.DEPENDENT));
statistics.incrementBeginCount();
}
public static void beginTransaction(String pDataSource) {
DefaultTransactionDefinition tx = new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRED);
beginTransaction(pDataSource, tx);
}
public static void beginTransaction(DBConnectionManager pDataSource) {
DefaultTransactionDefinition tx = new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRED);
beginTransaction(pDataSource, tx);
}
/**
* Determine if there is an active transaction for the current thread.
* @return false if there is no currently active transaction for the thread.
*/
public static boolean isActive() {
// We don't really need to know if there's an active transaction or not, because we might have
// "started" a transaction, but not actually had the DataSource set. So all we do is see if the
// exception stack is empty.
LightweightStack<TransactionData> stack = _transactionStorage.get();
return !(stack.isEmpty());
}
/**
* Determine if the current transaction was aborted. If there is no active transaction, this
* method will return false.
*/
public static boolean isAborted() {
LightweightStack<TransactionData> stack = _transactionStorage.get();
if (stack.isEmpty()) {
return false;
}
return stack.get(0).rolledBack;
}
/**
* Determine if the passed user transaction is currently active or inactive
* @param pDataSource - the DataSource to examine
* @return false if the passed transaction is null, or it is not currently active.
* @deprecated use {@link #isActive()} instead
*/
public static boolean isActive(String pDataSource) {
if (pDataSource == null) {
return false;
}
else {
return isActive(DBConnectionManager.getInstance(pDataSource));
}
}
/**
* Determine if the passed user transaction is currently active or inactive
* @param pDataSource - the DataSource to examine
* @return false if the passed transaction is null, or it is not currently active.
* @deprecated use {@link #isActive()} instead
*/
public static boolean isActive(DBConnectionManager pDataSource) {
if (pDataSource == null) {
return false;
}
else {
// There is no guaranteed way of determining whether a transaction is active or not
// under spring. So we try to join the existing one, and if we can't, it's not active
// (We could inspect the transaction data stack for the thread, but this isn't guaranteed
// if the user has created transactions for themselves)
try {
PlatformTransactionManager qq_txn1 = pDataSource.getTransactionManager();
TransactionStatus aTxnStatus = qq_txn1.getTransaction(new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_MANDATORY));
qq_txn1.commit(aTxnStatus);
return true;
}
catch (Exception e) {
return false;
}
}
}
/**
* The isDependent method returns true if the current transaction is a dependent transaction;
* otherwise isDependent returns false.<p>
* <p>
* @return This method returns true only if the transaction is actually a dependent transaction. If
* the transaction was started with the beginDependent method while the task was not already
* in a transaction, this method returns false.
*/
public static boolean isDependent() {
LightweightStack<TransactionData> currentTransactions = _transactionStorage.get();
if (currentTransactions.size() > 1 && currentTransactions.get(currentTransactions.size() - 1).type == TransactionType.DEPENDENT) {
return true;
}
else {
return false;
}
}
/**
* The isNested method returns true if the current transaction is a nested transaction;
* otherwise isDependent returns false.<p>
* <p>
* @return This method returns true only if the transaction is actually a nested transaction. If
* the transaction was started with the beginNested method while the task was not already
* in a transaction, this method returns false.
*/
public static boolean isNested() {
LightweightStack<TransactionData> currentTransactions = _transactionStorage.get();
if (currentTransactions.size() > 1 && currentTransactions.get(currentTransactions.size() - 1).type == TransactionType.NESTED) {
return true;
}
else {
return false;
}
}
/**
* returns the transaction ID for the current transaction
*/
public static int getTransactionID() {
LightweightStack<TransactionData> currentTransactions = _transactionStorage.get();
if (currentTransactions.empty()) {
throw new UsageException("getTransactionID called when not in a transaction");
}
TransactionData transactionData = (TransactionData)currentTransactions.peek();
return transactionData.id;
}
/**
* Abort the current transaction and all transactions within the transaction stack. This method differs
* to the normal {@link #abort()} method in that if the next call to the TransactionMgr from this thread
* is an endTransaction(), then this endTransaction call is ignored. Also, this method can be safely
* called even when not in a transaction, however there is no effect in this case.
* @see #abort()
*/
public static void abortWithinTransactionBlock() {
TransactionStack currentTransactions = _transactionStorage.get();
currentTransactions.setIgnoreEndTransaction();
// Since transactions may be nested, and an exception propegated from the inner one to the outer ones,
// it's necessary to see if there's any work to do prior to calling abort (as aborting the inner one
// will have cleared the error stack, so there's nothing for the abort on the outer one to do)
if (!currentTransactions.isEmpty()) {
abort();
}
}
/**
* Flag the current transaction to only be able to be rolled back. If commitTransaction() is
* invoked after this method is called for any transaction in the current transaction stack,
* the transaction will still be rolled back.
* @deprecated this method should not be needed anymore, and its implementation has been changed to that
* of {@link #abortWithinTransactionBlock()} to provide the same functionality as Forte
*/
public static void setRollback() {
abortWithinTransactionBlock();
/*
LightweightStack<TransactionData> currentTransactions = _transactionStorage.get();
if (currentTransactions.empty()) {
throw new UsageException("setRollback called when not in a transaction");
}
TransactionData transactionData = (TransactionData)currentTransactions.peek();
// NESTED transactions get whether to commit or rollback from their outermost transaction
// so ignore them unless they are the only transaction on the stack (in which case they
// should be flagged as INDEPENDENT transaction, but it doesn't hurt to check)
if (transactionData.type != TransactionType.NESTED || currentTransactions.size() == 1) {
TransactionStatus currentStatus = transactionData.getStatus();
if (currentStatus != null) {
_log.debug("Marking for rollback: " + currentStatus.toString());
currentStatus.setRollbackOnly();
}
// We must flag all transactions to be rolled back. This is necessary because there is
// no guarantee we have actually started the transaction, so we may not be able to mark
// it as being only able to be rolled back.
for (TransactionData data : currentTransactions ) {
data.rolledBack = true;
}
}
*/
}
/**
* Rollback the current transaction. Do not call this method if calling commit() threw an
* exception -- the transaction will have already been rolled back. This method will rarely
* be used by client code -- they will normally use {@link #endTransaction()} which will
* automatically invoke either this method or commitTransaction as appropriate
*/
public static void rollbackTransaction() {
// Check to see if the task has been cancelled
Task.checkTaskCancelled();
LightweightStack<TransactionData> currentTransactions = _transactionStorage.get();
if (currentTransactions.empty()) {
throw new UsageException("rollback called when not in a transaction");
}
TransactionData transactionData = (TransactionData)currentTransactions.pop();
// NESTED transactions get whether to commit or rollback from their outermost transaction
// so ignore them unless they are the only transaction on the stack (in which case they
// should be flagged as INDEPENDENT transaction, but it doesn't hurt to check)
// TF:27/04/2008:Changed the check of the size to 0, as we've popped the top of the stack
if (transactionData.type != TransactionType.NESTED || currentTransactions.size() == 0) {
TransactionStatus currentStatus = transactionData.getStatus();
PlatformTransactionManager aTxn = transactionData.getManager();
if (currentStatus != null && aTxn != null) {
_log.debug("Rollback Transaction: " + transactionData);
aTxn.rollback(currentStatus);
}
}
// We must flag all transactions to be rolled back. This is necessary because there is
// no guarantee we have actually started the transaction, so we may not be able to mark
// it as being only able to be rolled back.
for (TransactionData data : currentTransactions ) {
data.rolledBack = true;
}
completeRemoteTransaction(currentTransactions, transactionData, false);
statistics.incrementRollbackCount();
// if (currentTransactions.empty()) {
// _log.debug("RollBack Transaction: Releasing connection");
// DBConnectionManager dcm = DBConnectionManager.getInstance(transactionData.getDataSource());
// Connection con = dcm.getConnection();
// DataSourceUtils.releaseConnection(con, dcm.getDataSource());
//
// }
}
/**
* Flag the current transaction to only be able to be rolled back. If commitTransaction() is
* invoked after this method is called, the transaction will still be rolled back.
*/
public static void forceRollbackOnly() {
// TF:24/3/08:This method doesn't cater correctly for rolling back a nested transaction when then
// outer transaction is going to commit, whereas setRollback() does. This method should be deprecated
// and setRollback used in preference to it.
setRollback();
// LightweightStack<TransactionData> currentTransactions = _transactionStorage.get();
// if (currentTransactions.empty()) {
// throw new UsageException("rollback called when not in a transaction");
// }
// // We must flag all transactions to be rolled back. This is necessary because there is
// // no guarantee we have actually started the transaction, so we may not be able to mark
// // it as being only able to be rolled back.
// for (TransactionData data : currentTransactions ) {
// data.rolledBack = true;
// }
// TF:24/2/08:We haven't actually rolled this transaction back yet, so we can't flag it.
// statistics.incrementRollbackCount();
// if (currentTransactions.empty()) {
// _log.debug("RollBack Transaction: Releasing connection");
// DBConnectionManager dcm = DBConnectionManager.getInstance(transactionData.getDataSource());
// Connection con = dcm.getConnection();
// DataSourceUtils.releaseConnection(con, dcm.getDataSource());
//
// }
}
/**
* Commits the current transaction. Most client code will not call this method directly, as they
* will use endTransaction in preference to this method.
*/
public static void commitTransaction() {
// Check to see if the task has been cancelled
Task.checkTaskCancelled();
LightweightStack<TransactionData> currentTransactions = _transactionStorage.get();
if (currentTransactions.empty()) {
throw new UsageException("commit called when not in a transaction");
}
TransactionData transactionData = (TransactionData)currentTransactions.pop();
// NESTED transactions get whether to commit or rollback from their outermost transaction
// so ignore them unless they are the only transaction on the stack (in which case they
// should be flagged as INDEPENDENT transaction, but it doesn't hurt to check)
// TF:27/04/2008:Changed the check of the size to 0, as we've popped the top of the stack
if (transactionData.type != TransactionType.NESTED || currentTransactions.size() == 0) {
TransactionStatus currentStatus = transactionData.getStatus();
PlatformTransactionManager aTxn = transactionData.getManager();
if (aTxn != null && currentStatus != null) {
if (transactionData.rolledBack) {
// Something has previously rolled back, forcing this transaction to rollback
if (_log.isDebugEnabled()) {
_log.debug("Commit Transaction (will rollback): " + transactionData);
}
aTxn.rollback(currentStatus);
statistics.incrementRollbackCount();
}
else {
if (_log.isDebugEnabled()) {
_log.debug("Commit Transaction: " + transactionData);
}
aTxn.commit(currentStatus);
statistics.incrementCommitCount();
}
}
else {
statistics.incrementCommitCount();
}
}
else {
statistics.incrementCommitCount();
}
// NB: We do NOT attempt to do 2-phase commits here -- if the server transaction should fail
// we will become inconsistent, as we've already commited our transaction!
completeRemoteTransaction(currentTransactions, transactionData, !transactionData.rolledBack);
// if (currentTransactions.empty()) {
// _log.debug("Commit Transaction: Releasing connection");
// DBConnectionManager dcm = DBConnectionManager.getInstance(transactionData.getDataSource());
// Connection con = dcm.getConnection();
// DataSourceUtils.releaseConnection(con, dcm.getDataSource());
// }
}
/**
* End the current transaction, committing or rolling back the data as appropriate. Use {@link #setRollback()}
* to mark a transaction as being aborted, and then use this method to explicitly end the transaction.
*/
public static void endTransaction() {
TransactionStack currentTransactions = _transactionStorage.get();
if (currentTransactions.shouldIgnoreEndTransaction()) {
return;
}
if (currentTransactions.empty()) {
throw new UsageException("endTransaction called when not in a transaction");
}
TransactionData transactionData = (TransactionData)currentTransactions.peek();
if (transactionData.rolledBack) {
rollbackTransaction();
}
else {
commitTransaction();
}
}
/**
* The getOuterTransactionDesc() method provides access to the outermost transaction running for the current transaction.
* @return If no transaction is in process, the return value is null.
*/
public static TransactionData getOuterTransactionDesc() {
LightweightStack<TransactionData> currentTransactions = _transactionStorage.get();
if (currentTransactions.empty()) {
return null;
}
TransactionData transactionData = currentTransactions.get(0);
_log.debug("Outer Transaction: " + transactionData);
return transactionData;
}
/**
* The AbortNested method aborts the innermost nested transaction.
* <p>
* When you invoke the <code>abortNested</code> method, all work associated with the nested transaction is rolled back.
* Aborting a nested transaction has no effect on its outermost transaction. If the transaction is not nested,
* abortNested is equivalent to abort.
* <p>
* <b>Raising an exception</b>
* <p>
* If you wish to raise an exception when the transaction is aborted, set the value of the pRaiseException parameter to true.
* The default exception for the abortNested method is an UnexpectedRollbackException. If you wish
* to raise a different exception, you can use the error parameter.
* <p>
* If you invoke the abortNested method without raising an exception from within a begin nested transaction block you will
* cause a runtime error when you reach the final end transaction statement.
* <p>
* @param pRaiseException
* @param pError
*/
public static void abortNested(boolean pRaiseException, RuntimeException pError) {
LightweightStack<TransactionData> currentTransactions = _transactionStorage.get();
if (currentTransactions.empty()) {
throw new UsageException("endTransaction called when not in a transaction");
}
TransactionData transactionData = (TransactionData)currentTransactions.peek();
if (transactionData.getType() == TransactionType.NESTED) {
// A nested transaction either commits or rolls back based on the scope of the
// outer transaction. So it doesn't really matter if we commit it or roll it back
// here, the net result is the same. Just remove the transaction from the stack.
currentTransactions.pop();
if (pRaiseException) {
if (!ErrorMgr.isListed(pError)) {
ErrorMgr.addError(pError);
}
throw pError;
}
}
else {
// If we're not nested, this method is equivalent to Abort
TransactionMgr.abort(pRaiseException, pError);
}
}
/**
* The AbortNested method aborts the innermost nested transaction.
* <p>
* When you invoke the <code>abortNested</code> method, all work associated with the nested transaction is rolled back.
* Aborting a nested transaction has no effect on its outermost transaction. If the transaction is not nested,
* abortNested is equivalent to abort.
* <p>
* <b>Raising an exception</b>
* <p>
* If you wish to raise an exception when the transaction is aborted, set the value of the pRaiseException parameter to true.
* This will throw an UnexpectedRollbackException.
* <p>
* If you invoke the abortNested method without raising an exception from within a begin nested transaction block you will
* cause a runtime error when you reach the final end transaction statement.
* <p>
* @param pRaiseException
*/
public static void abortNested(boolean pRaiseException) {
TransactionMgr.abortNested(pRaiseException, new UnexpectedRollbackException("TransactionMgr.abortNested invoked"));
}
/**
* abort the outermost transaction.
* @deprecated the pError parameter is ignored in this overload, use abortNested() instead
*/
public static void abortNested(RuntimeException pError) {
abortNested();
}
/**
* The AbortNested method aborts the innermost nested transaction.
* <p>
* When you invoke the <code>abortNested</code> method, all work associated with the nested transaction is rolled back.
* Aborting a nested transaction has no effect on its outermost transaction. If the transaction is not nested,
* abortNested is equivalent to abort.
* <p>
*/
public static void abortNested() {
TransactionMgr.abortNested(false, null);
}
/**
* The Abort method aborts the outermost nested transaction.
* <p>
* When you invoke the <code>abort</code> method, all work associated with the outer transaction is rolled back.
* If you are running in a nested or dependent transaction, all nesting levels are aborted.
* <p>
* <b>Raising an exception</b>
* <p>
* If you wish to raise an exception when the transaction is aborted, set the value of the pRaiseException parameter to true.
* The default exception for the abort method is an UnexpectedRollbackException. If you wish
* to raise a different exception, you can use the error parameter.
* <p>
* If you invoke the abort method without raising an exception from within a begin nested transaction block you will
* cause a runtime error when you reach the final end transaction statement.
* <p>
* @param pRaiseException
* @param pError
*/
public static void abort(boolean pRaiseException, RuntimeException pError) {
LightweightStack<TransactionData> currentTransactions = _transactionStorage.get();
if (currentTransactions.empty()) {
throw new UsageException("abort called when not in a transaction");
}
while (!currentTransactions.isEmpty()) {
TransactionMgr.rollbackTransaction();
}
// TF:27/04/2008:Also abort any distributed transactions we're partaking in.
if (!currentTransactions.isEmpty()) {
abortRemoteTransaction(currentTransactions.get(0));
}
if (pRaiseException) {
if (!ErrorMgr.isListed(pError)) {
ErrorMgr.addError(pError);
}
throw pError;
}
}
/**
* abort the outermost transaction.
* @deprecated the pError parameter is ignored in this overload, use abort instead
*/
public static void abort(RuntimeException pError) {
abort();
}
/**
* The Abort method aborts the outermost nested transaction.
* <p>
* When you invoke the <code>abort</code> method, all work associated with the outer transaction is rolled back.
* If you are running in a nested or dependent transaction, all nesting levels are aborted.
* <p>
* <b>Raising an exception</b>
* <p>
* If you wish to raise an exception when the transaction is aborted, set the value of the pRaiseException parameter to true.
* This will raise an UnexpectedRollbackException.
* <p>
* If you invoke the abort method without raising an exception from within a begin nested transaction block you will
* cause a runtime error when you reach the final end transaction statement.
* <p>
* @param pRaiseException
*/
public static void abort(boolean pRaiseException) {
TransactionMgr.abort(pRaiseException, new UnexpectedRollbackException("TransactionMgr.abort invoked"));
}
/**
* The Abort method aborts the outermost nested transaction.
* <p>
* When you invoke the <code>abort</code> method, all work associated with the outer transaction is rolled back.
* If you are running in a nested or dependent transaction, all nesting levels are aborted.
*/
public static void abort() {
TransactionMgr.abort(false, null);
}
/**
* The commit method commits the transaction and all its nested and dependent transactions. This method is equivalent
* to the forte method Transaction.commit, but not the <code>end transaction</code> statement, which just commits
* a single transaction
*/
public static void commit() {
LightweightStack<TransactionData> currentTransactions = _transactionStorage.get();
if (currentTransactions.empty()) {
throw new UsageException("commit called when not in a transaction");
}
while (!currentTransactions.isEmpty()) {
TransactionMgr.endTransaction();
}
// There's no need to complete the remote transactions, this should have been taken
// care of in endTransaction()
}
/**
* The commitNested method commits the innermost nested transaction. If the innermost transaction
* is not a nested transaction, this method behaves the same as calling {@link #commit()}.
* Use the commitNested method for a transaction that was started with the beginNested method.
* Although the work is not actually committed until the calling transaction commits, you should
* use this method in case the transaction was actually started as a independent transaction. This
* happens when the task was not already in transaction at the time you gave the beginNested method.
* In this case, the commitNested method is equivalent to commit.
*/
public static void commitNested() {
LightweightStack<TransactionData> currentTransactions = _transactionStorage.get();
if (currentTransactions.empty()) {
throw new UsageException("commitNested called when not in a transaction");
}
TransactionData transactionData = (TransactionData)currentTransactions.peek();
if (transactionData.getType() == TransactionType.NESTED) {
// A nested transaction either commits or rolls back based on the scope of the
// outer transaction. So it doesn't really matter if we commit it or roll it back
// here, the net result is the same. Just remove the transaction from the stack.
currentTransactions.pop();
}
else {
// If we're not nested, this method is equivalent to commit()
TransactionMgr.commit();
}
}
/**
* The commitDependent method commits the innermost nested transaction. If the innermost transaction
* is not a dependent transaction, this method behaves the same as calling {@link #commit()}.
* Use the commitDependent method for a transaction that was started with the beginDependent method.
* Although the work is not actually committed until the calling transaction commits, you should
* use this method in case the transaction was actually started as a independent transaction. This
* happens when the task was not already in transaction at the time you gave the beginDependent method.
* In this case, the commitDependent method is equivalent to commit.
*/
public static void commitDependent() {
LightweightStack<TransactionData> currentTransactions = _transactionStorage.get();
if (currentTransactions.empty()) {
throw new UsageException("commitNested called when not in a transaction");
}
TransactionData transactionData = (TransactionData)currentTransactions.peek();
if (transactionData.getType() == TransactionType.DEPENDENT) {
// Use endTransaction to do the work for us, we know the state is consistent
TransactionMgr.endTransaction();
}
else {
// If we're not dependent, this method is equivalent to commit()
TransactionMgr.commit();
}
}
private static final String TXN_ID = "qq_DistTxnId";
private static final String COMMIT_TXN = "TransactionMgr.commit";
private static final String ROLLBACK_TXN = "TransactionMgr.rollback";
private static final String ABORT_TXN = "TransactionMgr.abort";
private static final String WAS_ABORTED = "qq_wasAborted";
private static void preProcessAttributeConsumers(AbstractPluggableBean bean, RemoteInvocation invocation) {
// Attribute consumers
for (RemoteInvocationAttributeConsumer attributeConsumer : bean.getRemoteInvocationAttributeConsumers()) { //JVM 1.5
attributeConsumer.consume(invocation.getArguments(), invocation.getAttribute(attributeConsumer.getAttributeName()));
}
}
private static void postProcessAttributeProviders(RemoteInvocation invocation, RemoteInvocationResultWrapper result, AbstractPluggableBean bean, Object targetObject) {
// Attribute Providers
for (RemoteInvocationAttributeProvider attributeProvider : bean.getRemoteInvocationAttributeProviders()) {
result.addAttribute(attributeProvider.getAttributeName(),
attributeProvider.getAttributeValue(invocation, targetObject));
}
// We also need to add an attribute which says if the distributed transaction
// was aborted or not. If it was, then we can kill this thread after we've returned
ErrorMgr.clear();
Task.clearThreadData();
}
private static void postProcessParameters(AppContextHolder response, Method invokedMethod, Object[] args) {
// Now, for any input, output, etc annotated parameters, copy their values back
Annotation[][] parameterAnnotations = invokedMethod.getParameterAnnotations();
for (int parameterIndex = 0; parameterIndex < parameterAnnotations.length; parameterIndex++) {
if (parameterAnnotations[parameterIndex] == null) continue;
for (int annotationIndex = 0; annotationIndex < parameterAnnotations[parameterIndex].length; annotationIndex++) {
/*
* if we wanted to have other annotations line Input or CopyInput we would
* change the following test for instanceof Input, etc
*/
if ((parameterAnnotations[parameterIndex][annotationIndex] instanceof Input)||
(parameterAnnotations[parameterIndex][annotationIndex] instanceof Output)||
(parameterAnnotations[parameterIndex][annotationIndex] instanceof InputOutput)||
(parameterAnnotations[parameterIndex][annotationIndex] instanceof CopyOutput)||
(parameterAnnotations[parameterIndex][annotationIndex] instanceof CopyInputOutput)){
response.setParameter("arg" + parameterIndex, args[parameterIndex]);
}
}
}
}
private static class ManagedThread extends Thread{
public RemoteInvocation invocation;
public Object targetObject;
public volatile boolean isBusy = true;
public Exception exception;
public boolean wasAborted;
RemoteInvocationResultWrapper result;
AbstractPluggableBean bean;
public String txnId;
// Attributes to be used for service object proxy calls
private AppContextHolder response;
private Map<String, Object> appContext;
private String proxyMethodName;
private Method invokedMethod;
private Object[] args;
private Object wrappedObject;
public ManagedThread(String name) {
super(name);
this.setDaemon(false);
}
public void initialiseForCall(Map<String, Object> appContext, String proxyMethodName,
Method invokedMethod, Object[] args, Object wrappedObject, String txnId) {
this.appContext = appContext;
this.proxyMethodName = proxyMethodName;
this.invokedMethod = invokedMethod;
this.args = args;
this.wrappedObject = wrappedObject;
this.txnId = txnId;
// Clear out the other part of the local variables to avoid executing the wrong part (ie
// the spring server stuff instead of the anchored object stuff)
this.invocation = null;
this.targetObject = null;
this.bean = null;
}
public void initialiseForCall(RemoteInvocation invocation, Object targetObject, String txnId, AbstractPluggableBean bean) {
this.invocation = invocation;
this.targetObject = targetObject;
this.txnId = txnId;
this.bean = bean;
// Clear out the other part of the local variables to avoid executing the wrong part (ie
// the anchored object stuff instead of the spring server stuff)
this.appContext = null;
this.proxyMethodName = null;
this.invokedMethod = null;
this.args = null;
this.wrappedObject = null;
}
/**
* This method is invoked from a thread when it is time for this managed thread to do it's work. initialiseForCall
* must have been invoked prior to this method.
*/
public void executeCall() {
synchronized(this) {
this.notifyAll();
try {
this.wait();
}
catch (InterruptedException e) {
}
}
}
public synchronized void run() {
// Begin a transaction on the new thread. The corresponding commit
// or rollback will be issued from the remote process
beginTransaction();
isBusy = false;
boolean terminateThread = false;
while (!terminateThread) {
try {
this.wait();
isBusy = true;
result = null;
response = null;
exception = null;
wasAborted = false;
try {
if (invocation != null) {
String methodName = invocation.getMethodName();
preProcessAttributeConsumers(bean, invocation);
if (methodName.equals(COMMIT_TXN)) {
TransactionMgr.commitTransaction();
result = new RemoteInvocationResultWrapper("Commit successful");
terminateThread = true;
}
else if (methodName.equals(ROLLBACK_TXN)) {
TransactionMgr.rollbackTransaction();
result = new RemoteInvocationResultWrapper("Rollback successful");
terminateThread = true;
}
else if (methodName.equals(ABORT_TXN)) {
TransactionMgr.abort();
result = new RemoteInvocationResultWrapper("Abort successful");
terminateThread = true;
}
else {
result = new RemoteInvocationResultWrapper(invocation.invoke(targetObject));
}
wasAborted = TransactionMgr.isAborted();
postProcessAttributeProviders(invocation, result, bean, targetObject);
// We also need to add an attribute which says if the distributed transaction
// was aborted or not. If it was, then we can kill this thread after we've returned
result.addAttribute(WAS_ABORTED, Boolean.valueOf(wasAborted));
}
else {
// This must be using a service proxy
FrameworkUtils.setAppContextValues(appContext);
if (proxyMethodName.equals(COMMIT_TXN)) {
TransactionMgr.commitTransaction();
response = new AppContextHolder("Commit successful");
terminateThread = true;
}
else if (proxyMethodName.equals(ROLLBACK_TXN)) {
TransactionMgr.rollbackTransaction();
response = new AppContextHolder("Rollback successful");
terminateThread = true;
}
else if (proxyMethodName.equals(ABORT_TXN)) {
TransactionMgr.abort();
response = new AppContextHolder("Abort successful");
terminateThread = true;
}
else {
response = new AppContextHolder(invokedMethod.invoke(wrappedObject, args));
postProcessParameters(response, invokedMethod, args);
}
wasAborted = TransactionMgr.isAborted();
}
}
catch (Exception e) {
exception = e;
wasAborted = TransactionMgr.isAborted();
ErrorMgr.clear();
Task.clearThreadData();
}
// We must also remove this thread from the set of distributed transactions
if (terminateThread) {
TransactionMgr.threadTable.remove(txnId);
break;
}
this.isBusy = false;
this.notifyAll();
}
catch (InterruptedException e) {
// The thread is being kill, just die nicely
break;
}
}
}
}
@SuppressWarnings("serial")
private static class AttributedException implements Serializable {
Exception exception;
boolean wasAborted;
public AttributedException(Exception exception, boolean wasAborted) {
super();
this.exception = exception;
this.wasAborted = wasAborted;
}
}
private static ManagedThread getThreadForTxn(String txnId) {
ManagedThread thread = threadTable.get(txnId);
if (thread == null) {
thread = new ManagedThread("TxnMgr-" + txnId);
thread.start();
threadTable.put(txnId, thread);
}
while (thread.isBusy) {
try {
Thread.sleep(50);
}
catch (InterruptedException e) {
break;
}
}
return thread;
}
private static Hashtable<String, ManagedThread> threadTable = new Hashtable<String, ManagedThread>();
/**
* The beginRemoteCall method is invoked when a thread is leaving the JVM for another JVM.
* This will perform actions needed to allow distributed transactions to work across process
* boundaries.
* <p>
* This overload will be called from standard services objects, ie not anchored objects. We will
* place desired attributes for the server onto the attributes of the remote call
* <p>
* @param remoteMechanism
* @return
*/
public static void beginRemoteCall(HttpInvokerClientConfiguration remoteMechanism, RemoteInvocation invocation) {
LightweightStack<TransactionData> stack = _transactionStorage.get();
if (!stack.isEmpty()) {
// We are in the middle of a transaction, and we're about to cross a process boundary
TransactionData data = stack.get(0);
if (data.distributedTransactionID == null) {
// This is the first time the transaction has cross a process boundary, allocate an id
UUIDGen gen = new UUIDGen();
gen.generate();
data.distributedTransactionID = gen.asCharPtr();
}
// Add this key to the list of invokers if not already there
data.currentInvoker = remoteMechanism;
if (data.distributedInvokers != null) {
boolean found = false;
for (Object invoker : data.distributedInvokers) {
if (invoker instanceof HttpInvokerClientConfiguration && ((HttpInvokerClientConfiguration)invoker).getServiceUrl().equals(remoteMechanism.getServiceUrl())) {
found = true;
break;
}
}
if (!found) {
data.distributedInvokers.add(remoteMechanism);
}
}
else {
data.distributedInvokers = new ArrayList<Object>();
data.distributedInvokers.add(remoteMechanism);
}
invocation.addAttribute(TXN_ID, data.distributedTransactionID);
}
}
/**
* The beginRemoteCall method is invoked when a thread is leaving the JVM for another JVM.
* This will perform actions needed to allow distributed transactions to work across process
* boundaries.
* <p>
* This overload will be called from anchored objects, that is where one class is a proxy to
* another class in a remote JVM. In this case we will simply add our parameters into the standard
* app context hash table (rather than changing method signatures)
* <p>
* @param remoteMechanism
* @return
*/
public static void beginRemoteCall(ServiceProxy remoteMechanism, Map<String, Object> parameters) {
LightweightStack<TransactionData> stack = _transactionStorage.get();
if (!stack.isEmpty()) {
// We are in the middle of a transaction, and we're about to cross a process boundary
TransactionData data = stack.get(0);
if (data.distributedTransactionID == null) {
// This is the first time the transaction has cross a process boundary, allocate an id
UUIDGen gen = new UUIDGen();
gen.generate();
data.distributedTransactionID = gen.asCharPtr();
}
// Add this key to the list of invokers if not already there
data.currentInvoker = remoteMechanism;
if (data.distributedInvokers != null) {
boolean found = false;
for (Object invoker : data.distributedInvokers) {
if (invoker instanceof ServiceProxy && ((ServiceProxy)invoker).getServiceUrl().equals(remoteMechanism.getServiceUrl())) {
found = true;
break;
}
}
if (!found) {
data.distributedInvokers.add(remoteMechanism);
}
}
else {
data.distributedInvokers = new ArrayList<Object>();
data.distributedInvokers.add(remoteMechanism);
}
parameters.put(TXN_ID, data.distributedTransactionID);
}
}
/**
* End the remote call, leaving the state of the server transaction active
* @param result
* @return
*/
public static RemoteInvocationResult endRemoteCall(RemoteInvocationResult result) {
if (result.getValue() instanceof AttributedException) {
AttributedException e = (AttributedException)result.getValue();
LightweightStack<TransactionData> currentTransactions = _transactionStorage.get();
if (e.wasAborted) {
// The remote transaction will have already rolled back, so don't bother with it
if (!currentTransactions.empty()) {
TransactionMgr.abortRemoteTransaction(currentTransactions.get(0));
}
TransactionMgr.abort();
}
if (!currentTransactions.empty()) {
currentTransactions.get(0).currentInvoker = null;
}
return new RemoteInvocationResult(e.exception);
}
else if (result.getValue() instanceof RemoteInvocationResultWrapper) {
RemoteInvocationResultWrapper wrapper = (RemoteInvocationResultWrapper)result.getValue();
Boolean wasAborted = (Boolean)wrapper.getAttribute(WAS_ABORTED);
LightweightStack<TransactionData> currentTransactions = _transactionStorage.get();
if (wasAborted != null && wasAborted.booleanValue()) {
// The remote transaction will have already rolled back, so don't bother with it
if (!currentTransactions.empty()) {
TransactionMgr.abortRemoteTransaction(currentTransactions.get(0));
}
TransactionMgr.abort();
}
if (!currentTransactions.empty()) {
currentTransactions.get(0).currentInvoker = null;
}
}
return result;
}
/**
* End the remote call, leaving the state of the server transaction active. This overload
* is to be called from anchored objects, rather than service object proxies
* @param result
* @return
*/
public static AppContextHolder endRemoteCall(AppContextHolder result) {
LightweightStack<TransactionData> currentTransactions = _transactionStorage.get();
if (!currentTransactions.empty()) {
Object abortedFlag = result.getParameter(WAS_ABORTED);
if (abortedFlag instanceof Boolean && ((Boolean)abortedFlag).booleanValue()) {
TransactionMgr.abortRemoteTransaction(currentTransactions.get(0));
TransactionMgr.abort();
}
currentTransactions.get(0).currentInvoker = null;
}
return result;
}
/**
* Perform a server call, managing the transactions properly. This method is public only as an implementation consequence
* and should not be invoked by normal code.
*/
public static Object performServerCall(RemoteInvocation invocation, Object targetObject, AbstractPluggableBean bean)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
_log.debug("Invoking: " + invocation.getMethodName());
String txnId = (String)invocation.getAttribute(TXN_ID);
Object result;
if (txnId != null) {
// We need to see if there's a transaction id, and if there is then get the thread that owns this id and then set up the transaction data.
ManagedThread thread = getThreadForTxn(txnId);
// This is in a distributed transaction, we must use the other thread (which must be waiting)
thread.initialiseForCall(invocation, targetObject, txnId, bean);
thread.executeCall();
if (thread.exception != null) {
result = new AttributedException(thread.exception, thread.wasAborted);
}
else {
result = thread.result;
}
}
else {
preProcessAttributeConsumers(bean, invocation);
RemoteInvocationResultWrapper wrapper = new RemoteInvocationResultWrapper(invocation.invoke(targetObject));
result = wrapper;
postProcessAttributeProviders(invocation, wrapper, bean, targetObject);
}
return result;
}
/**
* Perform a server call, managing the transactions properly. This method is public only as an implementation consequence
* and should not be invoked by normal code.
*/
public static AppContextHolder performServerCall(Anchorable wrappedObject, String methodName, Method invokedMethod, Object[] args, Map<String, Object>appContext) throws Throwable {
_log.debug("Invoking: " + methodName);
String txnId = (String)appContext.get(TXN_ID);
AppContextHolder response;
if (txnId != null) {
appContext.remove(TXN_ID);
// We need to see if there's a transaction id, and if there is then get the thread that owns this id and then set up the transaction data.
ManagedThread thread = getThreadForTxn(txnId);
// This is in a distributed transaction, we must use the other thread (which must be waiting)
thread.initialiseForCall(appContext, methodName, invokedMethod, args, wrappedObject, txnId);
thread.executeCall();
if (thread.exception != null) {
response = new AppContextHolder(new AttributedException(thread.exception, thread.wasAborted));
}
else {
response = thread.response;
}
response.setParameter(WAS_ABORTED, Boolean.valueOf(thread.wasAborted));
}
else {
FrameworkUtils.setAppContextValues(appContext);
Object result = invokedMethod.invoke(wrappedObject, args);
response = new AppContextHolder(result);
postProcessParameters(response, invokedMethod, args);
}
return response;
}
/**
* If this transaction is the last transaction in the transaction stack, we must tell any remote
* transaction that they are either to commit or rollback. If this transaction is not the last one
* in the stack, this method has no effect.
* @param currentTransactions
* @param transactionData
* @param commit
*/
private static void completeRemoteTransaction(LightweightStack<TransactionData> currentTransactions, TransactionData transactionData, boolean commit) {
if (currentTransactions.isEmpty() &&
transactionData.distributedTransactionID != null &&
transactionData.distributedInvokers != null) {
// This transaction started another remote transaction, we need to send
// the remote transaction a message to rollback.
String action = commit ? TransactionMgr.COMMIT_TXN : TransactionMgr.ROLLBACK_TXN;
for (Object invokerObject : transactionData.distributedInvokers) {
if (invokerObject instanceof HttpInvokerClientConfiguration) {
HttpInvokerClientConfiguration invoker = (HttpInvokerClientConfiguration)invokerObject;
HttpInvokerRequestExecutor nestedHttpInvokerRequestExecutor = new SimpleHttpInvokerRequestExecutor();
RemoteInvocation call = new RemoteInvocation(action, new Class<?>[0], new Object[0]);
call.addAttribute(TXN_ID, transactionData.distributedTransactionID);
try {
nestedHttpInvokerRequestExecutor.executeRequest(invoker, call);
}
catch (Exception e) {
_log.error("Completing distributed transaction failed", e);
}
}
else if (invokerObject instanceof ServiceProxy) {
ServiceProxy invoker = (ServiceProxy)invokerObject;
FrameworkUtils.setAppContext(TXN_ID, transactionData.distributedTransactionID);
invoker.intercept(action, (Object[])null);
FrameworkUtils.setAppContext(TXN_ID, null);
}
}
}
}
/**
* If the transaction was aborted on the client side, we need to tell any remote servers to abort
* their transactions too
*/
private static void abortRemoteTransaction(TransactionData rootData) {
if (rootData.distributedTransactionID != null && rootData.distributedInvokers != null) {
for (Object invokerObject : rootData.distributedInvokers) {
// If we're the current invoker, then this must have been caused by the remote invocation
// to which current invoker points aborting the transaction, in which case we don't neeed
// to re-tell it to abort the transaction
if (invokerObject != rootData.currentInvoker) {
if (invokerObject instanceof HttpInvokerClientConfiguration) {
HttpInvokerClientConfiguration invoker = (HttpInvokerClientConfiguration)invokerObject;
HttpInvokerRequestExecutor nestedHttpInvokerRequestExecutor = new SimpleHttpInvokerRequestExecutor();
RemoteInvocation call = new RemoteInvocation(ABORT_TXN, new Class<?>[0], new Object[0]);
call.addAttribute(TXN_ID, rootData.distributedTransactionID);
try {
nestedHttpInvokerRequestExecutor.executeRequest(invoker, call);
}
catch (Exception e) {
_log.error("Aborting distributed transaction failed", e);
}
}
else if (invokerObject instanceof ServiceProxy) {
ServiceProxy invoker = (ServiceProxy)invokerObject;
FrameworkUtils.setAppContext(TXN_ID, rootData.distributedTransactionID);
try {
invoker.intercept(ABORT_TXN, (Object[])null);
}
catch (Exception e) {
_log.error("Aborting distributed transaction failed", e);
}
FrameworkUtils.setAppContext(TXN_ID, null);
}
}
}
}
}
/**
* join adds this thread to the passed transaction descriptor
* @param desc
* @throws UnsupportedOperationException if this method is called
* @deprecated this feature is not supported in java
*/
public static void join(TransactionData desc) {
throw new UnsupportedOperationException("join is not supported");
}
/**
* Determine if this method is an aysnchronous participant in the transaction
* @param desc
* @throws UnsupportedOperationException if this method is called
* @deprecated this feature is not supported in java
*/
public static boolean isAsyncParticipant() {
throw new UnsupportedOperationException("isAsyncParticipant is not supported");
}
public static void main(String[] args) {
// TransactionMgr.beginDependentTransaction();
TransactionMgr.beginDependentTransaction();
try {
if (true) {
UsageException err = new UsageException();
throw err;
};
TransactionMgr.commitDependent();
}
catch (Throwable x) {
System.out.println("Exception caught" );
}
System.out.println(TransactionMgr.isDependent());
System.out.println(TransactionMgr.isActive());
// TransactionMgr.commitDependent();
}
}