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));
// Pay attention now, this bit is hairy. We need to add a new AbstractRecord (XAResourceRecord)
// to the BasicAction, which will thereafter drive its completion. However, the transaction
// core is not directly XA aware, so it's our job to start the XAResource. Problem is, if
// adding the record fails, BasicAction will never end the resource via the XAResourceRecord,
// so we must do so directly. start may fail due to dupl xid or other reason, and transactions
// may rollback async, for which reasons we can't call add before start.
// The xid will change on each pass of the loop, so we need to create a new record on each pass.
// The creation will fail in the case of multiple last resources being disallowed, in which
// case we don't call start on the resource at all. see JBTM-362 and JBTM-363
AbstractRecord abstractRecord = createRecord(xaRes, params, xid);
if(abstractRecord != null) {
xaRes.start(xid, xaStartNormal);
if(_theTransaction.add(abstractRecord) == AddOutcome.AR_ADDED) {
_resources.put(xaRes, new TxInfo(xid));
return true; // dive out, no need to set associatedWork = true;
} else {
// we called start on the resource, but _theTransaction did not accept it.
// we therefore have a mess which we must now clean up by ensuring the start is undone:
abstractRecord.topLevelAbort();
}
}
// if we get to here, something other than a failure of xaRes.start probably went wrong.
// so we don't loop and retry, we just give up.
markRollbackOnly();
return false;
}
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)
{