/*
* 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) 2005,
*
* Arjuna Technologies Ltd,
* Newcastle upon Tyne,
* Tyne and Wear,
* UK.
*
* $Id: XATerminatorImple.java 2342 2006-03-30 13:06:17Z $
*/
package com.arjuna.ats.internal.jta.transaction.arjunacore.jca;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.Stack;
import javax.transaction.HeuristicCommitException;
import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.RollbackException;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import com.arjuna.ats.arjuna.common.Uid;
import com.arjuna.ats.arjuna.coordinator.TwoPhaseOutcome;
import com.arjuna.ats.arjuna.coordinator.TxControl;
import com.arjuna.ats.arjuna.objectstore.RecoveryStore;
import com.arjuna.ats.arjuna.objectstore.StoreManager;
import com.arjuna.ats.arjuna.state.InputObjectState;
import com.arjuna.ats.internal.arjuna.common.UidHelper;
import com.arjuna.ats.internal.jta.recovery.arjunacore.XARecoveryModule;
import com.arjuna.ats.internal.jta.resources.spi.XATerminatorExtensions;
import com.arjuna.ats.internal.jta.transaction.arjunacore.subordinate.jca.SubordinateAtomicAction;
import com.arjuna.ats.internal.jta.transaction.arjunacore.subordinate.jca.TransactionImple;
import com.arjuna.ats.jta.exceptions.UnexpectedConditionException;
import com.arjuna.ats.jta.logging.jtaLogger;
import com.arjuna.ats.jta.xa.XATxConverter;
import com.arjuna.ats.jta.xa.XidImple;
import org.jboss.tm.ExtendedJBossXATerminator;
import org.jboss.tm.TransactionImportResult;
/**
* The XATerminator implementation.
*
* @author mcl
*/
public class XATerminatorImple implements javax.resource.spi.XATerminator, XATerminatorExtensions, ExtendedJBossXATerminator
{
/**
* Commit the transaction identified and hence any inflow-associated work.
*
* @param xid
* the transaction to commit
* @param onePhase
* whether or not this is a one-phase commit (should only be
* <code>true</code> if there is a single resource associated
* with the transaction).
* @exception XAException
* thrown if there are any errors, including if the
* transaction cannot commit and has to roll back.
*/
public void commit (Xid xid, boolean onePhase) throws XAException
{
try
{
SubordinateTransaction tx = SubordinationManager
.getTransactionImporter().getImportedTransaction(xid);
if (tx == null)
throw new XAException(XAException.XAER_INVAL);
if (tx.activated())
{
if (onePhase)
tx.doOnePhaseCommit();
else
if (!tx.doCommit()) {
throw new XAException(XAException.XAER_RMFAIL);
}
SubordinationManager.getTransactionImporter()
.removeImportedTransaction(xid);
}
else
throw new XAException(XAException.XA_RETRY);
}
catch (RollbackException e)
{
SubordinationManager.getTransactionImporter()
.removeImportedTransaction(xid);
XAException xaException = new XAException(XAException.XA_RBROLLBACK);
xaException.initCause(e);
throw xaException;
}
catch (XAException ex)
{
// resource hasn't had a chance to recover yet
if (ex.errorCode != XAException.XA_RETRY)
{
SubordinationManager.getTransactionImporter()
.removeImportedTransaction(xid);
}
throw ex;
}
catch (HeuristicRollbackException ex)
{
XAException xaException = new XAException(XAException.XA_HEURRB);
xaException.initCause(ex);
throw xaException;
}
catch (HeuristicMixedException ex)
{
XAException xaException = new XAException(XAException.XA_HEURMIX);
xaException.initCause(ex);
throw xaException;
}
catch (final HeuristicCommitException ex)
{
XAException xaException = new XAException(XAException.XA_HEURCOM);
xaException.initCause(ex);
throw xaException;
}
catch (final IllegalStateException ex)
{
SubordinationManager.getTransactionImporter()
.removeImportedTransaction(xid);
XAException xaException = new XAException(XAException.XAER_NOTA);
xaException.initCause(ex);
throw xaException;
}
catch (SystemException ex)
{
SubordinationManager.getTransactionImporter()
.removeImportedTransaction(xid);
XAException xaException = new XAException(XAException.XAER_RMERR);
xaException.initCause(ex);
throw xaException;
}
}
/**
* If the transaction subordinate generated a heuristic, then this operation
* will be called later once that heuristic has been resolved.
*
* @param xid
* the transaction.
* @throws XAException
* if any error happens.
*/
public void forget (Xid xid) throws XAException
{
try
{
SubordinateTransaction tx = SubordinationManager
.getTransactionImporter().getImportedTransaction(xid);
if (tx == null)
throw new XAException(XAException.XAER_INVAL);
tx.doForget();
}
catch (Exception ex)
{
XAException xaException = new XAException(XAException.XAER_RMERR);
xaException.initCause(ex);
throw xaException;
}
finally
{
SubordinationManager.getTransactionImporter()
.removeImportedTransaction(xid);
}
}
/**
* Prepare the imported transaction.
*
* @param xid
* the transaction to prepare.
* @throws XAException
* thrown if any error occurs, including if the transaction has
* rolled back.
* @return either XAResource.XA_OK if the transaction prepared, or
* XAResource.XA_RDONLY if it was a read-only transaction (and in
* which case, a second phase message is not expected/required.)
*/
public int prepare (Xid xid) throws XAException
{
// JBTM-927 this can happen if the transaction has been rolled back by the TransactionReaper
SubordinateTransaction tx = null;
try {
tx = SubordinationManager.getTransactionImporter().getImportedTransaction(xid);
} catch (XAException xae) {
if (xae.errorCode == XAException.XA_RBROLLBACK) {
SubordinationManager.getTransactionImporter().removeImportedTransaction(xid);
}
throw xae;
}
try
{
if (tx == null)
throw new XAException(XAException.XAER_INVAL);
switch (tx.doPrepare())
{
case TwoPhaseOutcome.PREPARE_READONLY:
SubordinationManager.getTransactionImporter()
.removeImportedTransaction(xid);
return XAResource.XA_RDONLY;
case TwoPhaseOutcome.PREPARE_NOTOK:
// the JCA API spec limits what we can do in terms of reporting
// problems.
// try to use the exception code and cause to provide info
// whilst
// remaining API compliant. JBTM-427.
Exception initCause = null;
int xaExceptionCode = XAException.XA_RBROLLBACK;
try
{
tx.doRollback();
}
catch (HeuristicCommitException e)
{
initCause = e;
xaExceptionCode = XAException.XAER_RMERR;
}
catch (HeuristicMixedException e)
{
initCause = e;
xaExceptionCode = XAException.XAER_RMERR;
}
catch (SystemException e)
{
initCause = e;
xaExceptionCode = XAException.XAER_RMERR;
}
catch (final HeuristicRollbackException e)
{
initCause = e;
xaExceptionCode = XAException.XAER_RMERR;
}
SubordinationManager.getTransactionImporter()
.removeImportedTransaction(xid);
XAException xaException = new XAException(xaExceptionCode);
if (initCause != null)
{
xaException.initCause(initCause);
}
throw xaException;
case TwoPhaseOutcome.PREPARE_OK:
return XAResource.XA_OK;
case TwoPhaseOutcome.INVALID_TRANSACTION:
throw new XAException(XAException.XAER_NOTA);
default:
throw new XAException(XAException.XA_RBOTHER);
}
}
catch (XAException ex)
{
throw ex;
}
}
/**
* Return a list of indoubt transactions. This may include those
* transactions that are currently in-flight and running 2PC and do not need
* recovery invoked on them.
*
* @param flag
* either XAResource.TMSTARTRSCAN to indicate the start of a
* recovery scan, or XAResource.TMENDRSCAN to indicate the end of
* the recovery scan.
* @throws XAException
* thrown if any error occurs.
* @return a list of potentially indoubt transactions or <code>null</code>.
*/
public Xid[] recover (int flag) throws XAException
{
/*
* Requires going through the objectstore for the states of imported
* transactions. Our own crash recovery takes care of transactions
* imported via CORBA, Web Services etc.
*/
switch (flag)
{
case XAResource.TMSTARTRSCAN: // check the object store
if (_recoveryStarted)
throw new XAException(XAException.XAER_PROTO);
else {
_recoveryStarted = true;
if (XARecoveryModule.getRegisteredXARecoveryModule() != null) {
XARecoveryModule.getRegisteredXARecoveryModule().periodicWorkFirstPass();
}
}
break;
case XAResource.TMENDRSCAN: // null op for us
if (_recoveryStarted) {
_recoveryStarted = false;
if (XARecoveryModule.getRegisteredXARecoveryModule() != null) {
XARecoveryModule.getRegisteredXARecoveryModule().periodicWorkSecondPass();
}
}
else
throw new XAException(XAException.XAER_PROTO);
return null;
case XAResource.TMNOFLAGS:
if (_recoveryStarted)
break;
default:
throw new XAException(XAException.XAER_PROTO);
}
// if we are here, then check the object store
return doRecover(null, null);
}
/**
* Return a list of indoubt transactions. This may include those
* transactions that are currently in-flight and running 2PC and do not need
* recovery invoked on them.
*
* @param nodeName
* Only recover transactions for this node (unless set to NodeNameXAResourceOrphanFilter.RECOVER_ALL_NODES)
* @throws XAException
* thrown if any error occurs.
* @return a list of potentially indoubt transactions or <code>null</code>.
*/
public Xid[] doRecover (Xid xid, String parentNodeName) throws XAException
{
/*
* Requires going through the objectstore for the states of imported
* transactions. Our own crash recovery takes care of transactions
* imported via CORBA, Web Services etc.
*/
Xid[] indoubt = null;
try
{
RecoveryStore recoveryStore = StoreManager.getRecoveryStore();
InputObjectState states = new InputObjectState();
// only look in the JCA section of the object store
if (recoveryStore.allObjUids(SubordinateAtomicAction.getType(), states)
&& (states.notempty()))
{
Stack<Xid> values = new Stack<Xid>();
boolean finished = false;
do
{
Uid uid = null;
try
{
uid = UidHelper.unpackFrom(states);
}
catch (IOException ex)
{
ex.printStackTrace();
finished = true;
}
if (uid.notEquals(Uid.nullUid()))
{
if (parentNodeName != null) {
SubordinateAtomicAction saa = new SubordinateAtomicAction(uid, true);
XidImple loadedXid = (XidImple) saa.getXid();
if (loadedXid != null && loadedXid.getFormatId() == XATxConverter.FORMAT_ID) {
String loadedXidSubordinateNodeName = XATxConverter.getSubordinateNodeName(loadedXid.getXID());
if ((loadedXidSubordinateNodeName == null && loadedXidSubordinateNodeName == TxControl.getXANodeName())
|| loadedXidSubordinateNodeName.equals(TxControl.getXANodeName())) {
if (parentNodeName.equals(saa.getParentNodeName())) {
if (jtaLogger.logger.isDebugEnabled()) {
jtaLogger.logger.debug("Found record for " + saa);
}
// TransactionImple tx = (TransactionImple) SubordinationManager.getTransactionImporter().recoverTransaction(uid);
values.push(loadedXid);
}
}
}
} else if (xid == null) {
TransactionImple tx = (TransactionImple) SubordinationManager.getTransactionImporter().recoverTransaction(uid);
if (tx != null)
values.push(tx.baseXid());
} else {
SubordinateAtomicAction saa = new SubordinateAtomicAction(uid, true);
XidImple loadedXid = (XidImple) saa.getXid();
if (loadedXid != null && loadedXid.getFormatId() == XATxConverter.FORMAT_ID) {
String loadedXidSubordinateNodeName = XATxConverter.getSubordinateNodeName(loadedXid.getXID());
if (XATxConverter.getSubordinateNodeName(new XidImple(xid).getXID()).equals(loadedXidSubordinateNodeName)) {
if (Arrays.equals(loadedXid.getGlobalTransactionId(), xid.getGlobalTransactionId())) {
if (jtaLogger.logger.isDebugEnabled()) {
jtaLogger.logger.debug("Found record for " + saa);
}
TransactionImple tx = (TransactionImple) SubordinationManager.getTransactionImporter().recoverTransaction(uid);
values.push(loadedXid);
}
}
}
}
}
else
finished = true;
}
while (!finished);
if (values.size() > 0)
{
int index = 0;
indoubt = new Xid[values.size()];
while (!values.empty())
{
indoubt[index] = values.pop();
index++;
}
}
}
}
catch (Exception ex)
{
ex.printStackTrace();
}
return indoubt;
}
@Override
public boolean isRecoveryByNodeOrXidSupported() {
return true;
}
/**
* Rollback the imported transaction subordinate.
*
* @param xid
* the transaction to roll back.
* @throws XAException
* thrown if there are any errors.
*/
public void rollback (Xid xid) throws XAException
{
// JBTM-927 this can happen if the transaction has been rolled back by
// the TransactionReaper
SubordinateTransaction tx = null;
try {
tx = SubordinationManager.getTransactionImporter().getImportedTransaction(xid);
} catch (XAException xae) {
if (xae.errorCode == XAException.XA_RBROLLBACK) {
SubordinationManager.getTransactionImporter().removeImportedTransaction(xid);
return;
}
throw xae;
}
try
{
if (tx == null)
throw new XAException(XAException.XAER_INVAL);
if (tx.activated())
{
tx.doRollback();
SubordinationManager.getTransactionImporter()
.removeImportedTransaction(xid);
}
else
throw new XAException(XAException.XA_RETRY);
}
catch (XAException ex)
{
// resource hasn't had a chance to recover yet
if (ex.errorCode != XAException.XA_RETRY)
{
SubordinationManager.getTransactionImporter()
.removeImportedTransaction(xid);
}
throw ex;
}
catch (final HeuristicRollbackException ex)
{
XAException xaException = new XAException(XAException.XA_HEURRB);
xaException.initCause(ex);
throw xaException;
}
catch (HeuristicCommitException ex)
{
XAException xaException = new XAException(XAException.XA_HEURCOM);
xaException.initCause(ex);
throw xaException;
}
catch (HeuristicMixedException ex)
{
XAException xaException = new XAException(XAException.XA_HEURMIX);
xaException.initCause(ex);
throw xaException;
}
catch (final IllegalStateException ex)
{
SubordinationManager.getTransactionImporter()
.removeImportedTransaction(xid);
XAException xaException = new XAException(XAException.XAER_NOTA);
xaException.initCause(ex);
throw xaException;
}
catch (SystemException ex)
{
SubordinationManager.getTransactionImporter()
.removeImportedTransaction(xid);
throw new XAException(XAException.XAER_RMERR);
}
}
public boolean beforeCompletion (Xid xid) throws javax.transaction.SystemException
{
try
{
SubordinateTransaction tx = SubordinationManager
.getTransactionImporter().getImportedTransaction(xid);
if (tx == null)
throw new UnexpectedConditionException();
return tx.doBeforeCompletion();
}
catch (final Exception ex)
{
UnexpectedConditionException e = new UnexpectedConditionException();
e.initCause(ex);
throw e;
}
}
public Transaction getTransaction(Xid xid) throws XAException {
// first see if the xid is a root coordinator
Transaction transaction = TransactionImple.getTransaction(new XidImple(xid).getTransactionUid());
// second see if the xid is a subordinate txn
if(transaction == null) {
transaction = SubordinationManager.getTransactionImporter().getImportedTransaction(xid);
}
return transaction;
}
public TransactionImportResult importTransaction(Xid xid, int timeoutIfNew) throws XAException {
return SubordinationManager.getTransactionImporter().importRemoteTransaction(xid, timeoutIfNew);
}
public SubordinateTransaction getImportedTransaction(Xid xid) throws XAException {
final TransactionImporter transactionImporter = SubordinationManager.getTransactionImporter();
return transactionImporter.getImportedTransaction(xid);
}
public Transaction getTransactionById(Object id) {
if (id instanceof Uid)
return TransactionImple.getTransaction((Uid) id);
return null;
}
public Object getCurrentTransactionId() {
com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionImple transaction = TransactionImple.getTransaction();
if (transaction == null)
return null;
return transaction.get_uid();
}
public void removeImportedTransaction(Xid xid) throws XAException {
SubordinationManager.getTransactionImporter().removeImportedTransaction(xid);
}
public Xid[] getXidsToRecoverForParentNode(boolean recoverInFlight, String parentNodeName, int recoveryFlags) throws XAException {
final Set<Xid> xidsToRecover = new HashSet<Xid>();
if (recoverInFlight) {
final TransactionImporter transactionImporter = SubordinationManager.getTransactionImporter();
if (transactionImporter instanceof TransactionImporterImple) {
final Set<Xid> inFlightXids = ((TransactionImporterImple) transactionImporter).getInflightXids(parentNodeName);
if (inFlightXids != null) {
xidsToRecover.addAll(inFlightXids);
}
}
}
final javax.resource.spi.XATerminator xaTerminator = SubordinationManager.getXATerminator();
if (xaTerminator instanceof XATerminatorImple) {
final Xid[] inDoubtTransactions = ((XATerminatorImple) xaTerminator).doRecover(null, parentNodeName);
if (inDoubtTransactions != null) {
xidsToRecover.addAll(Arrays.asList(inDoubtTransactions));
}
} else {
final Xid[] inDoubtTransactions = xaTerminator.recover(recoveryFlags);
if (inDoubtTransactions != null) {
xidsToRecover.addAll(Arrays.asList(inDoubtTransactions));
}
}
return xidsToRecover.toArray(NO_XIDS);
}
private boolean _recoveryStarted = false;
private static final Xid[] NO_XIDS = new Xid[0];
}