/*
* JBoss, Home of Professional Open Source
* Copyright 2006, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags.
* See the copyright.txt in the distribution for a full listing
* of individual contributors.
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU Lesser General Public License, v. 2.1.
* This program is distributed in the hope that it will be useful, but WITHOUT A
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public License,
* v.2.1 along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
* (C) 2005-2006,
* @author JBoss Inc.
*/
/*
* Copyright (C) 2001, 2002,
*
* Hewlett-Packard Arjuna Labs,
* Newcastle upon Tyne,
* Tyne and Wear,
* UK.
*
* $Id: CurrentImple.java 2342 2006-03-30 13:06:17Z $
*/
package com.arjuna.ats.internal.jts.orbspecific;
import org.omg.CORBA.BAD_OPERATION;
import org.omg.CORBA.BAD_PARAM;
import org.omg.CORBA.CompletionStatus;
import org.omg.CORBA.INVALID_TRANSACTION;
import org.omg.CORBA.LocalObject;
import org.omg.CORBA.NO_MEMORY;
import org.omg.CORBA.OBJECT_NOT_EXIST;
import org.omg.CORBA.SystemException;
import org.omg.CORBA.TRANSACTION_ROLLEDBACK;
import org.omg.CORBA.UNKNOWN;
import org.omg.CORBA.UserException;
import org.omg.CosTransactions.Control;
import org.omg.CosTransactions.Coordinator;
import org.omg.CosTransactions.HeuristicHazard;
import org.omg.CosTransactions.HeuristicMixed;
import org.omg.CosTransactions.Inactive;
import org.omg.CosTransactions.InvalidControl;
import org.omg.CosTransactions.NoTransaction;
import org.omg.CosTransactions.SubtransactionsUnavailable;
import org.omg.CosTransactions.Unavailable;
import com.arjuna.ArjunaOTS.ActionControl;
import com.arjuna.ats.arjuna.coordinator.CheckedAction;
import com.arjuna.ats.arjuna.coordinator.TxControl;
import com.arjuna.ats.internal.jts.ControlWrapper;
import com.arjuna.ats.internal.jts.OTSImpleManager;
import com.arjuna.ats.internal.jts.context.ContextManager;
import com.arjuna.ats.internal.jts.coordinator.CheckedActions;
import com.arjuna.ats.internal.jts.orbspecific.coordinator.ArjunaTransactionImple;
import com.arjuna.ats.jts.exceptions.ExceptionCodes;
import com.arjuna.ats.jts.extensions.ThreadAssociationControl;
import com.arjuna.ats.jts.logging.jtsLogger;
import com.arjuna.ats.jts.utils.Utility;
/**
* The implementation of CosTransactions::Current.
*
* In a multi-threaded environment where threads can terminate transactions they
* may not have started, then it is possible for a thread to call
* commit/rollback/rollback_only on a transaction which has already been (or is
* in the process of being) terminated. We assume that the subsequent thread is
* still associated with the transaction so that it can determine its status,
* rather than simply disassociate it when it tries to terminate and return
* NoTransaction, so we would return INVALID_TRANSACTION. This allows us to
* distinguish between the situation where there really is no transaction
* associated with the thread.
*
* @author Mark Little (mark@arjuna.com)
* @version $Id: CurrentImple.java 2342 2006-03-30 13:06:17Z $
* @since JTS 1.0.
*/
public class CurrentImple extends LocalObject implements
org.omg.CosTransactions.Current, com.arjuna.ats.jts.extensions.Current
{
public static final int TX_BEGUN = 0;
public static final int TX_COMMITTED = 1;
public static final int TX_ABORTED = 2;
public static final int TX_SUSPENDED = 3;
public static final int TX_RESUMED = 4;
public CurrentImple ()
{
if (jtsLogger.logger.isTraceEnabled()) {
jtsLogger.logger.trace("CurrentImple::CurrentImple ()");
}
_theManager = new ContextManager();
}
public void begin () throws SubtransactionsUnavailable, SystemException
{
ControlWrapper currentAction = _theManager.current();
if (currentAction == null) // no current, so create top-level action
{
if (jtsLogger.logger.isTraceEnabled()) {
jtsLogger.logger.trace("CurrentImple::begin - creating new top-level transaction.");
}
if (OTSImpleManager.localFactory())
currentAction = new ControlWrapper(
OTSImpleManager.factory().createLocal(get_timeout()));
else
currentAction = new ControlWrapper(
OTSImpleManager.get_factory().create(get_timeout()));
}
else
{
if (jtsLogger.logger.isTraceEnabled()) {
jtsLogger.logger.trace("CurrentImple::begin - creating new subtransaction.");
}
/*
* If the current transaction has terminated (by another thread)
* then we could simply start a new top-level transaction. However,
* the application may be assuming that the transaction returned by
* begin is a subtransaction. So, we throw INVALID_TRANSACTION.
*/
try
{
currentAction = currentAction.create_subtransaction();
}
catch (Unavailable ex)
{
throw new INVALID_TRANSACTION(
ExceptionCodes.UNAVAILABLE_COORDINATOR,
CompletionStatus.COMPLETED_NO);
}
catch (Inactive e)
{
throw new INVALID_TRANSACTION(
ExceptionCodes.INACTIVE_TRANSACTION,
CompletionStatus.COMPLETED_NO);
}
catch (NO_MEMORY nme)
{
System.gc();
throw nme;
}
catch (SystemException sysEx)
{
throw new INVALID_TRANSACTION(
ExceptionCodes.INACTIVE_TRANSACTION,
CompletionStatus.COMPLETED_NO);
}
catch (OutOfMemoryError me)
{
System.gc();
throw new NO_MEMORY(0, CompletionStatus.COMPLETED_NO);
}
}
_theManager.pushAction(currentAction);
try
{
ThreadAssociationControl.updateAssociation(currentAction, TX_BEGUN);
}
catch (Exception e)
{
/*
* An error happened, so mark the transaction as rollback only (in
* case it hasn't already been so marked.)
*/
try
{
rollback_only();
}
catch (Exception ex)
{
}
}
currentAction = null;
}
/**
* It's not possible to commit/abort out of order using the current
* interface.
*
* Do we delete the control if the transaction gives an heuristic result?
* CurrentImplely we do.
*
* If another thread has already terminated the transaction then: (i) if it
* committed, we do nothing - could throw TransactionRequired of
* INVALID_TRANSACTION, or NoTransaction. Probably not NoTransaction, since
* it would be better to distinguish between the situation where the
* transaction has already been terminated and there really is no
* transaction for this thread. (ii) if it rolledback, we throw
* TRANSACTION_ROLLEDBACK.
*
*/
public void commit (boolean report_heuristics) throws NoTransaction,
HeuristicMixed, HeuristicHazard, SystemException
{
if (jtsLogger.logger.isTraceEnabled()) {
jtsLogger.logger.trace("CurrentImple::commit ( "
+ report_heuristics + " )");
}
ControlWrapper currentAction = _theManager.current();
if (currentAction != null)
{
try
{
ThreadAssociationControl.updateAssociation(currentAction, TX_COMMITTED);
}
catch (Exception e)
{
/*
* An error happened, so mark the transaction as rollback only
* (in case it hasn't already been so marked.)
*/
rollback_only();
}
/*
* Note: we only destroy the control if we do not get an exception.
* This lets the user see what happened, and relies upon them to
* later destroy the control.
*/
try
{
currentAction.commit(report_heuristics);
_theManager.popAction();
}
catch (TRANSACTION_ROLLEDBACK e1)
{
/*
* Is ok to destroy transaction. Different for heuristics.
*/
_theManager.popAction();
throw e1;
}
catch (HeuristicMixed e2)
{
_theManager.popAction();
if (report_heuristics)
throw e2;
}
catch (HeuristicHazard e3)
{
_theManager.popAction();
if (report_heuristics)
throw e3;
}
catch (SystemException e4)
{
_theManager.popAction();
throw e4;
}
catch (Unavailable e5)
{
/*
* If terminated by some other thread then the reference we have
* will no longer be valid.
*/
_theManager.popAction();
throw new INVALID_TRANSACTION();
}
}
else
throw new NoTransaction();
}
/**
* If another thread has already terminated the transaction then: (i) if it
* rolled back, we do nothing - could throw TransactionRequired of
* INVALID_TRANSACTION, or NoTransaction. Probably not NoTransaction, since
* it would be better to distinguish between the situation where the
* transaction has already been terminated and there really is no
* transaction for this thread. (ii) if it committed, we throw
* INVALID_TRANSACTION.
*/
public void rollback () throws NoTransaction, SystemException
{
if (jtsLogger.logger.isTraceEnabled()) {
jtsLogger.logger.trace("CurrentImple::rollback ()");
}
ControlWrapper currentAction = _theManager.current();
if (currentAction != null)
{
ThreadAssociationControl.updateAssociation(currentAction, TX_ABORTED);
try
{
currentAction.rollback();
_theManager.popAction();
}
catch (INVALID_TRANSACTION e1)
{
/*
* If transaction has already terminated, then throw
* INVALID_TRANSACTION. Differentiates between this stat and not
* actually having a transaction associated with the thread.
*/
_theManager.popAction();
throw e1;
}
catch (SystemException e2)
{
_theManager.popAction();
throw e2;
}
catch (Unavailable e)
{
/*
* If no terminator then not allowed!
*/
_theManager.popAction();
throw new INVALID_TRANSACTION();
}
}
else
throw new NoTransaction();
}
/**
* If the transaction has already terminated (or is being terminated) then
* throw INVALID_TRANSACTION.
*/
public void rollback_only () throws NoTransaction, SystemException
{
if (jtsLogger.logger.isTraceEnabled()) {
jtsLogger.logger.trace("CurrentImple::rollback_only ()");
}
ControlWrapper currentAction = _theManager.current();
if (currentAction != null)
{
try
{
currentAction.rollback_only();
}
catch (org.omg.CosTransactions.Inactive exc)
{
throw new INVALID_TRANSACTION(
ExceptionCodes.INACTIVE_TRANSACTION,
CompletionStatus.COMPLETED_NO);
}
catch (SystemException e)
{
throw e;
}
catch (Unavailable ex)
{
throw new NoTransaction();
}
}
else
throw new NoTransaction();
}
public org.omg.CosTransactions.Status get_status () throws SystemException
{
ControlWrapper currentAction = _theManager.current();
org.omg.CosTransactions.Status stat = ((currentAction == null) ? org.omg.CosTransactions.Status.StatusNoTransaction
: currentAction.get_status());
if (jtsLogger.logger.isTraceEnabled()) {
jtsLogger.logger.trace("CurrentImple::get_status - returning "
+ Utility.stringStatus(stat));
}
return stat;
}
public String get_transaction_name () throws SystemException
{
ControlWrapper currentAction = _theManager.current();
String ch = ((currentAction == null) ? "null"
: currentAction.get_transaction_name());
if (jtsLogger.logger.isTraceEnabled()) {
jtsLogger.logger.trace("CurrentImple::get_transaction_name - returning "
+ ch);
}
return ch;
}
public synchronized void set_timeout (int seconds) throws SystemException
{
if (jtsLogger.logger.isTraceEnabled()) {
jtsLogger.logger.trace("CurrentImple::set_timeout ( "
+ seconds + " )");
}
/*
* Only bother if the value is anything other than the default.
*/
if (seconds > 0)
{
otsTransactionTimeout.set(new Integer(seconds));
}
else
{
if (seconds < 0)
{
throw new BAD_PARAM(ExceptionCodes.INVALID_TIMEOUT,
CompletionStatus.COMPLETED_NO);
}
else
{
otsTransactionTimeout.set(null);
}
}
}
public final synchronized int get_timeout () throws SystemException
{
Integer value = (Integer) otsTransactionTimeout.get();
int v = 0; // if not set then assume 0. What else can we do?
if (value != null)
{
v = value.intValue();
}
else
v = TxControl.getDefaultTimeout();
if (jtsLogger.logger.isTraceEnabled()) {
jtsLogger.logger.trace("CurrentImple::get_timeout - returning "
+ v);
}
return v;
}
public void setCheckedAction (CheckedAction ca) throws SystemException
{
if (jtsLogger.logger.isTraceEnabled()) {
jtsLogger.logger.trace("CurrentImple::setCheckedAction ( "
+ ca + " )");
}
CheckedActions.set(ca);
}
public CheckedAction getCheckedAction () throws SystemException
{
if (jtsLogger.logger.isTraceEnabled()) {
jtsLogger.logger.trace("CurrentImple::getCheckedAction ()");
}
return CheckedActions.get();
}
public org.omg.CosTransactions.Control get_control ()
throws SystemException
{
ControlWrapper theControl = _theManager.current();
if (theControl == null)
return null;
/*
* Always return a Control with a null terminator? Forces users to go
* through current, and attempts to stop explicit propagation, which
* prevents fully checked transactions. By default we don't do that
* because explicit transaction propagation is the only means of
* propagation we can guarantee between Orbs.
*/
if (true)
{
/*
* If it's a locally created control then we may not have registered
* it with the ORB yet, so do so now.
*/
try
{
return theControl.get_control();
}
catch (Unavailable e)
{
return null;
}
}
else
{
Coordinator coord = null;
try
{
coord = theControl.get_coordinator();
}
catch (Unavailable e)
{
coord = null;
throw new UNKNOWN(ExceptionCodes.UNAVAILABLE_COORDINATOR,
CompletionStatus.COMPLETED_NO);
}
catch (SystemException sysEx)
{
coord = null;
throw sysEx;
}
org.omg.CosTransactions.Control proxyControl = TransactionFactoryImple.createPropagatedControl(coord);
coord = null;
theControl = null;
return proxyControl;
}
}
/*
* Problem: there is a general problem with the Orb and memory management.
* If this method, say, is invoked remotely then we must duplicate the
* reference before returning it since the Orb will call release on the
* return value once it has been sent to the caller. However, in the local
* case, if we call duplicate then there is nothing to call release and we
* get memory leaks!
*
* Also assume that BasicAction's notion of current is the same as
* CurrentImple's, if the action is local.
*
*/
/**
* The spec. states that after suspend we should have a null transaction
* context, but is hazy as to what to do if we are nested. We shall assume
* that the control returned is for the current transaction and that we
* suspend the entire transaction hierarchy. Given the returned control, we
* can always regenerate the hierarchy later if required by resume.
*/
public org.omg.CosTransactions.Control suspend () throws SystemException
{
if (jtsLogger.logger.isTraceEnabled()) {
jtsLogger.logger.trace("CurrentImple::suspend ()");
}
ControlWrapper actPtr = _theManager.popAction();
if (actPtr == null)
{
ThreadAssociationControl.updateAssociation(null, TX_SUSPENDED);
return null;
}
else
{
ThreadAssociationControl.updateAssociation(actPtr, TX_SUSPENDED);
/*
* Purge the remaining controls from the thread context. If the
* controls are remote and proxies then we delete them here, since
* we must recreate them next time we want to use them anyway.
*/
_theManager.purgeActions();
if (actPtr.isLocal())
return actPtr.getImple().getControl();
else
return actPtr.getControl();
}
}
/**
* To support checked transactions we can only resume if the action is local
* or we received it implicitly.
*
* If the control refers to a nested transaction then we must recreate the
* entire hierarchy, i.e., the effect of a suspend/resume on the same
* control should be the same as never calling suspend in the first place.
*
* If the control is for a local transaction then it is simple to recreate
* the hierarchy. Otherwise we rely upon the PropagationContext to recreate
* it.
*
* If this control is a "proxy" then create a new proxy instance, so we can
* delete proxies whenever suspend is called.
*
* Should check if "new" transaction is not actually the current one anyway.
* If so, just return. The spec. doesn't mention what to do in this case, so
* for now we go to the overhead of the work regardless.
*/
public void resume (Control which) throws InvalidControl, SystemException
{
if (jtsLogger.logger.isTraceEnabled()) {
jtsLogger.logger.trace("CurrentImple::resume ( "
+ which + " )");
}
/*
* We must now "forget" any current transaction information. This is
* because when we end this transaction we must be associated with no
* transaction.
*/
_theManager.purgeActions();
if (which == null) // if null then return
{
ThreadAssociationControl.updateAssociation(null, TX_RESUMED);
return;
}
/*
* Must duplicate because it is an 'in' parameter which we want to keep.
*/
org.omg.CosTransactions.Control cont = which;
boolean invalidControl = false;
/*
* Now recreate the hierarchy (if any) of this transaction, pushing each
* control onto the stack. The method pushAction will push BasicActions
* onto the thread stack if necessary, so we don't need to worry about
* it here.
*/
try
{
Coordinator coord = cont.get_coordinator();
if (!coord.is_top_level_transaction())
{
/*
* Is the Control an ActionControl? If so then it has methods to
* allow us to get the parent directly. Otherwise, rely on the
* PropagationContext.
*/
ActionControl actControl = null;
try
{
actControl = com.arjuna.ArjunaOTS.ActionControlHelper.narrow(cont);
if (actControl == null)
throw new BAD_PARAM();
}
catch (Exception e)
{
/*
* Not an ActionControl.
*/
actControl = null;
}
if (actControl != null)
{
invalidControl = _theManager.addActionControlHierarchy(actControl);
}
else
{
invalidControl = _theManager.addRemoteHierarchy(cont);
}
}
coord = null;
}
catch (OBJECT_NOT_EXIST one)
{
// throw new InvalidControl();
}
catch (UNKNOWN ue) // JacORB 1.4.5 bug
{
}
catch (org.omg.CORBA.OBJ_ADAPTER oae) // JacORB 2.0 beta 2 bug
{
}
catch (SystemException sysEx)
{
throw new InvalidControl();
}
catch (UserException usrEx)
{
throw new InvalidControl();
}
catch (NullPointerException npx)
{
throw new InvalidControl();
}
catch (Exception ex)
{
throw new BAD_OPERATION("CurrentImple.resume: " + ex.toString());
}
/*
* Now put the new transaction on the top of the list.
*/
try
{
if (!invalidControl)
{
ControlWrapper wrap = new ControlWrapper(cont);
ThreadAssociationControl.updateAssociation(wrap, TX_RESUMED);
_theManager.pushAction(wrap);
}
}
catch (NullPointerException npx)
{
invalidControl = true;
}
cont = null;
if (invalidControl)
throw new InvalidControl();
}
/**
* Returns the internal context manager implementation. Applications should
* not use this method.
*
* @since JTS 2.1.
*/
public final ContextManager contextManager ()
{
return _theManager;
}
public void resumeImple (ControlImple which) throws InvalidControl,
SystemException
{
if (jtsLogger.logger.isTraceEnabled()) {
jtsLogger.logger.trace("CurrentImple::resumeImple ( "
+ which + " )");
}
/*
* We must now "forget" any current transaction information. This is
* because when we end this transaction we must be associated with no
* transaction.
*/
_theManager.purgeActions();
if (which == null) // if null then return
{
ThreadAssociationControl.updateAssociation(null, TX_RESUMED);
return;
}
boolean invalidControl = _theManager.addControlImpleHierarchy(which);
/*
* Now put the new transaction on the top of the list.
*/
try
{
if (!invalidControl)
{
ControlWrapper wrap = new ControlWrapper(which);
ThreadAssociationControl.updateAssociation(wrap, TX_RESUMED);
_theManager.pushAction(wrap);
}
}
catch (NullPointerException npx)
{
npx.printStackTrace();
invalidControl = true;
}
if (invalidControl)
throw new InvalidControl();
}
public void resumeWrapper (ControlWrapper which) throws InvalidControl,
SystemException
{
if (jtsLogger.logger.isTraceEnabled()) {
jtsLogger.logger.trace("CurrentImple::resumeWrapper ( "
+ which + " )");
}
if (which != null)
{
/*
* If this is a local transaction and we haven't zero-ed the transaction
* reference then resume it. Otherwise go with the Control.
*/
ArjunaTransactionImple tx = ((which.getImple() == null) ? null : which.getImple().getImplHandle());
if (which.isLocal() && (tx != null))
resumeImple(which.getImple());
else
resume(which.getControl());
}
else
{
resumeImple(null);
}
}
public ControlWrapper suspendWrapper () throws SystemException
{
if (jtsLogger.logger.isTraceEnabled()) {
jtsLogger.logger.trace("CurrentImple::suspendWrapper ()");
}
ControlWrapper actPtr = _theManager.popAction();
if (actPtr == null)
{
ThreadAssociationControl.updateAssociation(null, TX_SUSPENDED);
return null;
}
else
{
ThreadAssociationControl.updateAssociation(actPtr, TX_SUSPENDED);
/*
* Purge the remaining controls from the thread context. If the
* controls are remote and proxies then we delete them here, since
* we must recreate them next time we want to use them anyway.
*/
_theManager.purgeActions();
return actPtr;
}
}
public ControlWrapper getControlWrapper () throws SystemException
{
if (jtsLogger.logger.isTraceEnabled()) {
jtsLogger.logger.trace("CurrentImple.getControlWrapper ()");
}
return _theManager.current();
}
protected static ContextManager _theManager = null;
private static ThreadLocal otsTransactionTimeout = new ThreadLocal(); // thread
// specific
}