/* * Copyright (C) 2006-2013 Bitronix Software (http://www.bitronix.be) * * 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. */ package bitronix.tm.internal; import bitronix.tm.BitronixXid; import bitronix.tm.resource.common.ResourceBean; import bitronix.tm.resource.common.XAResourceHolder; import bitronix.tm.utils.Decoder; import bitronix.tm.utils.MonotonicClock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.transaction.xa.XAException; import javax.transaction.xa.XAResource; import java.util.Date; /** * {@link XAResourceHolder} state container. * Instances are kept in the transaction and bound to / unbound from the {@link XAResourceHolder} as the * resource participates in different transactions. A {@link XAResourceHolder} without {@link XAResourceHolderState} * is considered to be in local transaction mode. * <p>Objects of this class also expose resource specific configuration like the unique resource name.</p> * <p>The {@link XAResource} state during a transaction participation is also contained: assigned XID, transaction * start / end state...</p> * <p>There is exactly one {@link XAResourceHolderState} object per {@link XAResourceHolder} per * {@link javax.transaction.Transaction}.</p> * * @see bitronix.tm.resource.common.ResourceBean * @author Ludovic Orban */ public class XAResourceHolderState { private final static Logger log = LoggerFactory.getLogger(XAResourceHolderState.class); private final ResourceBean bean; private final XAResourceHolder xaResourceHolder; private volatile BitronixXid xid; private volatile boolean started; private volatile boolean ended; private volatile boolean suspended; private volatile Date transactionTimeoutDate; private volatile boolean isTimeoutAlreadySet; private volatile boolean failed; private volatile int hashCode; public XAResourceHolderState(XAResourceHolder resourceHolder, ResourceBean bean) { this.bean = bean; this.xaResourceHolder = resourceHolder; started = false; ended = false; suspended = false; isTimeoutAlreadySet = false; xid = null; hashCode = 17 * bean.hashCode(); } public XAResourceHolderState(XAResourceHolderState resourceHolderState) { this.bean = resourceHolderState.bean; this.xaResourceHolder = resourceHolderState.xaResourceHolder; started = false; ended = false; suspended = false; isTimeoutAlreadySet = false; xid = null; hashCode = 17 * bean.hashCode(); } public BitronixXid getXid() { return xid; } public void setXid(BitronixXid xid) throws BitronixSystemException { if (log.isDebugEnabled()) { log.debug("assigning <" + xid + "> to <" + this + ">"); } if (this.xid != null && !xid.equals(this.xid)) throw new BitronixSystemException("a XID has already been assigned to " + this); this.xid = xid; hashCode = 17 * (bean.hashCode() + (xid != null ? xid.hashCode() : 0)); } public XAResource getXAResource() { return xaResourceHolder.getXAResource(); } public XAResourceHolder getXAResourceHolder() { return xaResourceHolder; } public Date getTransactionTimeoutDate() { return transactionTimeoutDate; } public void setTransactionTimeoutDate(Date transactionTimeoutDate) { this.transactionTimeoutDate = transactionTimeoutDate; } public String getUniqueName() { return bean.getUniqueName(); } public boolean getUseTmJoin() { return bean.getUseTmJoin(); } public int getTwoPcOrderingPosition() { return bean.getTwoPcOrderingPosition(); } public boolean getIgnoreRecoveryFailures() { return bean.getIgnoreRecoveryFailures(); } public boolean isEnded() { return ended; } public boolean isStarted() { return started; } public boolean isSuspended() { return suspended; } public boolean isFailed() { return failed; } public void end(int flags) throws XAException { boolean ended = this.ended; boolean suspended = this.suspended; if (this.ended && (flags == XAResource.TMSUSPEND)) { if (log.isDebugEnabled()) { log.debug("resource already ended, changing state to suspended: " + this); } this.suspended = true; return; } if (this.ended) throw new BitronixXAException("resource already ended: " + this, XAException.XAER_PROTO); if (flags == XAResource.TMSUSPEND) { if (!this.started) throw new BitronixXAException("resource hasn't been started, cannot suspend it: " + this, XAException.XAER_PROTO); if (this.suspended) throw new BitronixXAException("resource already suspended: " + this, XAException.XAER_PROTO); if (log.isDebugEnabled()) { log.debug("suspending " + this + " with " + Decoder.decodeXAResourceFlag(flags)); } suspended = true; } else { if (log.isDebugEnabled()) { log.debug("ending " + this + " with " + Decoder.decodeXAResourceFlag(flags)); } ended = true; } try { getXAResource().end(xid, flags); if (log.isDebugEnabled()) { log.debug("ended " + this + " with " + Decoder.decodeXAResourceFlag(flags)); } } catch(XAException ex) { // could mean failed or unilaterally rolled back failed = true; throw ex; } finally { this.suspended = suspended; this.ended = ended; this.started = false; } } public void start(int flags) throws XAException { boolean suspended = this.suspended; boolean started = this.started; if (this.ended && (flags == XAResource.TMRESUME)) { if (log.isDebugEnabled()) { log.debug("resource already ended, changing state to resumed: " + this); } this.suspended = false; return; } if (flags == XAResource.TMRESUME) { if (!this.suspended) throw new BitronixXAException("resource hasn't been suspended, cannot resume it: " + this, XAException.XAER_PROTO); if (!this.started) throw new BitronixXAException("resource hasn't been started, cannot resume it: " + this, XAException.XAER_PROTO); if (log.isDebugEnabled()) { log.debug("resuming " + this + " with " + Decoder.decodeXAResourceFlag(flags)); } suspended = false; } else { if (this.started) throw new BitronixXAException("resource already started: " + this, XAException.XAER_PROTO); if (log.isDebugEnabled()) { log.debug("starting " + this + " with " + Decoder.decodeXAResourceFlag(flags)); } started = true; } if (!isTimeoutAlreadySet && transactionTimeoutDate != null && bean.getApplyTransactionTimeout()) { int timeoutInSeconds = (int) ((transactionTimeoutDate.getTime() - MonotonicClock.currentTimeMillis() + 999L) / 1000L); timeoutInSeconds = Math.max(1, timeoutInSeconds); // setting a timeout of 0 means resetting -> set it to at least 1 if (log.isDebugEnabled()) { log.debug("applying resource timeout of " + timeoutInSeconds + "s on " + this); } getXAResource().setTransactionTimeout(timeoutInSeconds); isTimeoutAlreadySet = true; } getXAResource().start(xid, flags); this.suspended = suspended; this.started = started; this.ended = false; if (log.isDebugEnabled()) { log.debug("started " + this + " with " + Decoder.decodeXAResourceFlag(flags)); } } @Override public int hashCode() { return hashCode; } @Override public boolean equals(Object obj) { if (!(obj instanceof XAResourceHolderState) || this.hashCode != obj.hashCode()) return false; XAResourceHolderState other = (XAResourceHolderState) obj; return equals(other.bean, bean) && equals(other.xid, xid); } private boolean equals(Object obj1, Object obj2) { if (obj1 == obj2) return true; if (obj1 == null || obj2 == null) return false; return obj1.equals(obj2); } @Override public String toString() { return "an XAResourceHolderState with uniqueName=" + bean.getUniqueName() + " XAResource=" + getXAResource() + (started ? " (started)":"") + (ended ? " (ended)":"") + (suspended ? " (suspended)":"") + " with XID " + xid; } }