/** * Copyright 2014-2016 yangming.liu<bytefox@126.com>. * * 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, as published by the Free Software Foundation. * * This program 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 distribution; if not, see <http://www.gnu.org/licenses/>. */ package org.bytesoft.bytetcc; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.transaction.Status; import javax.transaction.SystemException; import javax.transaction.xa.XAException; import javax.transaction.xa.Xid; import org.apache.commons.lang3.StringUtils; import org.bytesoft.bytejta.supports.jdbc.RecoveredResource; import org.bytesoft.bytejta.supports.resource.LocalXAResourceDescriptor; import org.bytesoft.common.utils.ByteUtils; import org.bytesoft.common.utils.CommonUtils; import org.bytesoft.compensable.CompensableBeanFactory; import org.bytesoft.compensable.CompensableManager; import org.bytesoft.compensable.TransactionContext; import org.bytesoft.compensable.archive.CompensableArchive; import org.bytesoft.compensable.aware.CompensableBeanFactoryAware; import org.bytesoft.compensable.logging.CompensableLogger; import org.bytesoft.transaction.CommitRequiredException; import org.bytesoft.transaction.RollbackRequiredException; import org.bytesoft.transaction.Transaction; import org.bytesoft.transaction.TransactionRecovery; import org.bytesoft.transaction.TransactionRepository; import org.bytesoft.transaction.archive.TransactionArchive; import org.bytesoft.transaction.archive.XAResourceArchive; import org.bytesoft.transaction.recovery.TransactionRecoveryCallback; import org.bytesoft.transaction.recovery.TransactionRecoveryListener; import org.bytesoft.transaction.supports.serialize.XAResourceDeserializer; import org.bytesoft.transaction.xa.TransactionXid; import org.bytesoft.transaction.xa.XidFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class TransactionRecoveryImpl implements TransactionRecovery, TransactionRecoveryListener, CompensableBeanFactoryAware { static final Logger logger = LoggerFactory.getLogger(TransactionRecoveryImpl.class); private CompensableBeanFactory beanFactory; private final Map<TransactionXid, Transaction> recovered = new HashMap<TransactionXid, Transaction>(); public void onRecovery(Transaction transaction) { org.bytesoft.transaction.TransactionContext transactionContext = transaction.getTransactionContext(); TransactionXid xid = transactionContext.getXid(); XidFactory xidFactory = this.beanFactory.getCompensableXidFactory(); TransactionXid globalXid = xidFactory.createGlobalXid(xid.getGlobalTransactionId()); this.recovered.put(globalXid, transaction); } public void startRecovery() { this.fireTransactionStartRecovery(); this.fireCompensableStartRecovery(); } private void fireTransactionStartRecovery() { TransactionRecovery transactionRecovery = this.beanFactory.getTransactionRecovery(); transactionRecovery.startRecovery(); } private void fireCompensableStartRecovery() { final TransactionRepository transactionRepository = this.beanFactory.getCompensableRepository(); CompensableLogger compensableLogger = this.beanFactory.getCompensableLogger(); compensableLogger.recover(new TransactionRecoveryCallback() { public void recover(TransactionArchive archive) { this.recover((org.bytesoft.compensable.archive.TransactionArchive) archive); } public void recover(org.bytesoft.compensable.archive.TransactionArchive archive) { XidFactory transactionXidFactory = beanFactory.getTransactionXidFactory(); CompensableTransactionImpl transaction = reconstructTransaction(archive); TransactionContext transactionContext = transaction.getTransactionContext(); TransactionXid compensableXid = transactionContext.getXid(); if (transactionContext.isCompensable() == false) { TransactionXid transactionXid = transactionXidFactory .createGlobalXid(compensableXid.getGlobalTransactionId()); Transaction tx = recovered.get(transactionXid); if (tx != null) { tx.setTransactionalExtra(transaction); transaction.setTransactionalExtra(tx); } } else { recoverStatusIfNecessary(transaction); } // end-if (transactionContext.isCoordinator()) transactionRepository.putTransaction(compensableXid, transaction); transactionRepository.putErrorTransaction(compensableXid, transaction); } }); } public CompensableTransactionImpl reconstructTransaction(TransactionArchive transactionArchive) { XidFactory xidFactory = this.beanFactory.getCompensableXidFactory(); org.bytesoft.compensable.archive.TransactionArchive archive = (org.bytesoft.compensable.archive.TransactionArchive) transactionArchive; TransactionContext transactionContext = new TransactionContext(); transactionContext.setCompensable(true); transactionContext.setCoordinator(archive.isCoordinator()); transactionContext.setPropagated(archive.isPropagated()); transactionContext.setCompensating(archive.isPropagated() == false); transactionContext.setRecoveried(true); transactionContext.setXid(xidFactory.createGlobalXid(archive.getXid().getGlobalTransactionId())); transactionContext.setPropagatedBy(transactionArchive.getPropagatedBy()); CompensableTransactionImpl transaction = new CompensableTransactionImpl(transactionContext); transaction.setBeanFactory(this.beanFactory); transaction.setTransactionVote(archive.getVote()); transaction.setTransactionStatus(archive.getCompensableStatus()); transaction.setVariables(archive.getVariables()); List<XAResourceArchive> participantList = archive.getRemoteResources(); for (int i = 0; i < participantList.size(); i++) { XAResourceArchive participantArchive = participantList.get(i); transaction.getParticipantArchiveList().add(participantArchive); } List<CompensableArchive> compensableList = archive.getCompensableResourceList(); for (int i = 0; i < compensableList.size(); i++) { CompensableArchive compensableArchive = compensableList.get(i); transaction.getCompensableArchiveList().add(compensableArchive); } return transaction; } public void recoverStatusIfNecessary(Transaction transaction) { org.bytesoft.transaction.TransactionContext transactionContext = transaction.getTransactionContext(); CompensableTransactionImpl compensable = (CompensableTransactionImpl) transaction; List<CompensableArchive> archiveList = compensable.getCompensableArchiveList(); CompensableLogger compensableLogger = this.beanFactory.getCompensableLogger(); Map<TransactionBranchKey, Boolean> triedMap = new HashMap<TransactionBranchKey, Boolean>(); int triedNumber = 0; int unTriedNumber = 0; int unknownNumber = 0; for (int i = 0; i < archiveList.size(); i++) { CompensableArchive archive = archiveList.get(i); TransactionBranchKey recordKey = new TransactionBranchKey(); recordKey.xid = archive.getTransactionXid(); recordKey.resource = archive.getTransactionResourceKey(); if (archive.isTried()) { triedNumber++; triedMap.put(recordKey, Boolean.TRUE); } else if (StringUtils.isBlank(recordKey.resource)) { unknownNumber++; logger.warn( "There is no valid resource participated in the trying branch transaction, the status of the branch transaction is unknown!"); } else if (triedMap.containsKey(recordKey)) { Boolean tried = triedMap.get(recordKey); if (Boolean.TRUE.equals(tried)) { archive.setTried(true); triedNumber++; compensableLogger.updateCompensable(archive); } else { unTriedNumber++; } } else { Boolean tried = this.calculateCompensableTried(recordKey); if (Boolean.TRUE.equals(tried)) { archive.setTried(true); triedNumber++; triedMap.put(recordKey, Boolean.TRUE); compensableLogger.updateCompensable(archive); } else { unTriedNumber++; triedMap.put(recordKey, tried); } } } // end-for if (transactionContext.isCoordinator()) { if (triedNumber > 0 && unTriedNumber > 0) { transaction.setTransactionStatus(Status.STATUS_PREPARING); compensableLogger.updateTransaction(compensable.getTransactionArchive()); } else if (triedNumber > 0 && unknownNumber > 0) { switch (transaction.getTransactionStatus()) { case Status.STATUS_PREPARING: case Status.STATUS_PREPARED: case Status.STATUS_COMMITTED: case Status.STATUS_ROLLEDBACK: case Status.STATUS_ROLLING_BACK: case Status.STATUS_COMMITTING: break; // ignore default: transaction.setTransactionStatus(Status.STATUS_PREPARING); compensableLogger.updateTransaction(compensable.getTransactionArchive()); } } else if (triedNumber > 0 && transactionContext.isPropagated() == false) { transaction.setTransactionStatus(Status.STATUS_PREPARED); compensableLogger.updateTransaction(compensable.getTransactionArchive()); } } // end-if (transactionContext.isCoordinator()) } private Boolean calculateCompensableTried(TransactionBranchKey recordKey) { if (StringUtils.isBlank(recordKey.resource)) { logger.warn( "There is no valid resource participated in the trying branch transaction, the status of the branch transaction is unknown!"); return null; } XAResourceDeserializer resourceDeserializer = this.beanFactory.getResourceDeserializer(); Xid transactionXid = recordKey.xid; try { LocalXAResourceDescriptor descriptor = // (LocalXAResourceDescriptor) resourceDeserializer.deserialize(recordKey.resource); RecoveredResource resource = (RecoveredResource) descriptor.getDelegate(); resource.recoverable(transactionXid); return true; } catch (XAException xaex) { switch (xaex.errorCode) { case XAException.XAER_NOTA: return false; case XAException.XAER_RMERR: logger.warn( "The database table 'bytejta' cannot found, the status of the trying branch transaction is unknown!"); break; case XAException.XAER_RMFAIL: logger.error("Error occurred while recovering the branch transaction service: {}", ByteUtils.byteArrayToString(transactionXid.getGlobalTransactionId()), xaex); break; default: logger.error("Illegal state, the status of the trying branch transaction is unknown!"); } } catch (RuntimeException rex) { logger.error("Illegal resources, the status of the trying branch transaction is unknown!"); } return null; } public synchronized void timingRecover() { TransactionRepository transactionRepository = beanFactory.getCompensableRepository(); List<Transaction> transactions = transactionRepository.getErrorTransactionList(); int total = transactions == null ? 0 : transactions.size(); int value = 0; for (int i = 0; transactions != null && i < transactions.size(); i++) { Transaction transaction = transactions.get(i); org.bytesoft.transaction.TransactionContext transactionContext = transaction.getTransactionContext(); TransactionXid xid = transactionContext.getXid(); try { this.recoverTransaction(transaction); } catch (CommitRequiredException ex) { logger.debug("{}| recover: branch={}, message= commit-required", ByteUtils.byteArrayToString(xid.getGlobalTransactionId()), ByteUtils.byteArrayToString(xid.getBranchQualifier())); continue; } catch (RollbackRequiredException ex) { logger.debug("{}| recover: branch={}, message= rollback-required", ByteUtils.byteArrayToString(xid.getGlobalTransactionId()), ByteUtils.byteArrayToString(xid.getBranchQualifier())); continue; } catch (SystemException ex) { logger.debug("{}| recover: branch={}, message= {}", ByteUtils.byteArrayToString(xid.getGlobalTransactionId()), ByteUtils.byteArrayToString(xid.getBranchQualifier()), ex.getMessage()); continue; } catch (RuntimeException ex) { logger.debug("{}| recover: branch={}, message= {}", ByteUtils.byteArrayToString(xid.getGlobalTransactionId()), ByteUtils.byteArrayToString(xid.getBranchQualifier()), ex.getMessage()); continue; } } logger.debug("transaction-recovery: total= {}, success= {}", total, value); } public void recoverTransaction(Transaction transaction) throws CommitRequiredException, RollbackRequiredException, SystemException { org.bytesoft.transaction.TransactionContext transactionContext = transaction.getTransactionContext(); if (transactionContext.isCoordinator()) { transaction.recover(); this.recoverCoordinator(transaction); } else { transaction.recover(); this.recoverParticipant(transaction); } } private void recoverCoordinator(Transaction transaction) throws CommitRequiredException, RollbackRequiredException, SystemException { CompensableManager compensableManager = this.beanFactory.getCompensableManager(); org.bytesoft.transaction.TransactionContext transactionContext = transaction.getTransactionContext(); try { compensableManager.associateThread(transaction); switch (transaction.getTransactionStatus()) { case Status.STATUS_ACTIVE: case Status.STATUS_MARKED_ROLLBACK: case Status.STATUS_PREPARING: case Status.STATUS_UNKNOWN: // TODO if (transactionContext.isPropagated() == false) { transaction.recoveryRollback(); transaction.forgetQuietly(); } break; case Status.STATUS_ROLLING_BACK: transaction.recoveryRollback(); transaction.forgetQuietly(); break; case Status.STATUS_PREPARED: case Status.STATUS_COMMITTING: transaction.recoveryCommit(); transaction.forgetQuietly(); break; case Status.STATUS_COMMITTED: case Status.STATUS_ROLLEDBACK: transaction.forgetQuietly(); break; default: // ignore } } finally { compensableManager.desociateThread(); } } private void recoverParticipant(Transaction transaction) throws CommitRequiredException, RollbackRequiredException, SystemException { CompensableManager compensableManager = this.beanFactory.getCompensableManager(); try { compensableManager.associateThread(transaction); switch (transaction.getTransactionStatus()) { case Status.STATUS_COMMITTED: case Status.STATUS_ROLLEDBACK: transaction.forgetQuietly(); break; default: // ignore } } finally { compensableManager.desociateThread(); } } public void setBeanFactory(CompensableBeanFactory tbf) { this.beanFactory = tbf; } private static class TransactionBranchKey { public Xid xid; public String resource; public int hashCode() { int hash = 3; hash += 7 * (this.xid == null ? 0 : this.xid.hashCode()); hash += 11 * (this.resource == null ? 0 : this.resource.hashCode()); return hash; } public boolean equals(Object obj) { if (obj == null) { return false; } else if (TransactionBranchKey.class.isInstance(obj) == false) { return false; } TransactionBranchKey that = (TransactionBranchKey) obj; boolean xidEquals = CommonUtils.equals(this.xid, that.xid); boolean resEquals = StringUtils.equals(this.resource, that.resource); return xidEquals && resEquals; } } }