/** * 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.io.Serializable; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.transaction.HeuristicMixedException; import javax.transaction.HeuristicRollbackException; import javax.transaction.RollbackException; import javax.transaction.Status; import javax.transaction.Synchronization; import javax.transaction.SystemException; import javax.transaction.xa.XAException; import javax.transaction.xa.XAResource; import javax.transaction.xa.Xid; import org.apache.commons.lang3.StringUtils; import org.bytesoft.bytejta.supports.jdbc.RecoveredResource; import org.bytesoft.bytejta.supports.resource.RemoteResourceDescriptor; import org.bytesoft.bytetcc.supports.resource.LocalResourceCleaner; import org.bytesoft.common.utils.ByteUtils; import org.bytesoft.common.utils.CommonUtils; import org.bytesoft.compensable.CompensableBeanFactory; import org.bytesoft.compensable.CompensableInvocation; import org.bytesoft.compensable.CompensableTransaction; import org.bytesoft.compensable.ContainerContext; import org.bytesoft.compensable.TransactionContext; import org.bytesoft.compensable.archive.CompensableArchive; import org.bytesoft.compensable.archive.TransactionArchive; 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.TransactionRepository; import org.bytesoft.transaction.archive.XAResourceArchive; import org.bytesoft.transaction.supports.TransactionListener; import org.bytesoft.transaction.supports.TransactionListenerAdapter; import org.bytesoft.transaction.supports.TransactionResourceListener; import org.bytesoft.transaction.supports.resource.XAResourceDescriptor; 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 CompensableTransactionImpl extends TransactionListenerAdapter implements CompensableTransaction { static final Logger logger = LoggerFactory.getLogger(CompensableTransactionImpl.class); private final TransactionContext transactionContext; private final List<CompensableArchive> archiveList = new ArrayList<CompensableArchive>(); private final List<XAResourceArchive> resourceList = new ArrayList<XAResourceArchive>(); private Transaction transaction; private CompensableBeanFactory beanFactory; private int transactionVote; private int transactionStatus = Status.STATUS_ACTIVE; /* current comensable-decision in confirm/cancel phase. */ private transient Boolean positive; /* current compense-archive in confirm/cancel phase. */ private transient CompensableArchive archive; /* current compensable-archive list in try phase. */ private transient final List<CompensableArchive> currentArchiveList = new ArrayList<CompensableArchive>(); private transient final Map<Xid, List<CompensableArchive>> archiveMap = new HashMap<Xid, List<CompensableArchive>>(); private boolean participantStickyRequired; private Map<String, Serializable> variables = new HashMap<String, Serializable>(); public CompensableTransactionImpl(TransactionContext txContext) { this.transactionContext = txContext; } public TransactionArchive getTransactionArchive() { TransactionArchive transactionArchive = new TransactionArchive(); transactionArchive.setVariables(this.variables); transactionArchive.setCoordinator(this.transactionContext.isCoordinator()); transactionArchive.setPropagated(this.transactionContext.isPropagated()); transactionArchive.setCompensable(this.transactionContext.isCompensable()); transactionArchive.setCompensableStatus(this.transactionStatus); transactionArchive.setVote(this.transactionVote); transactionArchive.setXid(this.transactionContext.getXid()); transactionArchive.getRemoteResources().addAll(this.resourceList); transactionArchive.getCompensableResourceList().addAll(this.archiveList); transactionArchive.setPropagatedBy(this.transactionContext.getPropagatedBy()); return transactionArchive; } public synchronized void participantCommit(boolean opc) throws RollbackException, HeuristicMixedException, HeuristicRollbackException, SecurityException, IllegalStateException, CommitRequiredException, SystemException { // Recover if transaction is recovered from tx-log. this.recoverIfNecessary(); if (this.transactionStatus != Status.STATUS_COMMITTED) { this.fireCommit(); // TODO } } public synchronized void commit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException, SecurityException, IllegalStateException, SystemException { if (this.transactionStatus == Status.STATUS_ACTIVE) { this.fireCommit(); } else if (this.transactionStatus == Status.STATUS_MARKED_ROLLBACK) { this.fireRollback(); throw new HeuristicRollbackException(); } else if (this.transactionStatus == Status.STATUS_ROLLEDBACK) /* should never happen */ { throw new RollbackException(); } else if (this.transactionStatus == Status.STATUS_COMMITTED) /* should never happen */ { logger.debug("Current transaction has already been committed."); } else { throw new IllegalStateException(); } } private void fireCommit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException, SecurityException, IllegalStateException, SystemException { CompensableLogger compensableLogger = this.beanFactory.getCompensableLogger(); this.transactionContext.setCompensating(true); this.transactionStatus = Status.STATUS_COMMITTING; compensableLogger.updateTransaction(this.getTransactionArchive()); SystemException systemEx = null; try { this.fireNativeParticipantConfirm(); } catch (SystemException ex) { systemEx = ex; logger.info("{}| confirm native branchs failed!", ByteUtils.byteArrayToString(this.transactionContext.getXid().getGlobalTransactionId()), ex); } catch (RuntimeException ex) { systemEx = new SystemException(ex.getMessage()); logger.info("{}| confirm native branchs failed!", ByteUtils.byteArrayToString(this.transactionContext.getXid().getGlobalTransactionId()), ex); } try { this.fireRemoteParticipantConfirm(); } catch (HeuristicMixedException ex) { logger.info("{}| confirm remote branchs failed!", ByteUtils.byteArrayToString(this.transactionContext.getXid().getGlobalTransactionId()), ex); throw ex; } catch (HeuristicRollbackException ex) { logger.info("{}| confirm remote branchs failed!", ByteUtils.byteArrayToString(this.transactionContext.getXid().getGlobalTransactionId()), ex); throw ex; } catch (SystemException ex) { logger.info("{}| confirm remote branchs failed!", ByteUtils.byteArrayToString(this.transactionContext.getXid().getGlobalTransactionId()), ex); throw ex; } catch (RuntimeException ex) { logger.info("{}| confirm remote branchs failed!", ByteUtils.byteArrayToString(this.transactionContext.getXid().getGlobalTransactionId()), ex); throw ex; } if (systemEx != null) { throw systemEx; } else { this.transactionStatus = Status.STATUS_COMMITTED; compensableLogger.updateTransaction(this.getTransactionArchive()); logger.info("{}| compensable transaction committed!", ByteUtils.byteArrayToString(transactionContext.getXid().getGlobalTransactionId())); } } public synchronized void recoveryCommit() throws CommitRequiredException, SystemException { this.recoverIfNecessary(); // Recover if transaction is recovered from tx-log. try { this.fireCommit(); } catch (SecurityException ex) { logger.error("{}| confirm native/remote branchs failed!", ByteUtils.byteArrayToString(this.transactionContext.getXid().getGlobalTransactionId()), ex); throw new SystemException(ex.getMessage()); } catch (RollbackException ex) { logger.error("{}| confirm native/remote branchs failed!", ByteUtils.byteArrayToString(this.transactionContext.getXid().getGlobalTransactionId()), ex); throw new SystemException(ex.getMessage()); } catch (HeuristicMixedException ex) { logger.error("{}| confirm native/remote branchs failed!", ByteUtils.byteArrayToString(this.transactionContext.getXid().getGlobalTransactionId()), ex); throw new SystemException(ex.getMessage()); } catch (HeuristicRollbackException ex) { logger.error("{}| confirm native/remote branchs failed!", ByteUtils.byteArrayToString(this.transactionContext.getXid().getGlobalTransactionId()), ex); throw new SystemException(ex.getMessage()); } } private synchronized void fireNativeParticipantConfirm() throws SystemException { boolean errorExists = false; ContainerContext container = this.beanFactory.getContainerContext(); for (int i = this.archiveList.size() - 1; i >= 0; i--) { CompensableArchive current = this.archiveList.get(i); if (current.isConfirmed()) { continue; } try { this.positive = true; this.archive = current; CompensableInvocation invocation = current.getCompensable(); if (invocation == null) { errorExists = true; logger.error( "{}| error occurred while confirming service: {}, please check whether the params of method(compensable-service) supports serialization.", ByteUtils.byteArrayToString(this.transactionContext.getXid().getGlobalTransactionId()), ByteUtils.byteArrayToString(current.getIdentifier().getGlobalTransactionId())); } else if (StringUtils.isNotBlank(invocation.getConfirmableKey())) { container.confirm(invocation); } else { current.setConfirmed(true); logger.info("{}| confirm: identifier= {}, resourceKey= {}, resourceXid= {}.", ByteUtils.byteArrayToString(transactionContext.getXid().getGlobalTransactionId()), ByteUtils.byteArrayToString(current.getIdentifier().getGlobalTransactionId()), current.getCompensableResourceKey(), current.getCompensableXid()); } } catch (RuntimeException rex) { errorExists = true; logger.error("{}| error occurred while confirming service: {}", ByteUtils.byteArrayToString(this.transactionContext.getXid().getGlobalTransactionId()), current, rex); } finally { this.archive = null; this.positive = null; } } if (errorExists) { throw new SystemException(); } } private synchronized void fireRemoteParticipantConfirm() throws HeuristicMixedException, HeuristicRollbackException, CommitRequiredException, SystemException { boolean committedExists = false; boolean rolledbackExists = false; boolean unFinishExists = false; boolean errorExists = false; for (int i = 0; i < this.resourceList.size(); i++) { XAResourceArchive current = this.resourceList.get(i); if (current.isCommitted()) { committedExists = true; continue; } else if (current.isRolledback()) { rolledbackExists = true; continue; } else if (current.isReadonly()) { continue; } CompensableLogger transactionLogger = this.beanFactory.getCompensableLogger(); XidFactory xidFactory = this.beanFactory.getCompensableXidFactory(); TransactionXid branchXid = (TransactionXid) current.getXid(); TransactionXid globalXid = xidFactory.createGlobalXid(branchXid.getGlobalTransactionId()); try { current.commit(globalXid, true); committedExists = true; current.setCommitted(true); current.setCompleted(true); logger.info("{}| confirm remote branch: {}", ByteUtils.byteArrayToString(branchXid.getGlobalTransactionId()), current.getDescriptor().getIdentifier()); } catch (XAException ex) { switch (ex.errorCode) { case XAException.XA_HEURCOM: committedExists = true; current.setHeuristic(true); current.setCommitted(true); current.setCompleted(true); break; case XAException.XA_HEURMIX: committedExists = true; rolledbackExists = true; current.setHeuristic(true); current.setCommitted(true); current.setRolledback(true); current.setCompleted(true); break; case XAException.XA_HEURRB: rolledbackExists = true; current.setHeuristic(true); current.setRolledback(true); current.setCompleted(true); break; case XAException.XA_HEURHAZ: unFinishExists = true; current.setHeuristic(true); logger.warn("{}| error occurred while confirming remote branch: {}, transaction is not exists!", ByteUtils.byteArrayToString(branchXid.getGlobalTransactionId()), current.getDescriptor().getIdentifier()); break; case XAException.XAER_RMFAIL: unFinishExists = true; logger.warn("{}| error occurred while confirming remote branch: {}, transaction is not exists!", ByteUtils.byteArrayToString(branchXid.getGlobalTransactionId()), current.getDescriptor().getIdentifier()); break; case XAException.XAER_NOTA: rolledbackExists = true; // TODO current.setRolledback(true); current.setCompleted(true); break; case XAException.XAER_RMERR: case XAException.XAER_INVAL: case XAException.XAER_PROTO: errorExists = false; logger.warn("{}| error occurred while confirming remote branch: {}, transaction is not exists!", ByteUtils.byteArrayToString(branchXid.getGlobalTransactionId()), current.getDescriptor().getIdentifier()); break; case XAException.XA_RBCOMMFAIL: case XAException.XA_RBDEADLOCK: case XAException.XA_RBINTEGRITY: case XAException.XA_RBOTHER: case XAException.XA_RBPROTO: case XAException.XA_RBROLLBACK: case XAException.XA_RBTIMEOUT: case XAException.XA_RBTRANSIENT: default: rolledbackExists = true; current.setRolledback(true); current.setCompleted(true); } } catch (RuntimeException rex) { errorExists = false; logger.warn("{}| error occurred while confirming remote branch: {}, transaction is not exists!", ByteUtils.byteArrayToString(branchXid.getGlobalTransactionId()), current.getDescriptor().getIdentifier()); } finally { if (current.isCompleted()) { transactionLogger.updateCoordinator(current); } } } if (committedExists && rolledbackExists) { throw new HeuristicMixedException(); } else if (unFinishExists) { throw new CommitRequiredException(); } else if (errorExists) { throw new SystemException(); } else if (rolledbackExists) { throw new HeuristicRollbackException(); } // else if (committedExists == false) { throw new XAException(XAException.XA_RDONLY); } } public int participantPrepare() throws RollbackRequiredException, CommitRequiredException { throw new RuntimeException("Not supported!"); } private void markCurrentBranchTransactionRollbackIfNecessary() { Transaction branch = this.transaction; boolean recoveried = this.transactionContext.isRecoveried(); if (recoveried == false && branch != null) /* used by participant only. */ { try { branch.setRollbackOnly(); } catch (IllegalStateException ex) { logger.info("The local transaction is not active.", ex); // tx in try-phase has been completed already. } catch (SystemException ex) { logger.warn("The local transaction is not active.", ex); // should never happen } catch (RuntimeException ex) { logger.warn("The local transaction is not active.", ex); // should never happen } } } public synchronized void participantRollback() throws IllegalStateException, SystemException { // Recover if transaction is recovered from tx-log. this.recoverIfNecessary(); if (this.transactionStatus != Status.STATUS_ROLLEDBACK) { this.fireRollback(); // TODO } } public synchronized void rollback() throws IllegalStateException, SystemException { if (this.transactionStatus == Status.STATUS_UNKNOWN) { throw new IllegalStateException(); } else if (this.transactionStatus == Status.STATUS_NO_TRANSACTION) { throw new IllegalStateException(); } else if (this.transactionStatus == Status.STATUS_COMMITTED) /* should never happen */ { throw new IllegalStateException(); } else if (this.transactionStatus == Status.STATUS_ROLLEDBACK) /* should never happen */ { logger.debug("Current transaction has already been rolled back."); } else { this.fireRollback(); } } private void fireRollback() throws IllegalStateException, SystemException { CompensableLogger compensableLogger = this.beanFactory.getCompensableLogger(); this.transactionStatus = Status.STATUS_ROLLING_BACK; this.markCurrentBranchTransactionRollbackIfNecessary(); this.transactionContext.setCompensating(true); compensableLogger.updateTransaction(this.getTransactionArchive()); SystemException systemEx = null; try { this.fireNativeParticipantCancel(); } catch (SystemException ex) { systemEx = ex; logger.info("{}| cancel native branchs failed!", ByteUtils.byteArrayToString(this.transactionContext.getXid().getGlobalTransactionId()), ex); } catch (RuntimeException ex) { systemEx = new SystemException(ex.getMessage()); logger.info("{}| cancel native branchs failed!", ByteUtils.byteArrayToString(this.transactionContext.getXid().getGlobalTransactionId()), ex); } try { this.fireRemoteParticipantCancel(); } catch (SystemException ex) { logger.info("{}| cancel remote branchs failed!", ByteUtils.byteArrayToString(this.transactionContext.getXid().getGlobalTransactionId()), ex); throw ex; } catch (RuntimeException ex) { logger.info("{}| cancel remote branchs failed!", ByteUtils.byteArrayToString(this.transactionContext.getXid().getGlobalTransactionId()), ex); throw new SystemException(ex.getMessage()); } if (systemEx != null) { throw systemEx; } else { this.transactionStatus = Status.STATUS_ROLLEDBACK; compensableLogger.updateTransaction(this.getTransactionArchive()); logger.info("{}| compensable transaction rolled back!", ByteUtils.byteArrayToString(transactionContext.getXid().getGlobalTransactionId())); } } public synchronized void recoveryRollback() throws RollbackRequiredException, SystemException { // Recover if transaction is recovered from tx-log. this.recoverIfNecessary(); this.fireRollback(); } private synchronized void fireNativeParticipantCancel() throws SystemException { boolean errorExists = false; ContainerContext container = this.beanFactory.getContainerContext(); for (int i = this.archiveList.size() - 1; i >= 0; i--) { CompensableArchive current = this.archiveList.get(i); if (current.isTried() == false) { logger.info( "{}| The operation in try phase is rolled back, so the cancel operation is ignored, compensable service: {}.", ByteUtils.byteArrayToString(transactionContext.getXid().getGlobalTransactionId()), ByteUtils.byteArrayToString(current.getIdentifier().getGlobalTransactionId())); continue; } else if (current.isCancelled()) { continue; } try { this.positive = false; this.archive = current; CompensableInvocation invocation = current.getCompensable(); if (invocation == null) { errorExists = true; logger.error( "{}| error occurred while cancelling service: {}, please check whether the params of method(compensable-service) supports serialization.", ByteUtils.byteArrayToString(this.transactionContext.getXid().getGlobalTransactionId()), ByteUtils.byteArrayToString(current.getIdentifier().getGlobalTransactionId())); } else if (StringUtils.isNotBlank(invocation.getCancellableKey())) { container.cancel(invocation); } else { current.setCancelled(true); logger.info("{}| cancel: identifier= {}, resourceKey= {}, resourceXid= {}.", ByteUtils.byteArrayToString(this.transactionContext.getXid().getGlobalTransactionId()), ByteUtils.byteArrayToString(current.getIdentifier().getGlobalTransactionId()), current.getCompensableResourceKey(), current.getCompensableXid()); } } catch (RuntimeException rex) { errorExists = true; logger.error("{}| error occurred while cancelling service: {}", ByteUtils.byteArrayToString(this.transactionContext.getXid().getGlobalTransactionId()), current, rex); } finally { this.archive = null; this.positive = null; } } if (errorExists) { throw new SystemException(); } } private synchronized void fireRemoteParticipantCancel() throws RollbackRequiredException, SystemException { boolean committedExists = false; boolean rolledbackExists = false; boolean unFinishExists = false; boolean errorExists = false; for (int i = 0; i < this.resourceList.size(); i++) { XAResourceArchive current = this.resourceList.get(i); if (current.isCommitted()) { committedExists = true; continue; } else if (current.isRolledback()) { rolledbackExists = true; continue; } else if (current.isReadonly()) { continue; } CompensableLogger transactionLogger = this.beanFactory.getCompensableLogger(); XidFactory xidFactory = this.beanFactory.getCompensableXidFactory(); TransactionXid branchXid = (TransactionXid) current.getXid(); TransactionXid globalXid = xidFactory.createGlobalXid(branchXid.getGlobalTransactionId()); try { current.rollback(globalXid); rolledbackExists = true; current.setRolledback(true); current.setCompleted(true); logger.info("{}| cancel remote branch: {}", ByteUtils.byteArrayToString(branchXid.getGlobalTransactionId()), current.getDescriptor().getIdentifier()); } catch (XAException xaex) { switch (xaex.errorCode) { case XAException.XA_HEURHAZ: unFinishExists = true; current.setHeuristic(true); logger.error("{}| error occurred while cancelling remote branch: {}", ByteUtils.byteArrayToString(branchXid.getGlobalTransactionId()), current, xaex); break; case XAException.XA_HEURMIX: committedExists = true; rolledbackExists = true; current.setCommitted(true); current.setRolledback(true); current.setHeuristic(true); current.setCompleted(true); break; case XAException.XA_HEURCOM: committedExists = true; current.setCommitted(true); current.setHeuristic(true); current.setCompleted(true); break; case XAException.XA_HEURRB: rolledbackExists = true; current.setRolledback(true); current.setHeuristic(true); current.setCompleted(true); break; case XAException.XA_RDONLY: current.setReadonly(true); current.setCompleted(true); break; case XAException.XAER_RMFAIL: unFinishExists = true; logger.error("{}| error occurred while cancelling remote branch: {}", ByteUtils.byteArrayToString(branchXid.getGlobalTransactionId()), current, xaex); break; case XAException.XAER_NOTA: rolledbackExists = true; current.setRolledback(true); current.setCompleted(true); break; case XAException.XAER_RMERR: default: errorExists = true; logger.error("{}| error occurred while cancelling remote branch: {}", ByteUtils.byteArrayToString(branchXid.getGlobalTransactionId()), current, xaex); } } catch (RuntimeException rex) { errorExists = true; logger.error("{}| error occurred while cancelling remote branch: {}", ByteUtils.byteArrayToString(branchXid.getGlobalTransactionId()), current, rex); } finally { if (current.isCompleted()) { transactionLogger.updateCoordinator(current); } } } if (committedExists && rolledbackExists) { throw new SystemException(XAException.XA_HEURMIX); } else if (unFinishExists) { throw new RollbackRequiredException(); } else if (errorExists) { throw new SystemException(XAException.XAER_RMERR); } else if (committedExists) { throw new SystemException(XAException.XA_HEURCOM); } // else if (rolledbackExists == false) { throw new SystemException(XAException.XA_RDONLY); } } public boolean enlistResource(XAResource xaRes) throws RollbackException, IllegalStateException, SystemException { if (this.transactionStatus == Status.STATUS_MARKED_ROLLBACK) { throw new RollbackException(); } else if (this.transactionStatus != Status.STATUS_ACTIVE) { throw new IllegalStateException(); } CompensableLogger compensableLogger = this.beanFactory.getCompensableLogger(); if (RemoteResourceDescriptor.class.isInstance(xaRes) == false) { throw new SystemException("Invalid resource!"); } XAResourceArchive resourceArchive = null; RemoteResourceDescriptor descriptor = (RemoteResourceDescriptor) xaRes; String identifier = descriptor.getIdentifier(); for (int i = 0; i < this.resourceList.size(); i++) { XAResourceArchive resource = this.resourceList.get(i); String resourceKey = resource.getDescriptor().getIdentifier(); if (CommonUtils.equals(identifier, resourceKey)) { resourceArchive = resource; break; } } if (resourceArchive == null) { XidFactory xidFactory = this.beanFactory.getCompensableXidFactory(); TransactionXid globalXid = this.transactionContext.getXid(); TransactionXid branchXid = xidFactory.createBranchXid(globalXid); resourceArchive = new XAResourceArchive(); resourceArchive.setXid(branchXid); resourceArchive.setDescriptor(descriptor); this.resourceList.add(resourceArchive); compensableLogger.createCoordinator(resourceArchive); logger.info("{}| enlist remote resource: {}.", ByteUtils.byteArrayToString(globalXid.getGlobalTransactionId()), identifier); return true; } else { return false; } } public boolean delistResource(XAResource xaRes, int flag) throws IllegalStateException, SystemException { CompensableLogger compensableLogger = this.beanFactory.getCompensableLogger(); if (flag == XAResource.TMFAIL && RemoteResourceDescriptor.class.isInstance(xaRes)) { RemoteResourceDescriptor descriptor = (RemoteResourceDescriptor) xaRes; String identifier = descriptor.getIdentifier(); for (Iterator<XAResourceArchive> itr = this.resourceList.iterator(); itr.hasNext();) { XAResourceArchive resource = itr.next(); String resourceKey = resource.getDescriptor().getIdentifier(); if (CommonUtils.equals(identifier, resourceKey)) { itr.remove(); break; } } compensableLogger.updateTransaction(this.getTransactionArchive()); } return true; } public void resume() throws SystemException { org.bytesoft.transaction.TransactionContext transactionContext = this.transaction.getTransactionContext(); TransactionXid xid = transactionContext.getXid(); List<CompensableArchive> compensableList = this.archiveMap.remove(xid); this.currentArchiveList.clear(); this.currentArchiveList.addAll(compensableList); } public void suspend() throws SystemException { org.bytesoft.transaction.TransactionContext transactionContext = this.transaction.getTransactionContext(); TransactionXid xid = transactionContext.getXid(); List<CompensableArchive> compensableList = new ArrayList<CompensableArchive>(); compensableList.addAll(this.currentArchiveList); this.currentArchiveList.clear(); this.archiveMap.put(xid, compensableList); } public void registerCompensable(CompensableInvocation invocation) { XidFactory xidFactory = this.beanFactory.getTransactionXidFactory(); CompensableArchive archive = new CompensableArchive(); TransactionXid globalXid = xidFactory.createGlobalXid(this.transactionContext.getXid().getGlobalTransactionId()); TransactionXid branchXid = xidFactory.createBranchXid(globalXid); archive.setIdentifier(branchXid); archive.setCompensable(invocation); this.archiveList.add(archive); this.currentArchiveList.add(archive); logger.info("{}| register compensable service: {}.", ByteUtils.byteArrayToString(this.transactionContext.getXid().getGlobalTransactionId()), ByteUtils.byteArrayToString(archive.getIdentifier().getGlobalTransactionId())); } public void registerSynchronization(Synchronization sync) throws RollbackException, IllegalStateException, SystemException { } public void registerTransactionListener(TransactionListener listener) { } public void registerTransactionResourceListener(TransactionResourceListener listener) { } public void onEnlistResource(Xid xid, XAResource xares) { String resourceKey = null; if (XAResourceDescriptor.class.isInstance(xares)) { XAResourceDescriptor descriptor = (XAResourceDescriptor) xares; resourceKey = descriptor.getIdentifier(); } else if (XAResourceArchive.class.isInstance(xares)) { XAResourceArchive resourceArchive = (XAResourceArchive) xares; XAResourceDescriptor descriptor = resourceArchive.getDescriptor(); resourceKey = descriptor == null ? null : descriptor.getIdentifier(); } CompensableLogger compensableLogger = this.beanFactory.getCompensableLogger(); if (this.transactionContext.isCompensating()) { this.archive.setCompensableXid(xid); this.archive.setCompensableResourceKey(resourceKey); compensableLogger.updateCompensable(this.archive); } else { for (int i = 0; i < this.currentArchiveList.size(); i++) { CompensableArchive compensableArchive = this.currentArchiveList.get(i); compensableArchive.setTransactionXid(xid); compensableArchive.setTransactionResourceKey(resourceKey); compensableLogger.createCompensable(compensableArchive); } } } public void onDelistResource(Xid xid, XAResource xares) { } public void onCommitSuccess(TransactionXid xid) { CompensableLogger compensableLogger = this.beanFactory.getCompensableLogger(); if (this.transactionContext.isCompensating()) { if (this.positive == null) { // ignore } else if (this.positive) { this.archive.setConfirmed(true); logger.info("{}| confirm: identifier= {}, resourceKey= {}, resourceXid= {}.", ByteUtils.byteArrayToString(transactionContext.getXid().getGlobalTransactionId()), ByteUtils.byteArrayToString(this.archive.getIdentifier().getGlobalTransactionId()), this.archive.getCompensableResourceKey(), this.archive.getCompensableXid()); } else { this.archive.setCancelled(true); logger.info("{}| cancel: identifier= {}, resourceKey= {}, resourceXid= {}.", ByteUtils.byteArrayToString(transactionContext.getXid().getGlobalTransactionId()), ByteUtils.byteArrayToString(this.archive.getIdentifier().getGlobalTransactionId()), this.archive.getCompensableResourceKey(), this.archive.getCompensableXid()); } compensableLogger.updateCompensable(this.archive); } else if (this.transactionContext.isCoordinator() && this.transactionContext.isPropagated() == false && this.transactionContext.getPropagationLevel() == 0) { for (Iterator<CompensableArchive> itr = this.currentArchiveList.iterator(); itr.hasNext();) { CompensableArchive compensableArchive = itr.next(); itr.remove(); // remove compensableArchive.setTried(true); // compensableLogger.updateCompensable(compensableArchive); logger.info("{}| try: identifier= {}, resourceKey= {}, resourceXid= {}.", ByteUtils.byteArrayToString(transactionContext.getXid().getGlobalTransactionId()), ByteUtils.byteArrayToString(compensableArchive.getIdentifier().getGlobalTransactionId()), compensableArchive.getTransactionResourceKey(), compensableArchive.getTransactionXid()); } TransactionArchive transactionArchive = this.getTransactionArchive(); transactionArchive.setCompensableStatus(Status.STATUS_COMMITTING); compensableLogger.updateTransaction(transactionArchive); logger.info("{}| try completed.", ByteUtils.byteArrayToString(transactionContext.getXid().getGlobalTransactionId())); } else { for (Iterator<CompensableArchive> itr = this.currentArchiveList.iterator(); itr.hasNext();) { CompensableArchive compensableArchive = itr.next(); itr.remove(); // remove compensableArchive.setTried(true); compensableLogger.updateCompensable(compensableArchive); logger.info("{}| try: identifier= {}, resourceKey= {}, resourceXid= {}.", ByteUtils.byteArrayToString(transactionContext.getXid().getGlobalTransactionId()), ByteUtils.byteArrayToString(compensableArchive.getIdentifier().getGlobalTransactionId()), compensableArchive.getTransactionResourceKey(), compensableArchive.getTransactionXid()); } } } public void recoverIfNecessary() throws SystemException { if (this.transactionContext.isRecoveried()) { this.recover(); // recover transaction status } } public synchronized void recover() throws SystemException { if (this.transactionStatus == Status.STATUS_COMMITTING) { this.recoverNativeResource(true); this.recoverRemoteResource(true); } else if (this.transactionStatus == Status.STATUS_ROLLING_BACK) { this.recoverNativeResource(false); this.recoverRemoteResource(false); } } private void recoverNativeResource(boolean positive) throws SystemException { XAResourceDeserializer resourceDeserializer = this.beanFactory.getResourceDeserializer(); CompensableLogger compensableLogger = this.beanFactory.getCompensableLogger(); boolean errorExists = false; for (int i = this.archiveList.size() - 1; i >= 0; i--) { CompensableArchive current = this.archiveList.get(i); String identifier = current.getCompensableResourceKey(); if (StringUtils.isBlank(identifier)) { continue; } else if (current.isConfirmed()) { continue; } else if (current.isCancelled()) { continue; } else if (current.isTried() == false) { logger.info("{}| the try operation is rolled back, so the cancel may be ignored, service: {}.", ByteUtils.byteArrayToString(transactionContext.getXid().getGlobalTransactionId()), ByteUtils.byteArrayToString(current.getIdentifier().getGlobalTransactionId())); continue; } try { try { XAResource xares = resourceDeserializer.deserialize(identifier); RecoveredResource resource = (RecoveredResource) xares; resource.recoverable(current.getCompensableXid()); if (positive) { current.setConfirmed(true); } else { current.setCancelled(true); } compensableLogger.updateCompensable(current); } catch (XAException xaex) { switch (xaex.errorCode) { case XAException.XAER_NOTA: break; case XAException.XAER_RMERR: logger.warn( "The database table 'bytejta' cannot found, the status of the current branch transaction is unknown!"); break; case XAException.XAER_RMFAIL: errorExists = true; logger.error("{}| error occurred while recovering the branch transaction service: {}", ByteUtils.byteArrayToString(this.transactionContext.getXid().getGlobalTransactionId()), ByteUtils.byteArrayToString(current.getIdentifier().getGlobalTransactionId()), xaex); break; default: logger.error("Illegal state, the status of the current branch transaction is unknown!"); } } catch (RuntimeException rex) { logger.error("Illegal resources, the status of the current branch transaction is unknown!"); } } catch (RuntimeException rex) { errorExists = true; TransactionXid transactionXid = this.transactionContext.getXid(); logger.error("{}| error occurred while recovering the branch transaction service: {}", ByteUtils.byteArrayToString(transactionXid.getGlobalTransactionId()), current, rex); } } // end-for if (errorExists) { throw new SystemException(); } } private void recoverRemoteResource(boolean positive) throws SystemException { // for (int i = 0; i < this.resourceList.size(); i++) { // XAResourceArchive archive = this.resourceList.get(i); // boolean xidExists = this.recover(archive); // } } // private boolean recover(XAResourceArchive archive) throws SystemException { // boolean xidRecovered = false; // // Xid thisXid = archive.getXid(); // byte[] thisGlobalTransactionId = thisXid.getGlobalTransactionId(); // try { // Xid[] array = archive.recover(XAResource.TMSTARTRSCAN | XAResource.TMENDRSCAN); // for (int j = 0; xidRecovered == false && array != null && j < array.length; j++) { // Xid thatXid = array[j]; // byte[] thatGlobalTransactionId = thatXid.getGlobalTransactionId(); // boolean formatIdEquals = thisXid.getFormatId() == thatXid.getFormatId(); // boolean transactionIdEquals = Arrays.equals(thisGlobalTransactionId, thatGlobalTransactionId); // xidRecovered = formatIdEquals && transactionIdEquals; // } // } catch (Exception ex) { // TransactionXid globalXid = this.transactionContext.getXid(); // logger.error("[{}] recover-resource failed. branch= {}", // ByteUtils.byteArrayToString(globalXid.getGlobalTransactionId()), // ByteUtils.byteArrayToString(globalXid.getBranchQualifier()), ex); // throw new SystemException(); // } // // archive.setRecovered(true); // // return xidRecovered; // } public synchronized void forgetQuietly() { TransactionXid xid = this.transactionContext.getXid(); try { this.forget(); } catch (SystemException ex) { logger.error("Error occurred while forgetting transaction: {}", ByteUtils.byteArrayToInt(xid.getGlobalTransactionId()), ex); } catch (RuntimeException ex) { logger.error("Error occurred while forgetting transaction: {}", ByteUtils.byteArrayToInt(xid.getGlobalTransactionId()), ex); } } public synchronized void forget() throws SystemException { LocalResourceCleaner resourceCleaner = this.beanFactory.getLocalResourceCleaner(); boolean success = true; Map<Xid, String> xidMap = new HashMap<Xid, String>(); for (int i = 0; i < this.archiveList.size(); i++) { CompensableArchive current = this.archiveList.get(i); Xid transactionXid = current.getTransactionXid(); Xid compensableXid = current.getCompensableXid(); if (transactionXid != null && current.isTried()) { xidMap.put(transactionXid, current.getTransactionResourceKey()); } if (compensableXid != null && (current.isConfirmed() || current.isCancelled())) { xidMap.put(compensableXid, current.getCompensableResourceKey()); } } for (Iterator<Map.Entry<Xid, String>> itr = xidMap.entrySet().iterator(); itr.hasNext();) { Map.Entry<Xid, String> entry = itr.next(); Xid xid = entry.getKey(); String resource = entry.getValue(); try { resourceCleaner.forget(xid, resource); } catch (RuntimeException rex) { success = false; logger.error("forget-transaction: error occurred while forgetting xid: {}", xid, rex); } } for (int i = 0; i < this.resourceList.size(); i++) { XAResourceArchive current = this.resourceList.get(i); if (current.isCompleted()) /* current.isHeuristic() */ { continue; // ignore } XidFactory xidFactory = this.beanFactory.getCompensableXidFactory(); TransactionXid branchXid = (TransactionXid) current.getXid(); TransactionXid globalXid = xidFactory.createGlobalXid(branchXid.getGlobalTransactionId()); try { current.forget(globalXid); } catch (XAException ex) { switch (ex.errorCode) { case XAException.XAER_NOTA: break; default: success = false; logger.error("forget-transaction: error occurred while forgetting branch: {}", branchXid, ex); } } catch (RuntimeException rex) { success = false; logger.error("forget-transaction: error occurred while forgetting branch: {}", branchXid, rex); } } if (success) { CompensableLogger compensableLogger = this.beanFactory.getCompensableLogger(); TransactionRepository compensableRepository = this.beanFactory.getCompensableRepository(); compensableLogger.deleteTransaction(this.getTransactionArchive()); compensableRepository.removeErrorTransaction(this.transactionContext.getXid()); compensableRepository.removeTransaction(this.transactionContext.getXid()); logger.info("{}| forget transaction.", ByteUtils.byteArrayToString(this.transactionContext.getXid().getGlobalTransactionId())); } else { throw new SystemException(); } } public XAResourceDescriptor getResourceDescriptor(String identifier) { XAResourceDescriptor descriptor = null; if (this.transaction != null) { descriptor = this.transaction.getResourceDescriptor(identifier); } if (descriptor == null) { for (int i = 0; descriptor == null && i < this.resourceList.size(); i++) { XAResourceArchive element = this.resourceList.get(i); XAResourceDescriptor xard = element.getDescriptor(); if (StringUtils.equals(identifier, xard.getIdentifier())) { descriptor = xard; } } } return descriptor; } public CompensableArchive getCompensableArchive() { return this.archive; } /** * only for recovery. */ public List<CompensableArchive> getCompensableArchiveList() { return this.archiveList; } /** * only for recovery. */ public List<XAResourceArchive> getParticipantArchiveList() { return this.resourceList; } public void setRollbackOnly() throws IllegalStateException, SystemException { throw new IllegalStateException(); } public void setRollbackOnlyQuietly() { throw new IllegalStateException(); } public boolean isLocalTransaction() { throw new IllegalStateException(); } public int getStatus() throws SystemException { return this.transactionStatus; } public int getTransactionStatus() { return this.transactionStatus; } public void setTransactionStatus(int status) { this.transactionStatus = status; } public boolean isTiming() { throw new IllegalStateException(); } public void setTransactionTimeout(int seconds) { throw new IllegalStateException(); } public TransactionContext getTransactionContext() { return this.transactionContext; } public void setBeanFactory(CompensableBeanFactory tbf) { this.beanFactory = tbf; } public Serializable getVariable(String key) { return this.variables.get(key); } public boolean isCurrentCompensableServiceTried() { return this.archive.isTried(); } public void setVariable(String key, Serializable variable) { this.variables.put(key, variable); } public boolean isParticipantStickyRequired() { return participantStickyRequired; } public void setParticipantStickyRequired(boolean participantStickyRequired) { this.participantStickyRequired = participantStickyRequired; } public Object getTransactionalExtra() { return transaction; } public void setTransactionalExtra(Object transactionalExtra) { this.transaction = (Transaction) transactionalExtra; } public Transaction getTransaction() { return (Transaction) this.getTransactionalExtra(); } public int getTransactionVote() { return transactionVote; } public void setTransactionVote(int transactionVote) { this.transactionVote = transactionVote; } public Map<String, Serializable> getVariables() { return variables; } public void setVariables(Map<String, Serializable> variables) { this.variables = variables; } }