org.csc.phynixx.xa.PhynixxXAResource Maven / Gradle / Ivy
Show all versions of phynixx-xa Show documentation
package org.csc.phynixx.xa;
/*
* #%L
* phynixx-xa
* %%
* Copyright (C) 2014 csc
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import org.csc.phynixx.common.exceptions.DelegatedRuntimeException;
import org.csc.phynixx.common.exceptions.ExceptionUtils;
import org.csc.phynixx.common.logger.IPhynixxLogger;
import org.csc.phynixx.common.logger.PhynixxLogManager;
import org.csc.phynixx.connection.IPhynixxConnection;
import org.csc.phynixx.watchdog.ITimeoutCondition;
import org.csc.phynixx.watchdog.TimeoutCondition;
import org.csc.phynixx.watchdog.log.ConditionViolatedLog;
import org.csc.phynixx.xa.IPhynixxXAResourceListener.IPhynixxXAResourceEvent;
import javax.transaction.TransactionManager;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import java.util.ArrayList;
import java.util.List;
/**
* A transactional branch may be associated with differnt XAResources (TMJOIN) or a XAResource may be shared with different transaction branches
*
* Therefore a transactional branch might be associated to many XAResources or to many transactions (==XIDs)
*
* A XAresourec can be initialized with a connection. This connection is used,
* the first time an TX is opened on the XAResource.
*
* Any following TX get an new connection
*
* @author christoph
*/
public class PhynixxXAResource implements IPhynixxXAResource {
private static final long DEFAULT_TIMEOUT = Long.MAX_VALUE; // msecs - no time out at all
private static final IPhynixxLogger LOG = PhynixxLogManager.getLogger(PhynixxXAResource.class);
private Object xaId = null;
private PhynixxXAResourceFactory xaResourceFactory = null;
/**
* @supplierCardinality 0..1
*/
private PhynixxManagedXAConnection xaConnectionHandle = null;
private volatile boolean closed = false;
private ITimeoutCondition timeoutCondition = null;
private boolean supportsTimeOut= false;
/**
* TODO timeOut ueber einen Listener steuern und konfigierbar machen
*/
public PhynixxXAResource(
String xaId,
TransactionManager transactionManager,
PhynixxXAResourceFactory xaResourceFactory) {
this.xaId = xaId;
this.xaResourceFactory = xaResourceFactory;
this.xaConnectionHandle = new PhynixxManagedXAConnection(this, transactionManager, xaResourceFactory.getXATransactionalBranchRepository(), xaResourceFactory.getManagedConnectionFactory());
// start a watchdog to watch the timeout ...
// this condition is not active
if( this.isSupportsTimeOut()) {
this.timeoutCondition = new TimeoutCondition(DEFAULT_TIMEOUT) {
public void conditionViolated() {
PhynixxXAResource.this.conditionViolated();
}
};
// it is registered at the resource xaResourceFactory's watchdog ....
xaResourceFactory.registerWatchCondition(this.timeoutCondition);
}
}
/**
* called when the current XAresource is expired (time out occurred)
* The current Impl. does nothing but marked the associated TX as rollback only
* The underlying core connection are not treated ....
*
* There are two different situation that have to be handled
* If the connection expires because of a long running method call the connection isn't
* treated and the XAResource is marked as rollback only. The rollback is done by the Transactionmanager.
* If the TX expires because the Transactionmanagers doesn't answer, the underlying connection is rolledback
* and the XAResource is marked as rollback only (N.B. rolling back the connection
* marks the XAResource as heuristically rolled back)
*/
public void conditionViolated() {
try {
if (this.xaConnectionHandle != null) {
XATransactionalBranch transactionalBranch = this.xaConnectionHandle.toGlobalTransactionBranch();
if (transactionalBranch != null) {
transactionalBranch.setRollbackOnly(true);
}
}
if (LOG.isInfoEnabled()) {
String logString = "PhynixxXAResource.expired :: XAResource " + this.getId() + " is expired (time out occurred) and all associated TX are rollbacked ";
LOG.info(new ConditionViolatedLog(this.timeoutCondition, logString).toString());
}
} finally {
// no monitoring anymore
setTimeOutActive(false);
}
}
private void setTimeOutActive(boolean active) {
if(this.timeoutCondition!=null) {
this.timeoutCondition.setActive(false);
}
}
public boolean isSupportsTimeOut() {
return supportsTimeOut;
}
public void setSupportsTimeOut(boolean supportsTimeOut) {
throw new IllegalStateException("Timeout isn't yet supported. Will be in version 2.1");
}
/**
* accessing the XAConnection forces the XAResource to enlist to the Transaction. {@link #getXAConnection()} must be called in the appropriate TransactionContext
*
* @return
*/
public IPhynixxXAConnection getXAConnection() {
return this.xaConnectionHandle;
}
public boolean isClosed() {
return closed;
}
/**
* transaction branch is interchangeably with transaction context
*
*
* situation : XAResource is associtaed with XID(1)
* flags=TMNOFLAGS with XID(1)
* result : XAResource creates a new transactional context
*
*
*
* situation : XAResource is associtaed with XID(1)
* flags=TMJOIN/TNRESUME with XID(1) (may be different in branch)
* result : XAResource joins the transactional context with the XAResource already associated with XID(1)
* If TMRESUME is specified, start is to resume a suspended transaction branch specified in xid.
*
*
*
*
*
* This method starts work on behalf of a transaction branch.
*
*
*
* If TMJOIN is specified, start is for joining an exisiting transaction branch xid.
*
* If neither TMJOIN nor TMRESUME is specified and the transaction branch specified in
* xid already exists, the resource manager throw the XAException
* with XAER_DUPID error code.
*
* If the XAResource has a current connection and flags ==TMJOIN/TMRESUME, the current connection is
* substituted by the connection of the existing TX.
*
* @param xid A global transaction identifier to be associated with the resource.
* @param flags One of TMNOFLAGS, TMJOIN, or TMRESUME.
* @throws: XAException An error has occurred.
* Possible exceptions are XA_RB*, XAER_RMERR, XAER_RMFAIL, XAER_DUPID, XAER_OUTSIDE, XAER_NOTA, XAER_INVAL, or XAER_PROTO.
*/
public void start(Xid xid, int flags) throws XAException {
try {
if (xid == null) {
throw new XAException(XAException.XAER_INVAL);
}
// Find the current Branch
XATransactionalBranch transactionalBranch = null;
// if resuming or joining an existing transaction
if (flags == TMRESUME) {
this.xaConnectionHandle.resumeTransactionalBranch(xid);
} else if (flags == TMJOIN || flags == TMNOFLAGS) {
this.xaConnectionHandle.startTransactionalBranch(xid);
}
LOG.debug(
"PhynixxXAResource[" + this.getId() + "]:start xid='"
+ xid
+ "' flags='"
+ ConstantsPrinter.getXAResourceMessage(flags)
+ "'"
+ " Connected to " +
this.xaConnectionHandle);
// start monitoring the timeout
this.setTimeOutActive(true);
} catch (XAException xaExc) {
LOG.error("PhynixxXAResource[" + this.getId() + "]:start xid='"
+ xid // maybe null, but doesn't matter for logging
+ "' flags='"
+ ConstantsPrinter.getXAResourceMessage(flags)
+ "'"
+ " ERROR " + ConstantsPrinter.getXAErrorCode(xaExc.errorCode));
throw xaExc;
} catch (Exception ex) {
LOG.error("PhynixxXAResource.start(" + xid + "," + flags + ") on XAResourceProgressState " + this.xaId + " :: " + ex + "\n" + ExceptionUtils.getStackTrace(ex));
throw new DelegatedRuntimeException("start(" + xid + "," + flags + ") on XAResourceProgressState " + this.xaId, ex);
}
}
public void commit(Xid xid, boolean onePhase) throws XAException {
XATransactionalBranch transactionalBranch = this.xaConnectionHandle.toGlobalTransactionBranch();
if (xid == null) {
LOG.error("No XID");
throw new XAException(XAException.XAER_INVAL);
}
if (transactionalBranch == null) {
LOG.error("XAConnection is not associated to a global Transaction (expected to XID=" + xid + ")");
throw new XAException(XAException.XAER_PROTO);
}
// assert that the current xaConnection is associated to this XID
if (!transactionalBranch.getXid().equals(xid)) {
LOG.error("XAResource " + this + " isnt't active for XID=" + xid);
throw new XAException(XAException.XAER_PROTO);
}
// Find the current Branch
try {
transactionalBranch.commit(onePhase);
} catch (XAException xaExc) {
LOG.error("PhynixxXAResource[" + this.getId() + "]:end xid='"
+ xid
+ "' onePhase='" + onePhase
+ " ERROR " + ConstantsPrinter.getXAErrorCode(xaExc.errorCode));
throw xaExc;
} catch (Exception ex) {
LOG.error("PhynixxXAResource.commit(" + xid + "," + onePhase + ") on XAResourceProgressState " + this.xaId + " :: " + ex + "\n" + ExceptionUtils.getStackTrace(ex));
throw new DelegatedRuntimeException("commit(" + xid + "," + onePhase + ") on XAResourceProgressState " + this.xaId, ex);
} finally {
try {
// Branch isn't active for the current XAresource
this.xaConnectionHandle.closeTransactionalBranch(xid);
} finally {
// stop monitoring the timeout
this.setTimeOutActive(false);
}
}
}
/**
* finds the transactional branch of the current XAResource associated with die XID
*
* Prepares to perform a commit. May actually perform a commit
* in the flag commitOnPrepare is set to true.
*
* This method is called to ask the resource manager to prepare for a
* transaction commit of the transaction specified in xid.
*
* @param xid A global transaction identifier.
* @return A value indicating the resource manager's vote on the outcome of
* the transaction. The possible values are: XA_RDONLY or XA_OK.
* If the resource manager wants to roll back the transaction, it should do so by
* throwing an appropriate XAException in the prepare method.
* @throws XAException An error has occurred.
* Possible exception values are:
* XA_RB*, XAER_RMERR, XAER_RMFAIL, XAER_NOTA, XAER_INVAL, or XAER_PROTO.
*/
public int prepare(Xid xid) throws XAException {
try {
LOG.debug(
"PhynixxXAResource[" + this.getId() + "]:prepare prepare to perform a commit for XID=" + xid);
XATransactionalBranch transactionalBranch = this.xaConnectionHandle.toGlobalTransactionBranch();
if (xid == null) {
LOG.error("No XID");
throw new XAException(XAException.XAER_INVAL);
}
if (transactionalBranch == null) {
LOG.error("XAConnection is not associated to a global Transaction");
throw new XAException(XAException.XAER_PROTO);
}
// assert that the current xaConnection is associated to this XID
if (!transactionalBranch.getXid().equals(xid)) {
LOG.error("XAResource " + this + " isnt't active for XID=" + xid);
throw new XAException(XAException.XAER_PROTO);
}
// must find connection for this transaction
int retVal = transactionalBranch.prepare();
if (retVal == XAResource.XA_RDONLY) {
this.xaConnectionHandle.closeTransactionalBranch(xid);
}
return retVal;
} catch (XAException xaExc) {
LOG.error("PhynixxXAResource[" + this.getId() + "]:prepare xid='"
+ xid
+ " ERROR " + ConstantsPrinter.getXAErrorCode(xaExc.errorCode));
throw xaExc;
} catch (Exception ex) {
LOG.error("PhynixxXAResource.prepare(" + xid + ") on XAResourceProgressState " + this.xaId + " :: " + ex + "\n" + ExceptionUtils.getStackTrace(ex));
throw new DelegatedRuntimeException("prepare(" + xid + ") on XAResourceProgressState " + this.xaId, ex);
}
}
/*
* finds the transactional branch of the current XAResource associated with die XID
*
* This method informs the resource manager to roll back
* work done on behalf of a transaction branch.
* Upon return, the resource manager has rolled back the branch's work and
* has released all held resources.
*
* @param xid A global transaction identifier.
* @throws XAException An error has occurred.
* Possible XAExceptions are
* XA_HEURHAZ, XA_HEURCOM,XA_HEURRB, XA_HEURMIX,
* XAER_RMERR, XAER_RMFAIL, XAER_NOTA,XAER_INVAL, or XAER_PROTO.
*
*
* @see javax.transaction.xa.XAResource#rollback(javax.transaction.xa.Xid)
*/
public void rollback(Xid xid) throws XAException {
{
try {
LOG.debug("PhynixxXAResource[" + this.getId() + "]:rollback started xid=" + xid);
XATransactionalBranch transactionalBranch = this.xaConnectionHandle.toGlobalTransactionBranch();
if (xid == null) {
LOG.error("No XID");
throw new XAException(XAException.XAER_INVAL);
}
if (transactionalBranch == null) {
LOG.error("XAConnection is not associated to a global Transaction");
throw new XAException(XAException.XAER_PROTO);
}
// assert that the current xaConnection is associated to this XID
if (!transactionalBranch.getXid().equals(xid)) {
LOG.error("XAResource " + this + " isnt't active for XID=" + xid);
throw new XAException(XAException.XAER_PROTO);
}
// Find the current Branch
if (transactionalBranch.isInActive()) {
throw new XAException(XAException.XAER_PROTO);
}
try {
transactionalBranch.rollback();
} finally {
// Branch isn't active for the current XAResource
this.xaConnectionHandle.closeTransactionalBranch(xid);
}
} catch (XAException xaExc) {
LOG.error("PhynixxXAResource[" + this.getId() + "]:rollback xid='"
+ xid
+ " ERROR " + ConstantsPrinter.getXAErrorCode(xaExc.errorCode));
throw xaExc;
} catch (Exception ex) {
LOG.error("PhynixxXAResource.rollback(" + xid + ") on XAResourceProgressState " + this.xaId + " :: " + ex + "\n" + ExceptionUtils.getStackTrace(ex));
throw new DelegatedRuntimeException("rollback(" + xid + ") on XAResourceProgressState " + this.xaId, ex);
} finally {
// stop monitoring the timeout
this.setTimeOutActive(false);
}
}
}
/**
* This method ends the work performed on behalf of a transaction branch.
*
* The resource manager dissociates the XA resource from the transaction branch specified and let the transaction
* be completed.
* If TMSUSPEND is specified in flags, the transaction branch is temporarily suspended in incomplete state.
* The transaction context is in suspended state and must be resumed via start with TMRESUME specified.
*
* If TMFAIL is specified, the portion of work has failed. The resource manager
* may mark the transaction as rollback only.
*
* the spec doesn't no fix the order of commit/rollback and end and we have to take of both orders.
* If end comes before rollback/commit, the transactional branch has to be freeze.
* If rollback/commit comes before end, the transactional branch has to be closed
*
* If TMSUCCESS is specified, the portion of work has completed successfully. end is called in Transaction.delistResource
*
* @param xid A global transaction identifier.
* @param flags If true, the resource manager should use a one-phase commit protocol
* to commit the work done on behalf of xid.
* @throws: XAException An error has occurred. Possible XAException values
* are XAER_RMERR, XAER_RMFAIL,XAER_NOTA, XAER_INVAL, XAER_PROTO,XA_RB*.
*/
public void end(Xid xid, int flags) throws XAException {
try {
//not tested XS
LOG.debug("PhynixxXAResource:end");
LOG.debug(
"PhynixxXAResource[" + this.getId() + "]:end xid='" + xid + "' flags='" + ConstantsPrinter.getXAResourceMessage(flags) + "'");
if (xid == null) {
LOG.error("No XID");
throw new XAException(XAException.XAER_INVAL);
}
XATransactionalBranch transactionalBranch = this.xaConnectionHandle.toGlobalTransactionBranch();
if (transactionalBranch == null) {
LOG.error("XAConnection is not associated to a global Transaction");
throw new XAException(XAException.XAER_PROTO);
}
// assert that the current xaConnection is associated to this XID
if (!transactionalBranch.getXid().equals(xid)) {
LOG.error("XAResource " + this + " isnt't active for XID=" + xid);
throw new XAException(XAException.XAER_PROTO);
}
if (flags == TMSUSPEND) {
this.xaConnectionHandle.suspendTransactionalBranch(xid);
} else if (flags == TMSUCCESS) {
// XAProtocol is finsihed an the branch isn't needed any longer
if (transactionalBranch.isXAProtocolFinished()) {
transactionalBranch.close();
LOG.debug("XAResource " + this + " closed gracefully ");
}
} else if (flags == TMFAIL) {
transactionalBranch.setRollbackOnly(true);
}
} catch (XAException xaExc) {
LOG.error("PhynixxXAResource[" + this.getId() + "]:end xid='"
+ xid
+ "' flags='"
+ ConstantsPrinter.getXAResourceMessage(flags)
+ "'"
+ " ERROR " + ConstantsPrinter.getXAErrorCode(xaExc.errorCode));
throw xaExc;
} catch (Exception ex) {
LOG.error("PhynixxXAResource.end(" + xid + "," + flags + ") on XAResourceProgressState " + this.xaId + " :: " + ex + "\n" + ExceptionUtils.getStackTrace(ex));
throw new DelegatedRuntimeException("end(" + xid + "," + flags + ") on XAResourceProgressState " + this.xaId, ex);
}
}
/**
* finds the transactional branch of the current XAResource associated with die XID and closes it without commit or explicit rollback
*
* @param xid
* @throws XAException
*/
public void forget(Xid xid) throws XAException {
try {
LOG.debug("PhynixxXAResource[" + this.getId() + "]:forget forget with Xid");
if (xid == null)
throw new XAException(XAException.XAER_INVAL);
XATransactionalBranch transactionalBranch = this.xaConnectionHandle.toGlobalTransactionBranch();
// must find connection for this transaction
if (transactionalBranch == null) {
return; //
}
this.xaConnectionHandle.closeTransactionalBranch(xid);
} finally {
// stop monitoring the timeout
this.setTimeOutActive(false);
}
}
public int getTransactionTimeout() throws XAException {
return (int) (this.timeoutCondition.getTimeout()) * 1000;
}
/**
* This method is called to determine if the resource
* manager instance represented by the target object is the same
* as the resource manager instance represented by the parameter xares .
*
* The resource manager is reresented by the ResourceFactory.
*
* @param xaResource An XAResource object.
* @return true if same RM instance; otherwise false.
*/
public boolean isSameRM(XAResource xaResource) throws XAException {
if (this.equals(xaResource)) { // if the same object
LOG.debug("PhynixxXAResource[" + this.getId() + "]:isSameRM isSameRM");
return true; // then definitely the same RM
}
if (!(xaResource instanceof PhynixxXAResource)) {
// if it's not one of our wrappers
LOG.debug("PhynixxXAResource[" + this.getId() + "]:isSameRM not isSameRM");
return false; // then it's definitely not the same RM
}
PhynixxXAResource sampleXARes = (PhynixxXAResource) xaResource;
try {
// cast to something more convenient
if (xaResourceFactory.equals(sampleXARes.xaResourceFactory)) {
// if they originate from same data source
LOG.debug("PhynixxXAResource[" + this.getId() + "]:isSameRM isSameRM (equal XAResourceFactory)");
return true; // then they're the same RM
} else {
LOG.debug("PhynixxXAResource[" + this.getId() + "]:isSameRM not isSameRM (not equal XAResourceFactory)");
return false;
}
} catch (Exception ex) {
LOG.error("PhynixxXAResource.isSameRM(" + sampleXARes.xaId + ") on XAResourceProgressState " + this.xaId + " :: " + ex + "\n" + ExceptionUtils.getStackTrace(ex));
throw new DelegatedRuntimeException("isSameRM(" + sampleXARes.xaId + ") on XAResourceProgressState " + this.xaId, ex);
}
}
/**
* finds the transactional branch of the current XAResource associated with die XID
*
* Close this XA XAResourceProgressState.
* All depending Connection are closed
*/
public void close() {
if (this.xaConnectionHandle != null) {
XATransactionalBranch transactionalBranch = this.xaConnectionHandle.toGlobalTransactionBranch();
if (transactionalBranch != null) {
transactionalBranch.close();
}
}
this.closed = true;
this.notifyClosed();
LOG.debug("PhynixxXAResource[" + this.getId() + "]:closed ");
}
/**
* the system is recovered by the xaResourceFactory representing the persistence management system,
*/
public Xid[] recover(int flags) throws XAException {
LOG.info("PhynixxXAResource[" + this.getId() + "]:recover recover flags=" + ConstantsPrinter.getXAResourceMessage(flags));
if (flags != TMSTARTRSCAN && flags != TMENDRSCAN && flags != TMNOFLAGS) {
throw new XAException(XAException.XAER_INVAL);
}
Xid[] retval = null;
retval = this.xaResourceFactory.recover(); // get all valid Xids
return retval;
}
/**
* This method sets the transaction timeout value for this XAResource instance.
* Once set, this timeout value is effective until setTransactionTimeout
* is invoked again with a different value.
* To reset the timeout value to the default value used by the resource manager,
* set the value to zero.
* If the timeout operation is performed successfully, the method returns true; otherwise false.
* If a resource manager does not support transaction timeout value to be set explicitly,
* this method returns false.
*
* @param seconds An positive integer specifying the timout value in seconds.
* Zero resets the transaction timeout value to the default one used
* by the resource manager. A negative value results in XAExceptio to be thrown
* with XAER_INVAL error code.
* @throws XAException An error has occurred. Possible exception values are: XAER_RMERR, XAER_RMFAIL, or XAER_INVAL.
* @returns true if transaction timeout value is set successfully; otherwise false.
*/
public boolean setTransactionTimeout(int seconds) throws XAException {
return false;
/**
if(!this.isSupportsTimeOut()) {
throw new IllegalStateException("TimeOut is not supported --> call setSupportsTimeOut");
}
if (seconds < 0) {
throw new XAException(XAException.XAER_INVAL);
}
long msecs = seconds * 1000;
if (seconds == 0) {
msecs = DEFAULT_TIMEOUT;
}
this.timeoutCondition.resetCondition(msecs);
return true;
**/
}
public final boolean equals(Object obj) {
return super.equals(obj);
}
public final int hashCode() {
return super.hashCode();
}
public Object getId() {
return this.xaId;
}
/*
public String toString()
{
StringBuffer sb = new StringBuffer();
sb.append("PhynixxXAResource "+this.xaId+" :\n");
sb.append(" is closed =<"+this.isClosed() + ">\n");
sb.append(" next timeOut =<"+this.nextTimeout + ">\n");
sb.append(" timeOut period =<"+this.timeoutPeriod + ">\n");
sb.append(" timeOut secs =<"+this.timeoutSecs + ">\n");
sb.append(" transaction manager=<"+this.transactionManager + ">\n");
return sb.toString();
}
*/
public String toString() {
return this.xaResourceFactory.getId().toString() + "." + this.xaId.toString();
}
private List> listeners = new ArrayList();
public void addXAResourceListener(IPhynixxXAResourceListener listener) {
if (!listeners.contains(listener)) {
this.listeners.add(listener);
}
}
public void removeXAResourceListener(IPhynixxXAResourceListener listener) {
this.listeners.remove(listener);
}
private void notifyClosed() {
IPhynixxXAResourceEvent event = new PhynixxXAResourceEvent(this);
for (int i = 0; i < listeners.size(); i++) {
IPhynixxXAResourceListener listener = listeners.get(i);
listener.closed(event);
}
}
}