/* * 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.arjunacore.jca; import java.util.HashSet; import java.util.Iterator; import java.util.Set; 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.arjuna.coordinator.TxControl; import com.arjuna.ats.internal.jta.transaction.arjunacore.subordinate.jca.TransactionImple; import com.arjuna.ats.jta.xa.XATxConverter; import com.arjuna.ats.jta.xa.XidImple; 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 javax.transaction.xa.XAException * thrown if there are any errors. */ public SubordinateTransaction importTransaction(Xid xid) throws XAException { return (SubordinateTransaction) importRemoteTransaction(xid, 0).getTransaction(); } 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 javax.transaction.xa.XAException * thrown if there are any errors. */ public TransactionImportResult importRemoteTransaction(Xid xid, int timeout) throws XAException { if (xid == null) throw new IllegalArgumentException(); /* * the imported transaction map is keyed by xid and the xid used is the one created inside * the TransactionImple ctor (it encodes the node name of this transaction manager) and is * the one returned by TransactionImple#baseXid() so pass in the converted value (using convertXid). */ return addImportedTransaction(null, convertXid(xid), xid, timeout); } /** * Used to recover an imported transaction. * * @param actId * the state to recover. * @return the recovered transaction object. * @throws javax.transaction.xa.XAException */ public TransactionImple recoverTransaction(Uid actId) throws XAException { if (actId == null) throw new IllegalArgumentException(); TransactionImple recovered = new TransactionImple(actId); if (recovered.baseXid() == null) throw new IllegalArgumentException(); /* * Is the transaction already in the map? This may be the case because * we scan the object store periodically and may get Uids to recover for * transactions that are progressing normally, i.e., do not need * recovery. In which case, we need to ignore them: * * ie calling addImportedTransaction with a non null value for recovered will * call recovered.recordTransaction() */ return (TransactionImple) 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 javax.transaction.xa.XAException * thrown if there are any errors. */ public SubordinateTransaction getImportedTransaction(Xid xid) throws XAException { if (xid == null) throw new IllegalArgumentException(); AtomicReference<TransactionImple> holder = _transactions.get(new SubordinateXidImple(xid)); TransactionImple 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) { e.printStackTrace(); throw new XAException(XAException.XA_RBROLLBACK); } if (!tx.activated()) { tx.recover(); return tx; } else return tx; } /** * Remove the subordinate (imported) transaction. * * @param xid * the global transaction. * * @throws javax.transaction.xa.XAException * thrown if there are any errors. */ public void removeImportedTransaction(Xid xid) throws XAException { if (xid == null) throw new IllegalArgumentException(); AtomicReference<TransactionImple> remove = _transactions.remove(new SubordinateXidImple(xid)); if (remove != null) { synchronized (remove) { TransactionImple transactionImple = remove.get(); while (transactionImple == null) { try { remove.wait(); transactionImple = remove.get(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new XAException(XAException.XAER_RMFAIL); } } TransactionImple.removeTransaction(transactionImple); } } } public Set<Xid> getInflightXids(String parentNodeName) { Iterator<AtomicReference<TransactionImple>> iterator = _transactions.values().iterator(); Set<Xid> toReturn = new HashSet<Xid>(); while (iterator.hasNext()) { AtomicReference<TransactionImple> holder = iterator.next(); TransactionImple imported = holder.get(); if (imported != null && imported.getParentNodeName().equals(parentNodeName)) { toReturn.add(imported.baseXid()); } } return toReturn; } /** * This can be used for newly imported transactions or recovered ones. * * @param recoveredTransaction If this is recovery * @param mapKey * @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<TransactionImple> holder = new AtomicReference<>(); AtomicReference<TransactionImple> existing; if ((existing = _transactions.putIfAbsent(importedXid, holder)) != null) { holder = existing; } TransactionImple 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 XidImple convertXid(Xid xid) { if (xid != null && xid.getFormatId() == XATxConverter.FORMAT_ID) { XidImple toImport = new XidImple(xid); XATxConverter.setSubordinateNodeName(toImport.getXID(), TxControl.getXANodeName()); return new SubordinateXidImple(toImport); } else { return new XidImple(xid); } } private static ConcurrentHashMap<SubordinateXidImple, AtomicReference<TransactionImple>> _transactions = new ConcurrentHashMap<>(); }