/*
* 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: TransactionImporterImple.java 2342 2006-03-30 13:06:17Z $
*/
package com.arjuna.ats.internal.jta.transaction.jts.jca;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import javax.transaction.SystemException;
import javax.transaction.xa.XAException;
import javax.transaction.xa.Xid;
import com.arjuna.ats.arjuna.common.Uid;
import com.arjuna.ats.internal.jta.transaction.arjunacore.jca.SubordinateTransaction;
import com.arjuna.ats.internal.jta.transaction.arjunacore.jca.SubordinateXidImple;
import com.arjuna.ats.internal.jta.transaction.arjunacore.jca.TransactionImporter;
import com.arjuna.ats.internal.jta.transaction.jts.subordinate.jca.TransactionImple;
import org.jboss.tm.TransactionImportResult;
public class TransactionImporterImple implements TransactionImporter
{
/**
* Create a subordinate transaction associated with the
* global transaction inflow. No timeout is associated with the
* transaction.
*
* @param xid the global transaction.
*
* @return the subordinate transaction.
*
* @throws XAException thrown if there are any errors.
*/
public SubordinateTransaction importTransaction (Xid xid) throws XAException
{
return (SubordinateTransaction) importRemoteTransaction(xid, 0).getTransaction();
}
@Override
public SubordinateTransaction importTransaction(Xid xid, int timeout) throws XAException {
return (SubordinateTransaction) importRemoteTransaction(xid, timeout).getTransaction();
}
/**
* Create a subordinate transaction associated with the
* global transaction inflow and having a specified timeout.
*
* @param xid the global transaction.
* @param timeout the timeout associated with the global transaction.
*
* @return the subordinate transaction.
*
* @throws XAException thrown if there are any errors.
*/
public TransactionImportResult importRemoteTransaction(Xid xid, int timeout) throws XAException
{
if (xid == null)
throw new IllegalArgumentException();
return addImportedTransaction(null, new SubordinateXidImple(xid), xid, timeout);
}
public SubordinateTransaction recoverTransaction (Uid actId) throws XAException
{
if (actId == null)
throw new IllegalArgumentException();
TransactionImple recovered = new TransactionImple(actId);
if (recovered.baseXid() == null)
throw new IllegalArgumentException();
return (SubordinateTransaction) addImportedTransaction(recovered, recovered.baseXid(), null, 0).getTransaction();
}
/**
* Get the subordinate (imported) transaction associated with the
* global transaction.
*
* @param xid the global transaction.
*
* @return the subordinate transaction or <code>null</code> if there
* is none.
*
* @throws XAException thrown if there are any errors.
*/
public SubordinateTransaction getImportedTransaction (Xid xid) throws XAException
{
if (xid == null)
throw new IllegalArgumentException();
AtomicReference<SubordinateTransaction> holder = _transactions.get(new SubordinateXidImple(xid));
SubordinateTransaction tx = holder == null ? null : holder.get();
if (tx == null) {
/*
* Remark: if holder != null and holder.get() == null then the setter is about to
* import the transaction but has not yet updated the holder. We implement the getter
* (the thing that is trying to terminate the imported transaction) as though the imported
* transaction only becomes observable when it has been fully imported.
*/
return null;
}
// https://issues.jboss.org/browse/JBTM-927
try {
if (tx.getStatus() == javax.transaction.Status.STATUS_ROLLEDBACK) {
throw new XAException(XAException.XA_RBROLLBACK);
}
} catch (SystemException e) {
throw new XAException(XAException.XA_RBROLLBACK);
}
if (tx.baseXid() == null)
{
/*
* Try recovery again. If it fails we'll throw a RETRY to the caller who
* should try again later.
*/
tx.recover();
return tx;
}
else
return tx;
}
/**
* Remove the subordinate (imported) transaction.
*
* @param xid the global transaction.
*
* @throws XAException thrown if there are any errors.
*/
public void removeImportedTransaction (Xid xid) throws XAException
{
if (xid == null)
throw new IllegalArgumentException();
AtomicReference<SubordinateTransaction> remove = _transactions.remove(new SubordinateXidImple(xid));
if (remove != null) {
synchronized (remove) {
com.arjuna.ats.internal.jta.transaction.jts.TransactionImple transactionImple = (com.arjuna.ats.internal.jta.transaction.jts.TransactionImple) remove.get();
while (transactionImple == null) {
try {
remove.wait();
transactionImple = (com.arjuna.ats.internal.jta.transaction.jts.TransactionImple) remove.get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new XAException(XAException.XAER_RMFAIL);
}
}
TransactionImple.removeTransaction(transactionImple);
}
}
}
/**
* This can be used for newly imported transactions or recovered ones.
*
* @param recoveredTransaction If this is recovery
* @param xid if this is import
* @param timeout
* @return
*/
private TransactionImportResult addImportedTransaction(TransactionImple recoveredTransaction, Xid mapKey, Xid xid, int timeout) {
boolean isNew = false;
SubordinateXidImple importedXid = new SubordinateXidImple(mapKey);
// We need to store the imported transaction in a volatile field holder so that it can be shared between threads
AtomicReference<SubordinateTransaction> holder = new AtomicReference<>();
AtomicReference<SubordinateTransaction> existing;
if ((existing = _transactions.putIfAbsent(importedXid, holder)) != null) {
holder = existing;
}
SubordinateTransaction txn = holder.get();
// Should only be called by the recovery system - this will replace the Transaction with one from disk
if (recoveredTransaction!= null) {
synchronized (holder) {
// now it's safe to add the imported transaction to the holder
recoveredTransaction.recordTransaction();
txn = recoveredTransaction;
holder.set(txn);
holder.notifyAll();
}
}
if (txn == null) {
// retry the get under a lock - this double check idiom is safe because AtomicReference is effectively
// a volatile so can be concurrently accessed by multiple threads
synchronized (holder) {
txn = holder.get();
if (txn == null) {
txn = new TransactionImple(timeout, xid);
holder.set(txn);
holder.notifyAll();
isNew = true;
}
}
}
return new TransactionImportResult(txn, isNew);
}
private static ConcurrentHashMap<Xid, AtomicReference<SubordinateTransaction>> _transactions = new ConcurrentHashMap<>();
}