![JAR search and dependency download from the Maven repository](/logo.png)
org.csc.phynixx.xa.PhynixxXAResource Maven / Gradle / Ivy
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 java.util.ArrayList;
import java.util.List;
import javax.transaction.TransactionManager;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
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;
/**
* 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 xaConnection = 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.xaConnection = 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) {
@Override
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.xaConnection != null) {
XATransactionalBranch transactionalBranch = this.xaConnection.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
*/
@Override
public IPhynixxXAConnection getXAConnection() {
return this.xaConnection;
}
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.
*/
@Override
public void start(Xid xid, int flags) throws XAException {
try {
if (xid == null) {
throw new XAException(XAException.XAER_INVAL);
}
// if resuming or joining an existing transaction
if (flags == TMRESUME) {
this.xaConnection.resumeTransactionalBranch(xid);
} else if (flags == TMJOIN || flags == TMNOFLAGS) {
this.xaConnection.startTransactionalBranch(xid);
}
LOG.debug("PhynixxXAResource[" + this.getId() + "]:start xid='" + xid + "' flags='"
+ ConstantsPrinter.getXAResourceMessage(flags) + "'" + " Connected to " + this.xaConnection);
// 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);
}
}
@Override
public void commit(Xid xid, boolean onePhase) throws XAException {
XATransactionalBranch transactionalBranch = this.xaConnection.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.xaConnection.closeTransactionalBranch(xid);
this.xaConnection.close();
} 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.
*/
@Override
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.xaConnection.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.xaConnection.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)
*/
@Override
public void rollback(Xid xid) throws XAException {
{
try {
LOG.debug("PhynixxXAResource[" + this.getId() + "]:rollback started xid=" + xid);
XATransactionalBranch transactionalBranch = this.xaConnection.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.xaConnection.closeTransactionalBranch(xid);
this.xaConnection.close();
}
} 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.
*/
@Override
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.xaConnection.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.xaConnection.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
*/
@Override
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.xaConnection.toGlobalTransactionBranch();
// must find connection for this transaction
if (transactionalBranch == null) {
return; //
}
this.xaConnection.closeTransactionalBranch(xid);
this.xaConnection.close();
} finally {
// stop monitoring the timeout
this.setTimeOutActive(false);
}
}
@Override
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.
*/
@Override
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
}
@SuppressWarnings({ "unchecked", "rawtypes" })
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.xaConnection != null) {
this.xaConnection.doClose();
}
this.closed = true;
this.notifyClosed();
if (LOG.isDebugEnabled()) {
LOG.debug("PhynixxXAResource[" + this.getId() + "]:closed ");
}
}
/**
* the system is recovered by the xaResourceFactory representing the
* persistence management system,
*/
@Override
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.
* @return true if transaction timeout value is set successfully; otherwise
* false.
*/
@Override
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;
**/
}
@Override
public final boolean equals(Object obj) {
return super.equals(obj);
}
@Override
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(); }
*/
@Override
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);
}
}
}