/* * JBoss, Home of Professional Open Source * Copyright 2009 Red Hat Inc. and/or its affiliates and other * contributors as indicated by the @author tags. All rights reserved. * See the copyright.txt in the distribution for a full listing of * individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY 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 along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.infinispan.transaction.xa; import org.infinispan.commands.CommandsFactory; import org.infinispan.config.Configuration; import org.infinispan.interceptors.locking.ClusteringDependentLogic; import org.infinispan.remoting.rpc.RpcManager; import org.infinispan.transaction.AbstractEnlistmentAdapter; import org.infinispan.transaction.TransactionCoordinator; import org.infinispan.transaction.TransactionTable; import org.infinispan.transaction.xa.recovery.RecoveryManager; import org.infinispan.transaction.xa.recovery.SerializableXid; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; import javax.transaction.xa.XAException; import javax.transaction.xa.XAResource; import javax.transaction.xa.Xid; /** * This acts both as an local {@link org.infinispan.transaction.xa.CacheTransaction} and implementor of an {@link * javax.transaction.xa.XAResource} that will be called by tx manager on various tx stages. * * @author Mircea.Markus@jboss.com * @since 4.0 */ public class TransactionXaAdapter extends AbstractEnlistmentAdapter implements XAResource { private static final Log log = LogFactory.getLog(TransactionXaAdapter.class); private static boolean trace = log.isTraceEnabled(); /** * It is really useful only if TM and client are in separate processes and TM fails. This is because a client might * call tm.begin and then the TM (running separate process) crashes. In this scenario the TM won't ever call * XAResource.rollback, so these resources would be held there forever. By knowing the timeout the RM can proceed * releasing the resources associated with given tx. */ private int txTimeout; private final XaTransactionTable txTable; private final TransactionCoordinator txCoordinator; /** * XAResource is associated with a transaction between enlistment (XAResource.start()) XAResource.end(). It's only the * boundary methods (prepare, commit, rollback) that need to be "stateless". * Reefer to section 3.4.4 from JTA spec v.1.1 */ private final LocalXaTransaction localTransaction; private final RecoveryManager recoveryManager; private volatile RecoveryManager.RecoveryIterator recoveryIterator; private boolean recoveryEnabled; public TransactionXaAdapter(LocalXaTransaction localTransaction, TransactionTable txTable, RecoveryManager rm, TransactionCoordinator txCoordinator, CommandsFactory commandsFactory, RpcManager rpcManager, ClusteringDependentLogic clusteringDependentLogic, Configuration config) { super(localTransaction, commandsFactory, rpcManager, txTable, clusteringDependentLogic, config); this.localTransaction = localTransaction; this.txTable = (XaTransactionTable) txTable; this.recoveryManager = rm; this.txCoordinator = txCoordinator; recoveryEnabled = config.isTransactionRecoveryEnabled(); } public TransactionXaAdapter(TransactionTable txTable, RecoveryManager rm, TransactionCoordinator txCoordinator, CommandsFactory commandsFactory, RpcManager rpcManager, ClusteringDependentLogic clusteringDependentLogic, Configuration config) { super(commandsFactory, rpcManager, txTable, clusteringDependentLogic, config); localTransaction = null; this.txTable = (XaTransactionTable) txTable; this.recoveryManager = rm; this.txCoordinator = txCoordinator; recoveryEnabled = config.isTransactionRecoveryEnabled(); } /** * This can be call for any transaction object. See Section 3.4.6 (Resource Sharing) from JTA spec v1.1. */ @Override public int prepare(Xid externalXid) throws XAException { Xid xid = convertXid(externalXid); LocalXaTransaction localTransaction = getLocalTransactionAndValidate(xid); return txCoordinator.prepare(localTransaction); } /** * Same comment as for {@link #prepare(javax.transaction.xa.Xid)} applies for commit. */ @Override public void commit(Xid externalXid, boolean isOnePhase) throws XAException { Xid xid = convertXid(externalXid); LocalXaTransaction localTransaction = getLocalTransactionAndValidate(xid); if (isOnePhase) { //isOnePhase being true means that we're the only participant in the distributed transaction and TM does the //1PC optimization. We run a 2PC though, as running only 1PC has a high chance of leaving the cluster in //inconsistent state. txCoordinator.prepare(localTransaction); txCoordinator.commit(localTransaction, false); } else { txCoordinator.commit(localTransaction, false); } forgetSuccessfullyCompletedTransaction(recoveryManager, xid, localTransaction); } /** * Same comment as for {@link #prepare(javax.transaction.xa.Xid)} applies for commit. */ @Override public void rollback(Xid externalXid) throws XAException { Xid xid = convertXid(externalXid); LocalXaTransaction localTransaction1 = getLocalTransactionAndValidateImpl(xid, txTable); localTransaction.markForRollback(true); //ISPN-879 : make sure that locks are no longer associated to this transactions txCoordinator.rollback(localTransaction1); forgetSuccessfullyCompletedTransaction(recoveryManager, xid, localTransaction1); } @Override public void start(Xid externalXid, int i) throws XAException { Xid xid = convertXid(externalXid); //transform in our internal format in order to be able to serialize localTransaction.setXid(xid); txTable.addLocalTransactionMapping(localTransaction); if (trace) log.tracef("start called on tx %s", this.localTransaction.getGlobalTransaction()); } @Override public void end(Xid externalXid, int i) throws XAException { if (trace) log.tracef("end called on tx %s(%s)", this.localTransaction.getGlobalTransaction(), config.getName()); } @Override public void forget(Xid externalXid) throws XAException { if (!recoveryEnabled) { return; } Xid xid = convertXid(externalXid); if (trace) log.tracef("forget called for xid %s", xid); try { recoveryManager.removeRecoveryInformationFromCluster(null, xid, true, null); } catch (Exception e) { log.warn("Exception removing recovery information: ", e); throw new XAException(XAException.XAER_RMERR); } } @Override public int getTransactionTimeout() throws XAException { if (trace) log.trace("start called"); return txTimeout; } /** * the only situation in which it returns true is when the other xa resource pertains to the same cache, on * the same node. */ @Override public boolean isSameRM(XAResource xaResource) throws XAException { if (!(xaResource instanceof TransactionXaAdapter)) { return false; } TransactionXaAdapter other = (TransactionXaAdapter) xaResource; //there is only one tx table per cache and this is more efficient that equals. return this.txTable == other.txTable; } @Override public Xid[] recover(int flag) throws XAException { if (!config.isTransactionRecoveryEnabled()) { log.recoveryIgnored(); return RecoveryManager.RecoveryIterator.NOTHING; } if (trace) log.trace("recover called: " + flag); if (isFlag(flag, TMSTARTRSCAN)) { recoveryIterator = recoveryManager.getPreparedTransactionsFromCluster(); if (trace) log.tracef("Fetched a new recovery iterator: %s" , recoveryIterator); } if (isFlag(flag, TMENDRSCAN)) { if (trace) log.trace("Flushing the iterator"); return recoveryIterator.all(); } else { //as per the spec: "TMNOFLAGS this flag must be used when no other flags are specified." if (!isFlag(flag, TMSTARTRSCAN) && !isFlag(flag, TMNOFLAGS)) throw new IllegalArgumentException("TMNOFLAGS this flag must be used when no other flags are specified." + " Received " + flag); return recoveryIterator.hasNext() ? recoveryIterator.next() : RecoveryManager.RecoveryIterator.NOTHING; } } private boolean isFlag(int value, int flag) { return (value & flag) != 0; } @Override public boolean setTransactionTimeout(int i) throws XAException { this.txTimeout = i; return true; } @Override public String toString() { return "TransactionXaAdapter{" + "localTransaction=" + localTransaction + '}'; } private void forgetSuccessfullyCompletedTransaction(RecoveryManager recoveryManager, Xid xid, LocalXaTransaction localTransaction) { final GlobalTransaction gtx = localTransaction.getGlobalTransaction(); if (config.isTransactionRecoveryEnabled()) { recoveryManager.removeRecoveryInformationFromCluster(localTransaction.getRemoteLocksAcquired(), xid, false, gtx); txTable.removeLocalTransaction(localTransaction); } else { releaseLocksForCompletedTransaction(localTransaction); } } private LocalXaTransaction getLocalTransactionAndValidate(Xid xid) throws XAException { return getLocalTransactionAndValidateImpl(xid, txTable); } private static LocalXaTransaction getLocalTransactionAndValidateImpl(Xid xid, XaTransactionTable txTable) throws XAException { LocalXaTransaction localTransaction = txTable.getLocalTransaction(xid); if (localTransaction == null) { if (trace) log.tracef("no tx found for %s", xid); throw new XAException(XAException.XAER_NOTA); } return localTransaction; } public LocalXaTransaction getLocalTransaction() { return localTransaction; } /** * Only does the conversion if recovery is enabled. */ private Xid convertXid(Xid externalXid) { if (recoveryEnabled && (!(externalXid instanceof SerializableXid))) { return new SerializableXid(externalXid); } else { return externalXid; } } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TransactionXaAdapter that = (TransactionXaAdapter) o; if (localTransaction != null ? !localTransaction.equals(that.localTransaction) : that.localTransaction != null) return false; //also include the name of the cache in comparison - needed when same tx spans multiple caches. return config.getName() != null ? config.getName().equals(that.config.getName()) : that.config.getName() == null; } }