/*
* JBoss, Home of Professional Open Source
* Copyright 2011 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.recovery;
import org.infinispan.CacheException;
import org.infinispan.factories.annotations.Inject;
import org.infinispan.remoting.transport.Address;
import org.infinispan.transaction.LocalTransaction;
import org.infinispan.transaction.RemoteTransaction;
import org.infinispan.transaction.xa.GlobalTransaction;
import org.infinispan.transaction.xa.LocalXaTransaction;
import org.infinispan.transaction.xa.XaTransactionTable;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
import javax.transaction.Transaction;
import javax.transaction.xa.Xid;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Transaction table that delegates prepared transaction's management to the {@link RecoveryManager}.
*
* @author Mircea.Markus@jboss.com
* @since 5.0
*/
public class RecoveryAwareTransactionTable extends XaTransactionTable {
private static final Log log = LogFactory.getLog(RecoveryAwareTransactionTable.class);
private RecoveryManagerImpl recoveryManager;
@Inject
public void initialize(RecoveryManager recoveryManager) {
this.recoveryManager = (RecoveryManagerImpl) recoveryManager;
}
/**
* Marks the transaction as prepared. If at a further point the originator fails, the transaction is removed form the
* "normal" transactions collection and moved into the cache that holds in-doubt transactions. See {@link
* #updateStateOnNodesLeaving(java.util.Collection)}
*/
@Override
public void remoteTransactionPrepared(GlobalTransaction gtx) {
RecoveryAwareRemoteTransaction remoteTransaction =
(RecoveryAwareRemoteTransaction) super.getRemoteTransaction(gtx);
if (remoteTransaction == null)
throw new CacheException(String.format(
"Remote transaction for global transaction (%s) not found", gtx));
remoteTransaction.setPrepared(true);
}
/**
* @see #localTransactionPrepared(org.infinispan.transaction.LocalTransaction)
*/
@Override
public void localTransactionPrepared(LocalTransaction localTransaction) {
((RecoveryAwareLocalTransaction) localTransaction).setPrepared(true);
}
/**
* First moves the prepared transactions originated on the leavers into the recovery cache and then cleans up the
* transactions that are not yet prepared.
*/
@Override
protected void updateStateOnNodesLeaving(Collection<Address> leavers) {
Iterator<RemoteTransaction> it = getRemoteTransactions().iterator();
while (it.hasNext()) {
RecoveryAwareRemoteTransaction recTx = (RecoveryAwareRemoteTransaction) it.next();
recTx.computeOrphan(leavers);
if (recTx.isInDoubt()) {
recoveryManager.registerInDoubtTransaction(recTx);
it.remove();
}
}
//this cleans up the transactions that are not yet prepared
super.updateStateOnNodesLeaving(leavers);
}
@Override
public RemoteTransaction getRemoteTransaction(GlobalTransaction txId) {
RemoteTransaction remoteTransaction = super.getRemoteTransaction(txId);
if (remoteTransaction != null) return remoteTransaction;
//also look in the recovery manager, as this transaction might be prepared
return recoveryManager.getPreparedTransaction(((RecoverableTransactionIdentifier) txId).getXid());
}
@Override
public void remoteTransactionRollback(GlobalTransaction gtx) {
super.remoteTransactionRollback(gtx);
recoveryManager.removeRecoveryInformation(((RecoverableTransactionIdentifier) gtx).getXid());
}
@Override
public void remoteTransactionCommitted(GlobalTransaction gtx) {
RecoveryAwareRemoteTransaction remoteTransaction = (RecoveryAwareRemoteTransaction) getRemoteTransaction(gtx);
if (remoteTransaction == null)
throw new CacheException(String.format("Remote transaction for global transaction (%s) not found", gtx));
remoteTransaction.markCompleted(true);
super.remoteTransactionCommitted(gtx);
}
public List<Xid> getLocalPreparedXids() {
List<Xid> result = new LinkedList<Xid>();
for (Map.Entry<Xid, LocalXaTransaction> e : xid2LocalTx.entrySet()) {
RecoveryAwareLocalTransaction value = (RecoveryAwareLocalTransaction) e.getValue();
if (value.isPrepared()) {
result.add(e.getKey());
}
}
return result;
}
@Override
public void failureCompletingTransaction(Transaction tx) {
RecoveryAwareLocalTransaction localTx = (RecoveryAwareLocalTransaction) getLocalTx(tx);
if (localTx == null)
throw new CacheException(String.format("Local transaction for transaction (%s) not found", tx));
localTx.setCompletionFailed(true);
log.tracef("Marked as completion failed %s", localTx);
}
public Set<RecoveryAwareLocalTransaction> getLocalTxThatFailedToComplete() {
Set<RecoveryAwareLocalTransaction> result = new HashSet<RecoveryAwareLocalTransaction>(4);
for (LocalTransaction lTx : xid2LocalTx.values()) {
RecoveryAwareLocalTransaction lTx1 = (RecoveryAwareLocalTransaction) lTx;
if (lTx1.isCompletionFailed()) {
result.add(lTx1);
}
}
return result;
}
/**
* Iterates over the remote transactions and returns the XID of the one that has an internal id equal with the
* supplied internal Id.
*/
public Xid getRemoteTransactionXid(Long internalId) {
for (RemoteTransaction rTx : getRemoteTransactions()) {
RecoverableTransactionIdentifier gtx = (RecoverableTransactionIdentifier) rTx.getGlobalTransaction();
if (gtx.getInternalId() == internalId) {
if (log.isTraceEnabled()) log.tracef("Found xid %s matching internal id %s", gtx.getXid(), internalId);
return gtx.getXid();
}
}
log.tracef("Could not find remote transactions matching internal id %s", internalId);
return null;
}
public RemoteTransaction removeRemoteTransaction(Xid xid) {
if (clustered) {
Iterator<RemoteTransaction> it = getRemoteTransactions().iterator();
while (it.hasNext()) {
RemoteTransaction next = it.next();
RecoverableTransactionIdentifier gtx = (RecoverableTransactionIdentifier) next.getGlobalTransaction();
if (xid.equals(gtx.getXid())) {
it.remove();
recalculateMinViewIdIfNeeded(next);
next.notifyOnTransactionFinished();
return next;
}
}
}
return null;
}
}