/*
* Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com
* The software in this package is published under the terms of the CPAL v1.0
* license, a copy of which has been included with this distribution in the
* LICENSE.txt file.
*/
package org.mule.runtime.core.util.xa;
import javax.transaction.Status;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Base class for an XAResource implementation.
*
* @param <T> type of the {@link org.mule.runtime.core.util.xa.AbstractXaTransactionContext} created for each transaction
*/
public abstract class DefaultXASession<T extends AbstractXaTransactionContext> implements XAResource {
protected transient Logger logger = LoggerFactory.getLogger(getClass());
protected Xid localXid;
protected AbstractXAResourceManager<T> resourceManager;
protected T localContext;
public DefaultXASession(AbstractXAResourceManager resourceManager) {
this.localContext = null;
this.localXid = null;
this.resourceManager = resourceManager;
}
public XAResource getXAResource() {
return this;
}
public Object getResourceManager() {
return resourceManager;
}
//
// XAResource implementation
//
public boolean isSameRM(XAResource xares) throws XAException {
return xares instanceof DefaultXASession && ((DefaultXASession) xares).getResourceManager().equals(resourceManager);
}
public void start(Xid xid, int flags) throws XAException {
if (logger.isDebugEnabled()) {
logger.debug(new StringBuilder(128).append("Thread ").append(Thread.currentThread())
.append(flags == TMNOFLAGS ? " starts" : flags == TMJOIN ? " joins" : " resumes")
.append(" work on behalf of transaction branch ").append(xid).toString());
}
// A local transaction is already begun
if (this.localContext != null) {
throw new XAException(XAException.XAER_PROTO);
}
// This session has already been associated with an xid
if (this.localXid != null) {
throw new XAException(XAException.XAER_PROTO);
}
switch (flags) {
// a new transaction
case TMNOFLAGS:
case TMJOIN:
default:
try {
localContext = createTransactionContext(xid);
resourceManager.beginTransaction(localContext);
} catch (Exception e) {
// TODO MULE-863: Is logging necessary?
logger.error("Could not create new transactional resource", e);
throw (XAException) new XAException(e.getMessage()).initCause(e);
}
break;
case TMRESUME:
localContext = resourceManager.getSuspendedTransactionalResource(xid);
if (localContext == null) {
throw new XAException(XAException.XAER_NOTA);
}
// TODO: resume context
resourceManager.removeSuspendedTransactionalResource(xid);
break;
}
localXid = xid;
resourceManager.addActiveTransactionalResource(localXid, localContext);
}
public void end(Xid xid, int flags) throws XAException {
if (logger.isDebugEnabled()) {
logger.debug(new StringBuilder(128).append("Thread ").append(Thread.currentThread())
.append(flags == TMSUSPEND ? " suspends" : flags == TMFAIL ? " fails" : " ends")
.append(" work on behalf of transaction branch ").append(xid).toString());
}
// No transaction is already begun
if (localContext == null) {
throw new XAException(XAException.XAER_NOTA);
}
// This session has already been associated with an xid
if (localXid == null || !localXid.equals(xid)) {
throw new XAException(XAException.XAER_PROTO);
}
try {
switch (flags) {
case TMSUSPEND:
// TODO: suspend context
resourceManager.addSuspendedTransactionalResource(localXid, localContext);
resourceManager.removeActiveTransactionalResource(localXid);
break;
case TMFAIL:
resourceManager.setTransactionRollbackOnly(localContext);
break;
case TMSUCCESS: // no-op
default: // no-op
break;
}
} catch (ResourceManagerException e) {
throw (XAException) new XAException(XAException.XAER_RMERR).initCause(e);
}
localXid = null;
localContext = null;
}
public void commit(Xid xid, boolean onePhase) throws XAException {
if (xid == null) {
throw new XAException(XAException.XAER_PROTO);
}
T context = resourceManager.getActiveTransactionalResource(xid);
if (context == null) {
if (logger.isDebugEnabled()) {
logger.debug("Commit called without a transaction context");
}
commitDanglingTransaction(xid, onePhase);
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Committing transaction branch " + xid);
}
if (context.status == Status.STATUS_MARKED_ROLLBACK) {
throw new XAException(XAException.XA_RBROLLBACK);
}
try {
if (context.status != Status.STATUS_PREPARED) {
if (onePhase) {
resourceManager.prepareTransaction(context);
} else {
throw new XAException(XAException.XAER_PROTO);
}
}
resourceManager.commitTransaction(context);
localContext = null;
} catch (ResourceManagerException e) {
throw (XAException) new XAException(XAException.XAER_RMERR).initCause(e);
}
resourceManager.removeActiveTransactionalResource(xid);
resourceManager.removeSuspendedTransactionalResource(xid);
}
public void rollback(Xid xid) throws XAException {
if (xid == null) {
throw new XAException(XAException.XAER_PROTO);
}
AbstractTransactionContext context = resourceManager.getActiveTransactionalResource(xid);
if (context == null) {
if (logger.isDebugEnabled()) {
logger.debug("Rollback called without a transaction context");
}
rollbackDandlingTransaction(xid);
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Rolling back transaction branch " + xid);
}
try {
resourceManager.rollbackTransaction(context);
localContext = null;
} catch (ResourceManagerException e) {
throw (XAException) new XAException(XAException.XAER_RMERR).initCause(e);
}
resourceManager.removeActiveTransactionalResource(xid);
resourceManager.removeSuspendedTransactionalResource(xid);
}
public int prepare(Xid xid) throws XAException {
if (xid == null) {
throw new XAException(XAException.XAER_PROTO);
}
T context = resourceManager.getTransactionalResource(xid);
if (context == null) {
throw new XAException(XAException.XAER_NOTA);
}
if (logger.isDebugEnabled()) {
logger.debug("Preparing transaction branch " + xid);
}
if (context.status == Status.STATUS_MARKED_ROLLBACK) {
throw new XAException(XAException.XA_RBROLLBACK);
}
try {
return resourceManager.prepareTransaction(context);
} catch (ResourceManagerException e) {
throw (XAException) new XAException(XAException.XAER_RMERR).initCause(e);
}
}
public void forget(Xid xid) throws XAException {
if (logger.isDebugEnabled()) {
logger.debug("Forgetting transaction branch " + xid);
}
AbstractTransactionContext context = resourceManager.getTransactionalResource(xid);
if (context == null) {
throw new XAException(XAException.XAER_NOTA);
}
resourceManager.removeActiveTransactionalResource(xid);
resourceManager.removeSuspendedTransactionalResource(xid);
}
public int getTransactionTimeout() throws XAException {
return (int) (resourceManager.getDefaultTransactionTimeout() / 1000);
}
public boolean setTransactionTimeout(int timeout) throws XAException {
resourceManager.setDefaultTransactionTimeout(timeout * 1000);
return false;
}
public T getTransactionContext() {
return this.localContext;
}
/**
* Commits a dangling transaction that can be caused by the failure of one of the XAResource involved in the transaction or a
* crash of the transaction manager.
*
* @param xid transaction identifier
* @param onePhase if the commit should be done using only one phase commit
* @throws XAException
*/
protected abstract void commitDanglingTransaction(Xid xid, boolean onePhase) throws XAException;
/**
* Commits a dangling transaction that can be caused by the failure of one of the XAResource involved in the transaction or a
* crash of the transaction manager.
*
* @param xid transaction identifier
* @throws XAException
*/
protected abstract void rollbackDandlingTransaction(Xid xid) throws XAException;
/**
* Creates a new transaction context with the given transaction identifier
*
* @param xid transaction identifier
* @return the new transaction context
*/
abstract protected T createTransactionContext(Xid xid);
}