All Downloads are FREE. Search and download functionalities are using the official Maven repository.

bitronix.tm.resource.jdbc.lrc.LrcXAResource Maven / Gradle / Ivy

There is a newer version: 62
Show newest version
/*
 * 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.resource.jdbc.lrc;

import bitronix.tm.internal.BitronixXAException;
import bitronix.tm.internal.LogDebugCheck;
import bitronix.tm.utils.Decoder;

import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import java.sql.Connection;
import java.sql.SQLException;

/**
 * XAResource implementation for a non-XA JDBC connection emulating XA with Last Resource Commit.
 * 

The XA protocol flow is implemented by this state machine:

*
 * NO_TX
 *   |
 *   | start(TMNOFLAGS)
 *   |
 *   |       end(TMFAIL)
 * STARTED -------------- NO_TX
 *   |
 *   | end(TMSUCCESS)
 *   |
 *   |    start(TMJOIN)
 * ENDED ---------------- STARTED
 *   |\
 *   | \  commit (one phase)
 *   |  ----------------- NO_TX
 *   |
 *   | prepare()
 *   |
 *   |       commit() or
 *   |       rollback()
 * PREPARED ------------- NO_TX
 * 
* {@link XAResource#TMSUSPEND} and {@link XAResource#TMRESUME} are not supported. * * @author Ludovic Orban */ @SuppressWarnings({"Duplicates"}) public class LrcXAResource implements XAResource { public static final int NO_TX = 0; public static final int STARTED = 1; public static final int ENDED = 2; public static final int PREPARED = 3; private static final String XID_EQUALS = ", XID="; private static final String XID_NOT_NULL = "XID cannot be null"; private static final String RESOURCE_NEVER_STARTED = "resource never started on XID "; private static final String RESOURCE_NEVER_ENDED = "resource never ended on XID "; private static final String RESOURCE_ALREADY_STARTED = "resource already started on XID "; private static final String FLAG_EQUALS = ", flag="; private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LrcXAResource.class.toString()); private final Connection connection; private volatile Xid xid; private volatile boolean autocommitActiveBeforeStart; private volatile int state = NO_TX; /** * Constructor LrcXAResource creates a new LrcXAResource instance. * * @param connection * of type Connection */ public LrcXAResource(Connection connection) { this.connection = connection; } /** * Method getState returns the state of this LrcXAResource object. * * @return the state (type int) of this LrcXAResource object. */ public int getState() { return state; } /** * Method commit ... * * @param xid * of type Xid * @param onePhase * of type boolean * * @throws XAException * when */ @Override public void commit(Xid xid, boolean onePhase) throws XAException { if (xid == null) { throw new BitronixXAException(XID_NOT_NULL, XAException.XAER_INVAL); } if (state == NO_TX) { throw new BitronixXAException(RESOURCE_NEVER_STARTED + xid, XAException.XAER_PROTO); } else if (state == STARTED) { throw new BitronixXAException(RESOURCE_NEVER_ENDED + xid, XAException.XAER_PROTO); } else if (state == ENDED) { if (onePhase) { if (LogDebugCheck.isDebugEnabled()) { log.finer("OK to commit with 1PC, old state=" + xlatedState() + XID_EQUALS + xid); } try { connection.commit(); } catch (SQLException ex) { throw new BitronixXAException("error committing (one phase) non-XA resource", XAException.XAER_RMERR, ex); } } else { throw new BitronixXAException("resource never prepared on XID " + xid, XAException.XAER_PROTO); } } else if (state == PREPARED) { if (!onePhase) { if (this.xid.equals(xid)) { if (LogDebugCheck.isDebugEnabled()) { log.finer("OK to commit, old state=" + xlatedState() + XID_EQUALS + xid); } } else { throw new BitronixXAException(RESOURCE_ALREADY_STARTED + this.xid + " - cannot commit it on another XID " + xid, XAException.XAER_PROTO); } } else { throw new BitronixXAException("cannot commit in one phase as resource has been prepared on XID " + xid, XAException.XAER_PROTO); } } this.state = NO_TX; this.xid = null; checkAutoCommit(autocommitActiveBeforeStart, connection); } /** * Method end ... * * @param xid * of type Xid * @param flag * of type int * * @throws XAException * when */ @Override public void end(Xid xid, int flag) throws XAException { if (flag != XAResource.TMSUCCESS && flag != XAResource.TMFAIL) { throw new BitronixXAException("unsupported end flag " + Decoder.decodeXAResourceFlag(flag), XAException.XAER_RMERR); } if (xid == null) { throw new BitronixXAException(XID_NOT_NULL, XAException.XAER_INVAL); } if (state == NO_TX) { throw new BitronixXAException(RESOURCE_NEVER_STARTED + xid, XAException.XAER_PROTO); } else if (state == STARTED) { if (this.xid.equals(xid)) { if (LogDebugCheck.isDebugEnabled()) { log.finer("OK to end, old state=" + xlatedState() + XID_EQUALS + xid + FLAG_EQUALS + Decoder.decodeXAResourceFlag(flag)); } } else { throw new BitronixXAException(RESOURCE_ALREADY_STARTED + this.xid + " - cannot end it on another XID " + xid, XAException.XAER_PROTO); } } else if (state == ENDED) { throw new BitronixXAException("resource already ended on XID " + xid, XAException.XAER_PROTO); } else if (state == PREPARED) { throw new BitronixXAException("cannot end, resource already prepared on XID " + xid, XAException.XAER_PROTO); } if (flag == XAResource.TMFAIL) { try { connection.rollback(); state = NO_TX; this.xid = null; return; } catch (SQLException ex) { throw new BitronixXAException("error rolling back resource on end", XAException.XAER_RMERR, ex); } } this.state = ENDED; } /** * Method forget ... * * @param xid * of type Xid * * @throws XAException * when */ @Override public void forget(Xid xid) throws XAException { //Nothing needed } /** * Method getTransactionTimeout returns the transactionTimeout of this LrcXAResource object. * * @return the transactionTimeout (type int) of this LrcXAResource object. * * @throws XAException * when */ @Override public int getTransactionTimeout() throws XAException { return 0; } /** * Method isSameRM ... * * @param xaResource * of type XAResource * * @return boolean * * @throws XAException * when */ @Override public boolean isSameRM(XAResource xaResource) throws XAException { return xaResource == this; } /** * Method prepare ... * * @param xid * of type Xid * * @return int * * @throws XAException * when */ @Override public int prepare(Xid xid) throws XAException { if (xid == null) { throw new BitronixXAException(XID_NOT_NULL, XAException.XAER_INVAL); } if (state == NO_TX) { throw new BitronixXAException(RESOURCE_NEVER_STARTED + xid, XAException.XAER_PROTO); } else if (state == STARTED) { throw new BitronixXAException(RESOURCE_NEVER_ENDED + xid, XAException.XAER_PROTO); } else if (state == ENDED) { if (this.xid.equals(xid)) { if (LogDebugCheck.isDebugEnabled()) { log.finer("OK to prepare, old state=" + xlatedState() + XID_EQUALS + xid); } } else { throw new BitronixXAException(RESOURCE_ALREADY_STARTED + this.xid + " - cannot prepare it on another XID " + xid, XAException.XAER_PROTO); } } else if (state == PREPARED) { throw new BitronixXAException("resource already prepared on XID " + this.xid, XAException.XAER_PROTO); } try { connection.commit(); this.state = PREPARED; return XAResource.XA_OK; } catch (SQLException ex) { throw new BitronixXAException("error preparing non-XA resource", XAException.XAER_RMERR, ex); } } /** * Method recover ... * * @param flags * of type int * * @return Xid[] * * @throws XAException * when */ @Override public Xid[] recover(int flags) throws XAException { return new Xid[0]; } /** * Method rollback ... * * @param xid * of type Xid * * @throws XAException * when */ @Override public void rollback(Xid xid) throws XAException { if (xid == null) { throw new BitronixXAException(XID_NOT_NULL, XAException.XAER_INVAL); } if (state == NO_TX) { throw new BitronixXAException(RESOURCE_NEVER_STARTED + xid, XAException.XAER_PROTO); } else if (state == STARTED) { throw new BitronixXAException(RESOURCE_NEVER_ENDED + xid, XAException.XAER_PROTO); } else if (state == ENDED) { if (this.xid.equals(xid)) { if (LogDebugCheck.isDebugEnabled()) { log.finer("OK to rollback, old state=" + xlatedState() + XID_EQUALS + xid); } } else { throw new BitronixXAException(RESOURCE_ALREADY_STARTED + this.xid + " - cannot roll it back on another XID " + xid, XAException.XAER_PROTO); } } else if (state == PREPARED) { this.state = NO_TX; throw new BitronixXAException("resource committed during prepare on XID " + this.xid, XAException.XA_HEURCOM); } try { connection.rollback(); } catch (SQLException ex) { throw new BitronixXAException("error preparing non-XA resource", XAException.XAER_RMERR, ex); } finally { this.state = NO_TX; this.xid = null; } checkAutoCommit(autocommitActiveBeforeStart, connection); } /** * Method setTransactionTimeout ... * * @param seconds * of type int * * @return boolean * * @throws XAException * when */ @Override public boolean setTransactionTimeout(int seconds) throws XAException { return false; } /** * Method start ... * * @param xid * of type Xid * @param flag * of type int * * @throws XAException * when */ @Override public void start(Xid xid, int flag) throws XAException { if (flag != XAResource.TMNOFLAGS && flag != XAResource.TMJOIN) { throw new BitronixXAException("unsupported start flag " + Decoder.decodeXAResourceFlag(flag), XAException.XAER_RMERR); } if (xid == null) { throw new BitronixXAException(XID_NOT_NULL, XAException.XAER_INVAL); } if (state == NO_TX) { if (this.xid != null) { throw new BitronixXAException(RESOURCE_ALREADY_STARTED + this.xid, XAException.XAER_PROTO); } else { if (flag == XAResource.TMJOIN) { throw new BitronixXAException("resource not yet started", XAException.XAER_PROTO); } else { if (LogDebugCheck.isDebugEnabled()) { log.finer("OK to start, old state=" + xlatedState() + XID_EQUALS + xid + FLAG_EQUALS + Decoder.decodeXAResourceFlag(flag)); } this.xid = xid; } } } else if (state == STARTED) { throw new BitronixXAException(RESOURCE_ALREADY_STARTED + this.xid, XAException.XAER_PROTO); } else if (state == ENDED) { if (flag == XAResource.TMNOFLAGS) { throw new BitronixXAException("resource already registered XID " + this.xid, XAException.XAER_DUPID); } else { if (xid.equals(this.xid)) { if (LogDebugCheck.isDebugEnabled()) { log.finer("OK to join, old state=" + xlatedState() + XID_EQUALS + xid + FLAG_EQUALS + Decoder.decodeXAResourceFlag(flag)); } } else { throw new BitronixXAException(RESOURCE_ALREADY_STARTED + this.xid + " - cannot start it on more than one XID at a time", XAException.XAER_RMERR); } } } else if (state == PREPARED) { throw new BitronixXAException("resource already prepared on XID " + this.xid, XAException.XAER_PROTO); } try { autocommitActiveBeforeStart = connection.getAutoCommit(); if (autocommitActiveBeforeStart) { if (LogDebugCheck.isDebugEnabled()) { log.finer("disabling autocommit mode on non-XA connection"); } connection.setAutoCommit(false); } this.state = STARTED; } catch (SQLException ex) { throw new BitronixXAException("cannot disable autocommit on non-XA connection", XAException.XAER_RMERR, ex); } } /** * Method xlatedState ... * * @return String */ private String xlatedState() { switch (state) { case NO_TX: return "NO_TX"; case STARTED: return "STARTED"; case ENDED: return "ENDED"; case PREPARED: return "PREPARED"; default: return "!invalid state (" + state + ")!"; } } private static void checkAutoCommit(boolean autocommitActiveBeforeStart, Connection connection) throws BitronixXAException { try { if (autocommitActiveBeforeStart) { if (LogDebugCheck.isDebugEnabled()) { log.finer("enabling back autocommit mode on non-XA connection"); } connection.setAutoCommit(true); } } catch (SQLException ex) { throw new BitronixXAException("cannot reset autocommit on non-XA connection", XAException.XAER_RMERR, ex); } } /** * Method toString ... * * @return String */ @Override public String toString() { return "a JDBC LrcXAResource in state " + xlatedState(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy