/*
* 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) 2002,
*
* Hewlett-Packard Arjuna Labs,
* Newcastle upon Tyne,
* Tyne and Wear,
* UK.
*
* $Id: TransactionImple.java 2342 2006-03-30 13:06:17Z $
*/
package com.arjuna.ats.internal.jta.transaction.arjunacore;
import com.arjuna.ats.internal.jta.xa.TxInfo;
import com.arjuna.ats.internal.jta.utils.*;
import com.arjuna.ats.internal.jta.utils.arjunacore.StatusConverter;
import com.arjuna.ats.internal.jta.resources.arjunacore.SynchronizationImple;
import com.arjuna.ats.internal.jta.resources.arjunacore.XAOnePhaseResource;
import com.arjuna.ats.internal.jta.resources.arjunacore.XAResourceRecord;
import com.arjuna.ats.jta.common.Configuration;
import com.arjuna.ats.jta.common.Environment;
import com.arjuna.ats.jta.common.jtaPropertyManager;
import com.arjuna.ats.jta.resources.LastResourceCommitOptimisation;
import com.arjuna.ats.jta.utils.XAHelper;
import com.arjuna.ats.jta.xa.XidImple;
import com.arjuna.ats.jta.logging.*;
import com.arjuna.ats.jta.xa.XAModifier;
import com.arjuna.ats.arjuna.LastResourceRecord;
import com.arjuna.ats.arjuna.coordinator.*;
import com.arjuna.ats.arjuna.common.*;
import com.arjuna.common.util.logging.*;
import javax.transaction.xa.*;
import java.util.*;
import javax.transaction.RollbackException;
import java.lang.IllegalStateException;
import EDU.oswego.cs.dl.util.concurrent.ConcurrentHashMap;
/*
* Is given an AtomicAction, but uses the TwoPhaseCoordinator aspects of it to
* ensure that the thread association continues.
*/
/**
* @message com.arjuna.ats.internal.jta.transaction.arjunacore.inactive
* [com.arjuna.ats.internal.jta.transaction.arjunacore.inactive] The
* transaction is not active!
* @message com.arjuna.ats.internal.jta.transaction.arjunacore.nullres
* [com.arjuna.ats.internal.jta.transaction.arjunacore.nullres]
* Resource paramater is null!
* @message com.arjuna.ats.internal.jta.transaction.arjunacore.illresstate
* [com.arjuna.ats.internal.jta.transaction.arjunacore.illresstate]
* illegal resource state
* @message com.arjuna.ats.internal.jta.transaction.arjunacore.notatomicaction
* [com.arjuna.ats.internal.jta.transaction.arjunacore.notatomicaction]
* Current transaction is not an AtomicAction!
* @message com.arjuna.ats.internal.jta.transaction.arjunacore.lastResourceOptimisationInterface
* [com.arjuna.ats.internal.jta.transaction.arjunacore.lastResourceOptimisationInterface] -
* failed to load Last Resource Optimisation Interface
*
* @message com.arjuna.ats.internal.jta.transaction.arjunacore.lastResource.multipleWarning
* [com.arjuna.ats.internal.jta.transaction.arjunacore.lastResource.multipleWarning]
* Multiple last resources have been added to the current transaction.
* This is transactionally unsafe and should not be relied upon.
* Current resource is {0}
* @message com.arjuna.ats.internal.jta.transaction.arjunacore.lastResource.disallow
* [com.arjuna.ats.internal.jta.transaction.arjunacore.lastResource.disallow]
* Adding multiple last resources is disallowed. Current resource is
* {0}
* @message com.arjuna.ats.internal.jta.transaction.arjunacore.lastResource.startupWarning
* [com.arjuna.ats.internal.jta.transaction.arjunacore.lastResource.startupWarning]
* You have chosen to enable multiple last resources in the transaction
* manager. This is transactionally unsafe and should not be relied
* upon.
*/
public class TransactionImple implements javax.transaction.Transaction,
com.arjuna.ats.jta.transaction.Transaction
{
/*
* Only works with AtomicAction and TwoPhaseCoordinator.
*/
/**
* Create a new transaction with the specified timeout.
*/
public TransactionImple(int timeout)
{
_theTransaction = new AtomicAction();
_theTransaction.begin(timeout);
_resources = new Hashtable();
_duplicateResources = new Hashtable();
_suspendCount = 0;
_xaTransactionTimeoutEnabled = getXATransactionTimeoutEnabled();
}
/**
* Overloads Object.equals()
*/
public boolean equals(Object obj)
{
if (jtaLogger.logger.isDebugEnabled())
{
jtaLogger.logger.debug(DebugLevel.FUNCTIONS,
VisibilityLevel.VIS_PUBLIC,
com.arjuna.ats.jta.logging.FacilityCode.FAC_JTA,
"TransactionImple.equals");
}
if (obj == null)
return false;
if (obj == this)
return true;
if (obj instanceof TransactionImple)
{
/*
* If we can't get either coordinator to compare, then assume
* transactions are different.
*/
try
{
TransactionImple tx = (TransactionImple) obj;
return tx.get_uid().equals(_theTransaction.get_uid());
}
catch (Exception e)
{
}
}
return false;
}
/**
* Return -1 if we fail.
*/
public int hashCode()
{
if (_theTransaction == null)
return -1;
else
return _theTransaction.get_uid().hashCode();
}
/**
* The JTA specification is vague on whether the calling thread can have any
* transaction associated with it. It does say that it need not have the
* same transaction as this one. We could call suspend prior to making these
* calls, but for now we do nothing.
*/
/**
* We should never throw a HeuristicRollbackException because if we get a
* HeuristicRollback from a resource, and can successfully rollback the
* other resources, this is then the same as having simply been forced to
* rollback the transaction during phase 1.
*
* @message com.arjuna.ats.internal.jta.transaction.arjunacore.invalidstate
* [com.arjuna.ats.internal.jta.transaction.arjunacore.invalidstate]
* Invalid transaction state
* @message com.arjuna.ats.internal.jta.transaction.arjunacore.commitwhenaborted
* [com.arjuna.ats.internal.jta.transaction.arjunacore.commitwhenaborted]
* Can't commit because the transaction is in aborted state
*/
public void commit() throws javax.transaction.RollbackException,
javax.transaction.HeuristicMixedException,
javax.transaction.HeuristicRollbackException,
java.lang.SecurityException, javax.transaction.SystemException,
java.lang.IllegalStateException
{
if (jtaLogger.logger.isDebugEnabled())
{
jtaLogger.logger.debug(DebugLevel.FUNCTIONS,
VisibilityLevel.VIS_PUBLIC,
com.arjuna.ats.jta.logging.FacilityCode.FAC_JTA,
"TransactionImple.commit");
}
if (_theTransaction != null)
{
switch (_theTransaction.status())
{
case ActionStatus.RUNNING:
case ActionStatus.ABORT_ONLY:
break;
default:
throw new IllegalStateException(
jtaLogger.logMesg
.getString("com.arjuna.ats.internal.jta.transaction.arjunacore.inactive"));
}
/*
* Call end on any suspended resources. If this fails, then the
* transaction will be rolled back.
*/
if (!endSuspendedRMs())
_theTransaction.preventCommit();
// use end of TwoPhaseCoordinator to avoid thread changes.
int status = _theTransaction.end(true);
TransactionImple.removeTransaction(this);
switch (status)
{
case ActionStatus.COMMITTED:
case ActionStatus.COMMITTING: // in case of async commit
break;
case ActionStatus.H_MIXED:
throw new javax.transaction.HeuristicMixedException();
case ActionStatus.H_HAZARD:
throw new javax.transaction.HeuristicMixedException();
case ActionStatus.H_ROLLBACK:
case ActionStatus.ABORTED:
RollbackException rollbackException = new RollbackException(
jtaLogger.logMesg
.getString("com.arjuna.ats.internal.jta.transaction.arjunacore.commitwhenaborted"));
if (_theTransaction.getDeferredThrowable() != null)
{
rollbackException.initCause(_theTransaction
.getDeferredThrowable());
}
throw rollbackException;
default:
throw new IllegalStateException(
jtaLogger.logMesg
.getString("com.arjuna.ats.internal.jta.transaction.arjunacore.invalidstate"));
}
}
else
throw new IllegalStateException(
jtaLogger.logMesg
.getString("com.arjuna.ats.internal.jta.transaction.arjunacore.inactive"));
}
/**
* @message com.arjuna.ats.internal.jta.transaction.arjunacore.rollbackstatus
* [com.arjuna.ats.internal.jta.transaction.arjunacore.rollbackstatus]
* Transaction rollback status is:
* @message com.arjuna.ats.internal.jta.transaction.arjunacore.endsuspendfailed1
* [com.arjuna.ats.internal.jta.transaction.arjunacore.endsuspendfailed1]
* Ending suspended RMs failed when rolling back the transaction!
* @message com.arjuna.ats.internal.jta.transaction.arjunacore.endsuspendfailed2
* [com.arjuna.ats.internal.jta.transaction.arjunacore.endsuspendfailed2]
* Ending suspended RMs failed when rolling back the transaction,
* but transaction rolled back.
*/
public void rollback() throws java.lang.IllegalStateException,
java.lang.SecurityException, javax.transaction.SystemException
{
if (jtaLogger.logger.isDebugEnabled())
{
jtaLogger.logger.debug(DebugLevel.FUNCTIONS,
VisibilityLevel.VIS_PUBLIC,
com.arjuna.ats.jta.logging.FacilityCode.FAC_JTA,
"TransactionImple.rollback");
}
if (_theTransaction != null)
{
switch (_theTransaction.status())
{
case ActionStatus.RUNNING:
case ActionStatus.ABORT_ONLY:
break;
default:
throw new IllegalStateException(
jtaLogger.logMesg
.getString("com.arjuna.ats.internal.jta.transaction.arjunacore.inactive"));
}
/*
* Call end on any suspended resources. If this fails, then there's
* not a lot else we can do because the transaction is about to roll
* back anyway!
*/
boolean endSuspendedFailed = !endSuspendedRMs();
if (endSuspendedFailed)
{
if (jtaLogger.loggerI18N.isWarnEnabled())
{
jtaLogger.loggerI18N
.warn("com.arjuna.ats.internal.jta.transaction.arjunacore.endsuspendfailed1");
}
}
int outcome = _theTransaction.cancel(); // use cancel of
// TwoPhaseCoordinator to
// avoid thread changes.
TransactionImple.removeTransaction(this);
switch (outcome)
{
case ActionStatus.ABORTED:
case ActionStatus.ABORTING: // in case of async rollback
break;
default:
throw new IllegalStateException(
jtaLogger.logMesg
.getString("com.arjuna.ats.internal.jta.transaction.arjunacore.rollbackstatus")
+ ActionStatus.stringForm(outcome));
}
if (endSuspendedFailed)
throw new IllegalStateException(
jtaLogger.logMesg
.getString("com.arjuna.ats.internal.jta.transaction.arjunacore.endsuspendfailed2"));
}
else
throw new IllegalStateException(
jtaLogger.logMesg
.getString("com.arjuna.ats.internal.jta.transaction.arjunacore.inactive"));
}
public void setRollbackOnly() throws java.lang.IllegalStateException,
javax.transaction.SystemException
{
if (jtaLogger.logger.isDebugEnabled())
{
jtaLogger.logger.debug(DebugLevel.FUNCTIONS,
VisibilityLevel.VIS_PUBLIC,
com.arjuna.ats.jta.logging.FacilityCode.FAC_JTA,
"TransactionImple.setRollbackOnly");
}
if (_theTransaction != null)
{
if (!_theTransaction.preventCommit())
{
switch (_theTransaction.status())
{
case ActionStatus.ABORTED:
case ActionStatus.ABORTING:
break;
default:
throw new IllegalStateException(
jtaLogger.logMesg
.getString("com.arjuna.ats.internal.jta.transaction.arjunacore.inactive"));
}
}
}
else
throw new IllegalStateException(
jtaLogger.logMesg
.getString("com.arjuna.ats.internal.jta.transaction.arjunacore.inactive"));
}
public int getStatus() throws javax.transaction.SystemException
{
if (jtaLogger.logger.isDebugEnabled())
{
jtaLogger.logger.debug(DebugLevel.FUNCTIONS,
VisibilityLevel.VIS_PUBLIC,
com.arjuna.ats.jta.logging.FacilityCode.FAC_JTA,
"TransactionImple.getStatus");
}
int status = javax.transaction.Status.STATUS_NO_TRANSACTION;
if (_theTransaction != null)
{
return StatusConverter.convert(_theTransaction.status());
}
return status;
}
/**
* @message com.arjuna.ats.internal.jta.transaction.arjunacore.nullparam
* [com.arjuna.ats.internal.jta.transaction.arjunacore.nullparam]
* null synchronization parameter!
* @message com.arjuna.ats.internal.jta.transaction.arjunacore.syncsnotallowed
* [com.arjuna.ats.internal.jta.transaction.arjunacore.syncsnotallowed]
* Synchronizations are not allowed!
*/
public void registerSynchronization(javax.transaction.Synchronization sync)
throws javax.transaction.RollbackException,
java.lang.IllegalStateException, javax.transaction.SystemException
{
if (jtaLogger.logger.isDebugEnabled())
{
jtaLogger.logger.debug(DebugLevel.FUNCTIONS,
VisibilityLevel.VIS_PUBLIC,
com.arjuna.ats.jta.logging.FacilityCode.FAC_JTA,
"TransactionImple.registerSynchronization");
}
if (sync == null)
{
throw new javax.transaction.SystemException(
"TransactionImple.registerSynchronization - "
+ jtaLogger.logMesg
.getString("com.arjuna.ats.internal.jta.transaction.arjunacore.nullparam"));
}
registerSynchronizationImple(new SynchronizationImple(sync, false));
}
// package-private method also for use by
// TransactionSynchronizationRegistryImple
/**
* @message com.arjuna.ats.internal.jta.transaction.arjunacore.syncwhenaborted
* [com.arjuna.ats.internal.jta.transaction.arjunacore.syncwhenaborted]
* Can't register synchronization because the transaction is in
* aborted state
*/
void registerSynchronizationImple(SynchronizationImple synchronizationImple)
throws javax.transaction.RollbackException,
java.lang.IllegalStateException, javax.transaction.SystemException
{
if (_theTransaction != null)
{
if (_theTransaction.addSynchronization(synchronizationImple) != AddOutcome.AR_ADDED)
{
int status = _theTransaction.status();
switch (status)
{
case ActionStatus.ABORTED:
throw new javax.transaction.RollbackException(
jtaLogger.logMesg
.getString("com.arjuna.ats.internal.jta.transaction.arjunacore.syncwhenaborted"));
case ActionStatus.CREATED:
throw new IllegalStateException(
jtaLogger.logMesg
.getString("com.arjuna.ats.internal.jta.transaction.arjunacore.inactive"));
default:
throw new IllegalStateException(
jtaLogger.logMesg
.getString("com.arjuna.ats.internal.jta.transaction.arjunacore.syncsnotallowed"));
}
}
}
else
throw new IllegalStateException(
jtaLogger.logMesg
.getString("com.arjuna.ats.internal.jta.transaction.arjunacore.inactive"));
}
/**
* This is the JTA compliant version of the method. However, you had better
* know that your XAResource and family are truly compliant implementations.
* If they aren't then we may fail gracefully (e.g., some versions of Oracle
* don't work with arbitrary Xid implementations!)
*
* If the family isn't compliant, then you should use the other method and
* pass through a relevant XAModifier, which should address the issues we
* have already come across.
*/
public boolean enlistResource(XAResource xaRes) throws RollbackException,
IllegalStateException, javax.transaction.SystemException
{
return enlistResource(xaRes, null);
}
/**
* @message com.arjuna.ats.internal.jta.transaction.arjunacore.enlisterror
* [com.arjuna.ats.internal.jta.transaction.arjunacore.enlisterror]
* {0} - caught: {1}
* @message com.arjuna.ats.internal.jta.transaction.arjunacore.timeouterror[com.arjuna.ats.internal.jta.transaction.arjunacore.timeouterror]
* {0} setTransactionTimeout on XAResource threw: {1}
* @message com.arjuna.ats.internal.jta.transaction.arjunacore.enliststarterror
* [com.arjuna.ats.internal.jta.transaction.arjunacore.enliststarterror]
* {0} - XAResource.start returned: {1} for {2}
* @message com.arjuna.ats.internal.jta.transaction.arjunacore.couldnotregister
* [com.arjuna.ats.internal.jta.transaction.arjunacore.couldnotregister]
* could not register transaction
* @message com.arjuna.ats.internal.jta.transaction.arjunacore.xastart
* [com.arjuna.ats.internal.jta.transaction.arjunacore.xastart] {0} -
* caught: {1} for {2}
* @message com.arjuna.ats.internal.jta.transaction.arjunacore.elistwhenmarkedrollback
* [com.arjuna.ats.internal.jta.transaction.arjunacore.elistwhenmarkedrollback]
* Can't enlist the resource because the transaction is marked for
* rollback
*/
public boolean enlistResource(XAResource xaRes, Object[] params)
throws RollbackException, IllegalStateException,
javax.transaction.SystemException
{
if (jtaLogger.logger.isDebugEnabled())
{
jtaLogger.logger.debug(DebugLevel.FUNCTIONS,
VisibilityLevel.VIS_PUBLIC,
com.arjuna.ats.jta.logging.FacilityCode.FAC_JTA,
"TransactionImple.enlistResource ( " + xaRes + " )");
}
if (xaRes == null)
throw new javax.transaction.SystemException(
"TransactionImple.enlistResource - "
+ jtaLogger.logMesg
.getString("com.arjuna.ats.internal.jta.transaction.arjunacore.nullres"));
int status = getStatus();
switch (status)
{
case javax.transaction.Status.STATUS_MARKED_ROLLBACK:
throw new RollbackException(
"TransactionImple.enlistResource - "
+ jtaLogger.logMesg
.getString("com.arjuna.ats.internal.jta.transaction.arjunacore.nullres"));
case javax.transaction.Status.STATUS_ACTIVE:
break;
default:
throw new IllegalStateException(
jtaLogger.logMesg
.getString("com.arjuna.ats.internal.jta.transaction.arjunacore.inactive"));
}
XAModifier theModifier = null;
if (params != null)
{
if (params.length >= XAMODIFIER + 1)
{
if (params[XAMODIFIER] instanceof XAModifier)
{
theModifier = (XAModifier) params[XAMODIFIER];
}
}
}
try
{
/*
* For each transaction we maintain a list of resources registered
* with it. Each element on this list also contains a list of
* threads which have registered this resource, and what their XID
* was for that registration.
*/
TxInfo info = null;
/*
* Have we seen this specific resource instance before? Do this
* trawl first before checking the RM instance later. Saves time.
*/
try
{
synchronized (this)
{
info = (TxInfo) _resources.get(xaRes);
if (info == null)
{
/*
* Null info means it's not in the main resources list,
* but may be in the duplicates.
*/
info = (TxInfo) _duplicateResources.get(xaRes);
}
}
if (info != null)
{
switch (info.getState())
{
case TxInfo.ASSOCIATION_SUSPENDED:
{
/*
* Have seen resource before, so do a resume. The
* Resource instance will still be registered with the
* transaction though.
*/
int xaStartResume = ((theModifier == null) ? XAResource.TMRESUME
: theModifier
.xaStartParameters(XAResource.TMRESUME));
xaRes.start(info.xid(), xaStartResume);
info.setState(TxInfo.ASSOCIATED);
synchronized (this)
{
_suspendCount--;
}
return true; // already registered resource with this
// transaction!
}
case TxInfo.ASSOCIATED:
{
/*
* Already active on this transaction.
*/
return true;
}
case TxInfo.NOT_ASSOCIATED:
{
/*
* Resource was associated, but was presumably delisted.
*/
int xaStartJoin = ((theModifier == null) ? XAResource.TMJOIN
: theModifier
.xaStartParameters(XAResource.TMJOIN));
xaRes.start(info.xid(), xaStartJoin);
info.setState(TxInfo.ASSOCIATED);
return true;
}
default:
{
// Note: this exception will be caught by our catch
// block
throw new IllegalStateException(
"TransactionImple.enlistResource - "
+ jtaLogger.logMesg
.getString("com.arjuna.ats.internal.jta.transaction.arjunacore.illresstate")
+ ":" + info.getState());
}
}
}
}
catch (IllegalStateException ex)
{
throw ex; // we threw it in the first place
}
catch (XAException exp)
{
if (info != null)
info.setState(TxInfo.FAILED);
if (jtaLogger.loggerI18N.isWarnEnabled())
{
jtaLogger.loggerI18N
.warn(
"com.arjuna.ats.internal.jta.transaction.arjunacore.enlisterror",
new Object[]
{ "TransactionImple.enlistResource",
XAHelper.printXAErrorCode(exp) });
}
return false;
}
// if (threadIsActive(xaRes))
// return true; // this thread has already registered a resource for
// this db
/*
* We definitely haven't seen this specific resource instance
* before, but that doesn't mean that we haven't seen the RM it is
* connected to.
*/
Xid xid = null;
TxInfo existingRM = isNewRM(xaRes);
if (existingRM == null)
{
/*
* New RM, so create xid with new branch.
*/
boolean branchRequired = true;
synchronized (this)
{
if (_resources.size() == 0)// first ever, so no need for
// branch
{
// branchRequired = false;
branchRequired = true;
}
}
xid = createXid(branchRequired, theModifier);
boolean associatedWork = false;
int retry = 20;
/*
* If another process has (or is about to) create the same
* transaction association then we will probably get a failure
* during start with XAER_DUPID. We know this must be due to
* another server, since we keep track of our own registrations.
* So, if this happens we create a new transaction branch and
* try again.
*
* To save time we could always just create branches by default.
*
* Is there a benefit to a zero branch?
*/
while (!associatedWork)
{
try
{
if (_xaTransactionTimeoutEnabled)
{
int timeout = _theTransaction.getTimeout();
if (timeout > 0)
{
try
{
xaRes.setTransactionTimeout(timeout);
}
catch (XAException te)
{
if (jtaLogger.loggerI18N.isWarnEnabled())
{
jtaLogger.loggerI18N
.warn(
"com.arjuna.ats.internal.jta.transaction.arjunacore.timeouterror",
new Object[]
{
"TransactionImple.enlistResource",
XAHelper
.printXAErrorCode(te),
xid });
}
}
}
}
int xaStartNormal = ((theModifier == null) ? XAResource.TMNOFLAGS
: theModifier
.xaStartParameters(XAResource.TMNOFLAGS));
xaRes.start(xid, xaStartNormal);
associatedWork = true;
_resources.put(xaRes, new TxInfo(xid));
}
catch (XAException e)
{
// transaction already created by another server
if ((e.errorCode == XAException.XAER_DUPID)
|| (e.errorCode == XAException.XAER_RMERR))
{
if (retry > 0)
xid = createXid(true, theModifier);
retry--;
}
else
{
/*
* Can't do start, so set transaction to rollback
* only.
*/
if (jtaLogger.loggerI18N.isWarnEnabled())
{
jtaLogger.loggerI18N
.warn(
"com.arjuna.ats.internal.jta.transaction.arjunacore.enliststarterror",
new Object[]
{
"TransactionImple.enlistResource",
XAHelper
.printXAErrorCode(e),
xid });
}
markRollbackOnly();
throw e;
}
if (retry < 0)
{
if (jtaLogger.loggerI18N.isWarnEnabled())
{
jtaLogger.loggerI18N
.warn(
"com.arjuna.ats.internal.jta.transaction.arjunacore.enliststarterror",
new Object[]
{
"TransactionImple.enlistResource",
XAHelper
.printXAErrorCode(e),
xid });
}
markRollbackOnly();
throw new javax.transaction.SystemException(
"TransactionImple.enlistResource - XAResource.start "
+ jtaLogger.logMesg
.getString("com.arjuna.ats.internal.jta.transaction.arjunacore.couldnotregister")
+ ": " + xid);
}
}
}
}
else
{
/*
* Have seen this RM before, so ignore this instance. The first
* registered RM instance will be used to drive the transaction
* completion. We add it to the duplicateResource list so we can
* delist it correctly later though.
*/
/*
* Re-create xid.
*/
xid = existingRM.xid();
try
{
int xaStartJoin = ((theModifier == null) ? XAResource.TMJOIN
: theModifier.xaStartParameters(XAResource.TMJOIN));
xaRes.start(xid, xaStartJoin);
}
catch (XAException ex)
{
if (jtaLogger.loggerI18N.isWarnEnabled())
{
jtaLogger.loggerI18N
.warn(
"com.arjuna.ats.internal.jta.transaction.arjunacore.xastart",
new Object[]
{
"TransactionImple.enlistResource - xa_start ",
XAHelper.printXAErrorCode(ex),
xid });
}
markRollbackOnly();
throw ex;
}
/*
* Add to duplicate resources list so we can keep track of it
* (particularly if we later have to delist).
*/
_duplicateResources.put(xaRes, new TxInfo(xid));
return true;
}
/*
* Control and Coordinator should be set, or we would not have
* gotten this far!
*/
final AbstractRecord record;
if ((xaRes instanceof LastResourceCommitOptimisation)
|| ((LAST_RESOURCE_OPTIMISATION_INTERFACE != null) && LAST_RESOURCE_OPTIMISATION_INTERFACE
.isInstance(xaRes)))
{
if (lastResourceCount == 1)
{
if (jtaLogger.loggerI18N.isWarnEnabled())
{
if (ALLOW_MULTIPLE_LAST_RESOURCES)
{
jtaLogger.loggerI18N
.warn(
"com.arjuna.ats.internal.jta.transaction.arjunacore.lastResource.multipleWarning",
new Object[]
{ xaRes });
}
else
{
jtaLogger.loggerI18N
.warn(
"com.arjuna.ats.internal.jta.transaction.arjunacore.lastResource.disallow",
new Object[]
{ xaRes });
}
}
}
if ((lastResourceCount++ == 0) || ALLOW_MULTIPLE_LAST_RESOURCES)
{
record = new LastResourceRecord(new XAOnePhaseResource(
xaRes, xid, params));
}
else
{
record = null;
}
}
else
{
record = new XAResourceRecord(this, xaRes, xid, params);
}
if ((record == null)
|| (_theTransaction.add(record) != AddOutcome.AR_ADDED))
{
markRollbackOnly();
return false;
}
else
return true;
}
catch (Exception e)
{
e.printStackTrace();
/*
* Some exceptional condition arose and we probably could not enlist
* the resouce. So, for safety mark the transaction as rollback
* only.
*/
markRollbackOnly();
return false;
}
}
/*
* Do we have to unregister resources? Assume not as it would not make much
* sense otherwise!
*/
/**
* @message com.arjuna.ats.internal.jta.transaction.arjunacore.unknownresource
* [com.arjuna.ats.internal.jta.transaction.arjunacore.unknownresource]
* {0} - unknown resource
* @message com.arjuna.ats.internal.jta.transaction.arjunacore.delistresource
* [com.arjuna.ats.internal.jta.transaction.arjunacore.delistresource]
* {0} - caught exception during delist : {1}
* @message com.arjuna.ats.internal.jta.transaction.arjunacore.delistgeneral
* [com.arjuna.ats.internal.jta.transaction.arjunacore.delistgeneral]
* {0} caught exception {1}
* @message com.arjuna.ats.internal.jta.transaction.arjunacore.ressuspended
* [com.arjuna.ats.internal.jta.transaction.arjunacore.ressuspended]
* resource already suspended.
*/
public boolean delistResource(XAResource xaRes, int flags)
throws IllegalStateException, javax.transaction.SystemException
{
if (jtaLogger.logger.isDebugEnabled())
{
jtaLogger.logger.debug(DebugLevel.FUNCTIONS,
VisibilityLevel.VIS_PUBLIC,
com.arjuna.ats.jta.logging.FacilityCode.FAC_JTA,
"TransactionImple.delistResource ( " + xaRes + " )");
}
if (xaRes == null)
throw new javax.transaction.SystemException(
"TransactionImple.delistResource - "
+ jtaLogger.logMesg
.getString("com.arjuna.ats.internal.jta.transaction.arjunacore.nullres"));
int status = getStatus();
switch (status)
{
case javax.transaction.Status.STATUS_ACTIVE:
break;
case javax.transaction.Status.STATUS_MARKED_ROLLBACK:
break;
default:
throw new IllegalStateException(
jtaLogger.logMesg
.getString("com.arjuna.ats.internal.jta.transaction.arjunacore.inactive"));
}
TxInfo info = null;
try
{
synchronized (this)
{
info = (TxInfo) _resources.get(xaRes);
if (info == null)
info = (TxInfo) _duplicateResources.get(xaRes);
}
if (info == null)
{
if (jtaLogger.loggerI18N.isWarnEnabled())
{
jtaLogger.loggerI18N
.warn(
"com.arjuna.ats.internal.jta.transaction.arjunacore.unknownresource",
new Object[]
{ "TransactionImple.delistResource" });
}
return false;
}
else
{
boolean optimizedRollback = false;
try
{
/*
* If we know the transaction is going to rollback, then we
* can try to rollback the RM now. Just an optimisation.
*/
if (status == javax.transaction.Status.STATUS_MARKED_ROLLBACK)
{
if (XAUtils.canOptimizeDelist(xaRes))
{
xaRes.end(info.xid(), XAResource.TMFAIL);
xaRes.rollback(info.xid());
info.setState(TxInfo.OPTIMIZED_ROLLBACK);
optimizedRollback = true;
}
}
}
catch (Exception e)
{
// failed, so try again when transaction does rollback
}
switch (info.getState())
{
case TxInfo.ASSOCIATED:
{
if ((flags & XAResource.TMSUCCESS) != 0)
{
xaRes.end(info.xid(), XAResource.TMSUCCESS);
info.setState(TxInfo.NOT_ASSOCIATED);
}
else
{
if ((flags & XAResource.TMSUSPEND) != 0)
{
xaRes.end(info.xid(), XAResource.TMSUSPEND);
info.setState(TxInfo.ASSOCIATION_SUSPENDED);
synchronized (this)
{
_suspendCount++;
}
}
else
{
xaRes.end(info.xid(), XAResource.TMFAIL);
info.setState(TxInfo.FAILED);
}
}
}
break;
case TxInfo.ASSOCIATION_SUSPENDED:
{
if ((flags & XAResource.TMSUCCESS) != 0)
{
// Oracle barfs if we don't send resume first, despite
// what XA says!
if (XAUtils.mustEndSuspendedRMs(xaRes))
xaRes.start(info.xid(), XAResource.TMRESUME);
xaRes.end(info.xid(), XAResource.TMSUCCESS);
info.setState(TxInfo.NOT_ASSOCIATED);
synchronized (this)
{
_suspendCount--;
}
}
else
{
if ((flags & XAResource.TMSUSPEND) != 0)
{
// Note: this exception will be caught by our catch
// block
throw new IllegalStateException(
"TransactionImple.delistResource - "
+ jtaLogger.logMesg
.getString("com.arjuna.ats.internal.jta.transaction.arjunacore.ressuspended"));
}
else
{
xaRes.end(info.xid(), XAResource.TMFAIL);
info.setState(TxInfo.FAILED);
synchronized (this)
{
_suspendCount--;
}
}
}
}
break;
default:
{
if (!optimizedRollback)
throw new IllegalStateException(
"TransactionImple.delistResource - "
+ jtaLogger.logMesg
.getString("com.arjuna.ats.internal.jta.transaction.arjunacore.illresstate")
+ ":" + info.getState());
}
}
info = null;
return true;
}
}
catch (IllegalStateException ex)
{
throw ex;
}
catch (XAException exp)
{
if (info != null)
info.setState(TxInfo.FAILED);
/*
* For safety mark the transaction as rollback only.
*/
markRollbackOnly();
if (jtaLogger.loggerI18N.isWarnEnabled())
{
jtaLogger.loggerI18N
.warn(
"com.arjuna.ats.internal.jta.transaction.arjunacore.delistresource",
new Object[]
{ "TransactionImple.delistResource",
XAHelper.printXAErrorCode(exp) });
}
return false;
}
catch (Exception e)
{
if (jtaLogger.loggerI18N.isWarnEnabled())
{
jtaLogger.loggerI18N
.warn(
"com.arjuna.ats.internal.jta.transaction.arjunacore.delistgeneral",
new Object[]
{ "TransactionImple.delistResource", e });
}
/*
* Some exception occurred and we probably could not delist the
* resource. So, for safety mark the transaction as rollback only.
*/
markRollbackOnly();
return false;
}
}
public final Uid get_uid()
{
return _theTransaction.get_uid();
}
public String toString()
{
if (_theTransaction == null)
return "TransactionImple < ac, NoTransaction >";
else
{
return "TransactionImple < ac, " + _theTransaction + " >";
}
}
public int getXAResourceState(XAResource xaRes)
{
int state = TxInfo.UNKNOWN;
if (xaRes != null)
{
TxInfo info = (TxInfo) _resources.get(xaRes);
if (info == null)
{
info = (TxInfo) _duplicateResources.get(xaRes);
}
if (info != null)
state = info.getState();
}
return state;
}
public static final TransactionImple getTransaction()
{
TransactionImple tx = null;
final BasicAction current = BasicAction.Current();
if (current != null)
{
final Uid txid = current.get_uid();
tx = (TransactionImple) _transactions.get(txid);
if (tx == null)
tx = new TransactionImple(current);
}
return tx;
}
public static final TransactionImple getTransaction(Uid id)
{
try
{
if (id != null)
return (TransactionImple) _transactions.get(id);
else
return null;
}
catch (Exception e)
{
return new TransactionImple(null);
}
}
// get a key-value pair from a transaction specific Map
Object getTxLocalResource(Object key)
{
if (txLocalResources == null)
{
return null;
}
return txLocalResources.get(key);
}
// store a key-value pair in the scope of the transaction.
void putTxLocalResource(Object key, Object value)
{
if (txLocalResources == null)
{
txLocalResources = Collections.synchronizedMap(new HashMap());
}
txLocalResources.put(key, value);
}
protected TransactionImple()
{
this(null);
}
/**
* Create a new TransactionImple representation of a specified transaction.
*/
protected TransactionImple(BasicAction curr)
{
try
{
if (curr == null)
{
_theTransaction = (com.arjuna.ats.arjuna.AtomicAction) BasicAction
.Current();
}
else
_theTransaction = (com.arjuna.ats.arjuna.AtomicAction) curr;
}
catch (ClassCastException ex)
{
if (jtaLogger.loggerI18N.isWarnEnabled())
{
jtaLogger.loggerI18N
.warn("com.arjuna.ats.internal.jta.transaction.arjunacore.notatomicaction");
}
}
if (_theTransaction != null)
{
_resources = new Hashtable();
_duplicateResources = new Hashtable();
}
else
{
_resources = null;
_duplicateResources = null;
}
_suspendCount = 0;
_xaTransactionTimeoutEnabled = getXATransactionTimeoutEnabled();
}
final com.arjuna.ats.arjuna.AtomicAction getAtomicAction()
{
return _theTransaction;
}
/**
* Does the same as commit, but also changes the thread-to-tx association.
*/
protected void commitAndDisassociate()
throws javax.transaction.RollbackException,
javax.transaction.HeuristicMixedException,
javax.transaction.HeuristicRollbackException,
java.lang.SecurityException, javax.transaction.SystemException,
java.lang.IllegalStateException
{
if (jtaLogger.logger.isDebugEnabled())
{
jtaLogger.logger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, com.arjuna.ats.jta.logging.FacilityCode.FAC_JTA, "TransactionImple.commitAndDisassociate");
}
try
{
if (_theTransaction != null)
{
switch (_theTransaction.status())
{
case ActionStatus.ABORTED:
case ActionStatus.ABORTING:
_theTransaction.abort(); // assure thread disassociation
throw new IllegalStateException(
jtaLogger.logMesg.getString("com.arjuna.ats.internal.jta.transaction.arjunacore.inactive"));
case ActionStatus.COMMITTED:
case ActionStatus.COMMITTING: // in case of async commit
_theTransaction.commit(true); // assure thread disassociation
throw new IllegalStateException(
jtaLogger.logMesg.getString("com.arjuna.ats.internal.jta.transaction.arjunacore.inactive"));
}
switch (_theTransaction.commit(true))
{
case ActionStatus.COMMITTED:
case ActionStatus.COMMITTING: // in case of async commit
break;
case ActionStatus.H_MIXED:
throw new javax.transaction.HeuristicMixedException();
case ActionStatus.H_HAZARD:
throw new javax.transaction.HeuristicMixedException();
case ActionStatus.H_ROLLBACK:
case ActionStatus.ABORTED:
case ActionStatus.ABORTING:
RollbackException rollbackException = new RollbackException(jtaLogger.logMesg.getString("com.arjuna.ats.internal.jta.transaction.arjunacore.commitwhenaborted"));
if(_theTransaction.getDeferredThrowable() != null) {
rollbackException.initCause(_theTransaction.getDeferredThrowable());
}
throw rollbackException;
default:
throw new IllegalStateException(
jtaLogger.logMesg.getString("com.arjuna.ats.internal.jta.transaction.arjunacore.invalidstate"));
}
}
else
throw new IllegalStateException(
jtaLogger.logMesg.getString("com.arjuna.ats.internal.jta.transaction.arjunacore.inactive"));
}
finally
{
TransactionImple.removeTransaction(this);
}
}
/**
* If this is an imported transaction (via JCA) then this will be the Xid we
* are pretending to be. Otherwise, it will be null.
*
* @return null if we are a local transaction, a valid Xid if we have been
* imported.
*/
protected Xid baseXid()
{
return null;
}
/**
* Does the same as rollback, but also changes the thread-to-tx association.
*/
protected void rollbackAndDisassociate()
throws java.lang.IllegalStateException,
java.lang.SecurityException, javax.transaction.SystemException
{
if (jtaLogger.logger.isDebugEnabled())
{
jtaLogger.logger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, com.arjuna.ats.jta.logging.FacilityCode.FAC_JTA, "TransactionImple.rollbackAndDisassociate");
}
try
{
boolean statusIsValid = false;
if (_theTransaction != null)
{
if(_theTransaction.status() == ActionStatus.RUNNING || _theTransaction.status() == ActionStatus.ABORT_ONLY) {
// in these cases we may be able to finish without throwing an exception, if nothing else goes wrong...
statusIsValid = true;
}
int outcome = _theTransaction.abort(); // assure thread disassociation, even if tx is already done.
switch (outcome)
{
case ActionStatus.ABORTED:
case ActionStatus.ABORTING: // in case of async rollback
break;
default:
throw new IllegalStateException(
jtaLogger.logMesg.getString("com.arjuna.ats.internal.jta.transaction.arjunacore.rollbackstatus")
+ ActionStatus.stringForm(outcome));
}
}
if(_theTransaction == null || !statusIsValid) {
throw new IllegalStateException(
jtaLogger.logMesg.getString("com.arjuna.ats.internal.jta.transaction.arjunacore.inactive"));
}
}
finally
{
TransactionImple.removeTransaction(this);
}
}
/**
* If there are any suspended RMs then we should call end on them before the
* transaction is terminated.
*
* @message com.arjuna.ats.internal.jta.transaction.arjunacore.xaenderror
* [com.arjuna.ats.internal.jta.transaction.arjunacore.xaenderror]
* Could not call end on a suspended resource!
*/
protected boolean endSuspendedRMs()
{
boolean result = true;
if (_suspendCount > 0)
{
Enumeration el = _resources.keys();
/*
* Loop over all registered resources. Those that are in a suspended
* state must have end called on them. If this fails, then we will
* eventually roll back the transaction, but we will continue down
* the list to try to end any other suspended resources.
*/
if (el != null)
{
try
{
/*
* Would it gain us much to just loop for _suspendCount?
*/
while (el.hasMoreElements())
{
/*
* Get the XAResource in case we have to call end on it.
*/
XAResource xaRes = (XAResource) el.nextElement();
TxInfo info = (TxInfo) _resources.get(xaRes);
if (info.getState() == TxInfo.ASSOCIATION_SUSPENDED)
{
if (XAUtils.mustEndSuspendedRMs(xaRes))
xaRes.start(info.xid(), XAResource.TMRESUME);
xaRes.end(info.xid(), XAResource.TMSUCCESS);
info.setState(TxInfo.NOT_ASSOCIATED);
}
}
}
catch (XAException ex)
{
if (jtaLogger.loggerI18N.isWarnEnabled())
{
jtaLogger.loggerI18N
.warn("com.arjuna.ats.internal.jta.transaction.arjunacore.xaenderror");
}
result = false;
}
}
_suspendCount = 0;
}
return result;
}
/*
* If this thread has already registered a resource for the same db then
* don't use this copy. For some databases it would actually be ok for us to
* use the resource (at least to do an xa_start equivalent on it), but for
* Oracle 8.1.6 it causes their JDBC driver to crash!
*/
/**
* @message com.arjuna.ats.internal.jta.transaction.arjunacore.threadexception
* [com.arjuna.ats.internal.jta.transaction.arjunacore.threadexception]
* Caught the following error: {0}
*/
private final boolean threadIsActive(XAResource xaRes)
{
Thread t = Thread.currentThread();
try
{
Enumeration el = _resources.keys();
if (el != null)
{
while (el.hasMoreElements())
{
XAResource x = (XAResource) el.nextElement();
if (x.isSameRM(xaRes))
{
TxInfo info = (TxInfo) _resources.get(x);
if (info.thread() == t)
return true;
}
}
}
el = _duplicateResources.keys();
if (el != null)
{
while (el.hasMoreElements())
{
XAResource x = (XAResource) el.nextElement();
if (x.isSameRM(xaRes))
{
TxInfo info = (TxInfo) _resources.get(x);
if (info.thread() == t)
return true;
}
}
}
}
catch (Exception e)
{
if (jtaLogger.loggerI18N.isWarnEnabled())
{
jtaLogger.loggerI18N
.warn(
"com.arjuna.ats.internal.jta.transaction.arjunacore.threadexception",
new Object[]
{ e });
}
throw new com.arjuna.ats.arjuna.exceptions.FatalError(e.toString());
}
return false;
}
/**
* isNewRM returns an existing TxInfo for the same RM, if present. Null
* otherwise.
*
* @message com.arjuna.ats.internal.jta.transaction.arjunacore.newtmerror
* [com.arjuna.ats.internal.jta.transaction.arjunacore.newtmerror]
* {0} caught XAException: {0}
* @message com.arjuna.ats.internal.jta.transaction.arjunacore.isnewrm
* [com.arjuna.ats.internal.jta.transaction.arjunacore.isnewrm]
* Caught unexpected exception: {0}
*/
private final TxInfo isNewRM(XAResource xaRes)
{
try
{
synchronized (this)
{
Enumeration el = _resources.keys();
if (el != null)
{
while (el.hasMoreElements())
{
XAResource x = (XAResource) el.nextElement();
if (x.isSameRM(xaRes))
{
return (TxInfo) _resources.get(x);
}
}
}
el = _duplicateResources.keys();
if (el != null)
{
while (el.hasMoreElements())
{
XAResource x = (XAResource) el.nextElement();
if (x.isSameRM(xaRes))
{
return (TxInfo) _duplicateResources.get(x);
}
}
}
}
}
catch (XAException ex)
{
if (jtaLogger.loggerI18N.isWarnEnabled())
{
jtaLogger.loggerI18N
.warn(
"com.arjuna.ats.internal.jta.transaction.arjunacore.newtmerror",
new Object[]
{ "TransactionImple.isNewRM",
XAHelper.printXAErrorCode(ex) });
}
throw new com.arjuna.ats.arjuna.exceptions.FatalError(ex.toString());
}
catch (Exception e)
{
if (jtaLogger.loggerI18N.isWarnEnabled())
{
jtaLogger.loggerI18N
.warn(
"com.arjuna.ats.internal.jta.transaction.arjunacore.newtmerror",
new Object[]
{ e });
}
throw new com.arjuna.ats.arjuna.exceptions.FatalError(e.toString());
}
return null;
}
private final Xid createXid(boolean branch, XAModifier theModifier)
{
Xid xid = baseXid();
if (xid != null)
return xid;
xid = new XidImple(_theTransaction, branch);
if (theModifier != null)
{
try
{
xid = theModifier.createXid((XidImple) xid);
}
catch (Exception e)
{
e.printStackTrace();
}
}
return xid;
}
/*
* This method calls setRollbackOnly and catches any exceptions it may throw
* and issues a warning. We use this in places where we need to force the
* outcome of the transaction but already have an exception to throw back to
* the application, so a failure here will only be masked.
*/
/**
* @message com.arjuna.ats.internal.jta.transaction.arjunacore.markrollback
* [com.arjuna.ats.internal.jta.transaction.arjunacore.markrollback]
* {0} - could not mark {0} as rollback only
*/
private final void markRollbackOnly()
{
try
{
setRollbackOnly();
}
catch (Exception ex)
{
if (jtaLogger.loggerI18N.isWarnEnabled())
{
jtaLogger.loggerI18N
.warn(
"com.arjuna.ats.internal.jta.transaction.arjunacore.markrollback",
new Object[]
{ "TransactionImple.markRollbackOnly",
_theTransaction });
}
}
}
/*
* Add and remove transactions from list.
*/
static final protected void putTransaction(TransactionImple tx)
{
_transactions.put(tx.get_uid(), tx);
}
static final protected void removeTransaction(TransactionImple tx)
{
_transactions.remove(tx.get_uid());
}
private static boolean getXATransactionTimeoutEnabled()
{
final Boolean xaTransactionTimeoutEnabled = Configuration
.getXATransactionTimeoutEnabled();
if (xaTransactionTimeoutEnabled != null)
{
return xaTransactionTimeoutEnabled.booleanValue();
}
return XA_TRANSACTION_TIMEOUT_ENABLED;
}
protected com.arjuna.ats.arjuna.AtomicAction _theTransaction;
private Hashtable _resources;
private Hashtable _duplicateResources;
private int _suspendCount;
private final boolean _xaTransactionTimeoutEnabled;
private Map txLocalResources;
/**
* Count of last resources seen in this transaction.
*/
private int lastResourceCount;
/**
* Do we allow multiple last resources?
*/
private static final boolean ALLOW_MULTIPLE_LAST_RESOURCES;
private static final boolean XA_TRANSACTION_TIMEOUT_ENABLED;
private static final Class LAST_RESOURCE_OPTIMISATION_INTERFACE;
static
{
final String xaTransactionTimeoutEnabled = jtaPropertyManager.propertyManager
.getProperty(Environment.XA_TRANSACTION_TIMEOUT_ENABLED);
if (xaTransactionTimeoutEnabled != null)
{
XA_TRANSACTION_TIMEOUT_ENABLED = Boolean.valueOf(
xaTransactionTimeoutEnabled).booleanValue();
}
else
{
XA_TRANSACTION_TIMEOUT_ENABLED = true;
}
final String lastResourceOptimisationInterfaceName = jtaPropertyManager.propertyManager
.getProperty(Environment.LAST_RESOURCE_OPTIMISATION_INTERFACE);
Class lastResourceOptimisationInterface = null;
if (lastResourceOptimisationInterfaceName != null)
{
try
{
lastResourceOptimisationInterface = Thread.currentThread()
.getContextClassLoader().loadClass(
lastResourceOptimisationInterfaceName);
}
catch (final Throwable th)
{
if (jtaLogger.loggerI18N.isWarnEnabled())
{
jtaLogger.loggerI18N
.warn(
"com.arjuna.ats.internal.jta.transaction.arjunacore.lastResourceOptimisationInterface",
new Object[]
{ lastResourceOptimisationInterfaceName },
th);
}
}
}
LAST_RESOURCE_OPTIMISATION_INTERFACE = lastResourceOptimisationInterface;
final String allowMultipleLastResources = jtaPropertyManager
.getPropertyManager().getProperty(
Environment.ALLOW_MULTIPLE_LAST_RESOURCES);
ALLOW_MULTIPLE_LAST_RESOURCES = (allowMultipleLastResources == null ? false
: Boolean.valueOf(allowMultipleLastResources).booleanValue());
if (ALLOW_MULTIPLE_LAST_RESOURCES
&& jtaLogger.loggerI18N.isWarnEnabled())
{
jtaLogger.loggerI18N
.warn("com.arjuna.ats.internal.jta.transaction.arjunacore.lastResource.startupWarning");
}
}
private static ConcurrentHashMap _transactions = new ConcurrentHashMap();
}