/* * 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.factories.annotations.Inject; import org.infinispan.jmx.annotations.MBean; import org.infinispan.jmx.annotations.ManagedOperation; import org.infinispan.remoting.transport.Address; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; import javax.transaction.Status; import javax.transaction.xa.Xid; import java.util.Set; /** * Admin utility class for allowing management of in-doubt transactions (e.g. transactions for which the * originator node crashed after prepare). * * @author Mircea Markus * @since 5.0 */ @MBean(objectName = "RecoveryAdmin", description = "Exposes tooling for handling transaction recovery.") public class RecoveryAdminOperations { private static final Log log = LogFactory.getLog(RecoveryAdminOperations.class); public static final String SEPARAOR = ", "; private RecoveryManager recoveryManager; @Inject public void init(RecoveryManager recoveryManager) { this.recoveryManager = recoveryManager; } @ManagedOperation(description = "Shows all the prepared transactions for which the originating node crashed") public String showInDoubtTransactions() { Set<RecoveryManager.InDoubtTxInfo> info = getRecoveryInfoFromCluster(); if (log.isTraceEnabled()) { log.tracef("Found in doubt transactions: %s", info.size()); } StringBuilder result = new StringBuilder(); for (RecoveryManager.InDoubtTxInfo i : info) { result.append("xid = [").append(i.getXid()).append("], ").append(SEPARAOR) .append("internalId = ").append(i.getInternalId()).append(SEPARAOR); result.append("status = [ "); for (Integer status : i.getStatus()) { if (status == Status.STATUS_PREPARED) { result.append("_PREPARED_"); } else if (status == Status.STATUS_COMMITTED) { result.append("_COMMITTED_"); } else if (status == Status.STATUS_ROLLEDBACK) { result.append("_ROLLEDBACK_"); } } result.append(" ]"); result.append('\n'); } return result.toString(); } @ManagedOperation(description = "Forces the commit of an in-doubt transaction") public String forceCommit(long internalID) { if (log.isTraceEnabled()) log.tracef("Forces the commit of an in-doubt transaction: %s", internalID); return completeBasedOnInternalId(internalID, true); } @ManagedOperation(description = "Forces the commit of an in-doubt transaction") public String forceCommit(int formatId, byte[] globalTxId, byte[] branchQualifier) { return completeBasedOnXid(formatId, globalTxId, branchQualifier, true); } @ManagedOperation(description = "Forces the rollback of an in-doubt transaction") public String forceRollback(long internalId) { return completeBasedOnInternalId(internalId, false); } @ManagedOperation(description = "Forces the rollback of an in-doubt transaction") public String forceRollback(int formatId, byte[] globalTxId, byte[] branchQualifier) { return completeBasedOnXid(formatId, globalTxId, branchQualifier, false); } @ManagedOperation(description = "Removes recovery info for the given transaction.") public String forget(int formatId, byte[] globalTxId, byte[] branchQualifier) { recoveryManager.removeRecoveryInformationFromCluster(null, new SerializableXid(branchQualifier, globalTxId, formatId), true, null); return "Recovery info removed."; } @ManagedOperation(description = "Removes recovery info for the given transaction.") public String forget(long internalId) { recoveryManager.removeRecoveryInformationFromCluster(null, internalId, true); return "Recovery info removed."; } private String completeBasedOnXid(int formatId, byte[] globalTxId, byte[] branchQualifier, boolean commit) { RecoveryManager.InDoubtTxInfo inDoubtTxInfo = lookupRecoveryInfo(formatId, globalTxId, branchQualifier); if (inDoubtTxInfo != null) { return completeTransaction(inDoubtTxInfo.getXid(), inDoubtTxInfo, commit); } else { return transactionNotFound(formatId, globalTxId, branchQualifier); } } private String completeBasedOnInternalId(Long internalId, boolean commit) { RecoveryManager.InDoubtTxInfo inDoubtTxInfo = lookupRecoveryInfo(internalId); if (inDoubtTxInfo != null) { return completeTransaction(inDoubtTxInfo.getXid(), inDoubtTxInfo, commit); } else { return transactionNotFound(internalId); } } private String completeTransaction(Xid xid, RecoveryManager.InDoubtTxInfo i, boolean commit) { //try to run it locally at first if (i.isLocal()) { log.tracef("Forcing completion of local transaction: %s", i); return recoveryManager.forceTransactionCompletion(xid, commit); } else { log.tracef("Forcing completion of remote transaction: %s", i); Set<Address> owners = i.getOwners(); if (owners == null || owners.isEmpty()) throw new IllegalStateException("Owner list cannot be empty for " + i); return recoveryManager.forceTransactionCompletionFromCluster(xid, owners.iterator().next(), commit); } } private RecoveryManager.InDoubtTxInfo lookupRecoveryInfo(int formatId, byte[] globalTxId, byte[] branchQualifier) { Set<RecoveryManager.InDoubtTxInfo> info = getRecoveryInfoFromCluster(); SerializableXid xid = new SerializableXid(branchQualifier, globalTxId, formatId); for (RecoveryManager.InDoubtTxInfo i : info) { if (i.getXid().equals(xid)) { log.tracef("Found matching recovery info: %s", i); return i; } } return null; } private Set<RecoveryManager.InDoubtTxInfo> getRecoveryInfoFromCluster() { Set<RecoveryManager.InDoubtTxInfo> info = recoveryManager.getInDoubtTransactionInfoFromCluster(); log.tracef("Recovery info from cluster is: %s", info); return info; } private RecoveryManager.InDoubtTxInfo lookupRecoveryInfo(Long internalId) { Set<RecoveryManager.InDoubtTxInfo> info = getRecoveryInfoFromCluster(); for (RecoveryManager.InDoubtTxInfo i : info) { if (i.getInternalId().equals(internalId)) { log.tracef("Found matching recovery info: %s", i); return i; } } return null; } private String transactionNotFound(int formatId, byte[] globalTxId, byte[] branchQualifier) { return "Transaction not found: " + new SerializableXid(branchQualifier, globalTxId, formatId); } private String transactionNotFound(Long internalId) { return "Transaction not found for internal id: " + internalId; } }