/* Copyright (c) 2001-2019, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


package org.hsqldb.jdbc.pool;

import javax.transaction.xa.XAResource;
import javax.transaction.xa.XAException;
import javax.transaction.xa.Xid;

// These require a global transaction API:
//import javax.transaction.HeuristicMixedException;
//import javax.transaction.HeuristicCommitException;
//import javax.transaction.HeuristicRollbackException;
import org.hsqldb.jdbc.JDBCConnection;

import java.sql.SQLException;

import org.hsqldb.HsqlException;

// @(#)$Id: JDBCXAResource.java 5969 2019-04-27 14:59:54Z fredt $

Used by a global transaction service to control HSQLDB transactions. Not for use by end-users. End manage global transactions using transaction APIs such as JTA.

According to section 12.3 of the JDBC 3.0 spec, there is a 1:1 correspondence between XAConnection and XAResource, and A given XAConnection object may be associated with at most one transaction at a time. Therefore, there may be at any time at most one transaction managed by a XAResource object. One implication is, the XAResource can track the current transaction state with a scalar. Another implication is, the Xids for most of the XAResource interface methods just introduce unnecessary complexity and an unnecessary point of failure-- there can be only one transaction for this object, so why track another identifier for it. My strategy is to just "validate" that the Xid does not change within a transaction. Exceptions to this are the commit and rollback methods, which the JDBC spec says can operate against any XAResource instance from the same XADataSource. N.b. The JDBC Spec does not state whether the prepare and forget methods are XAResource-specific or XADataSource-specific.

Author:Blaine Simpson (blaine dot simpson at admc dot com)
See Also:
Version:2.3.3
Since:2.0.0
/** * Used by a global transaction service to control HSQLDB transactions. * Not for use by end-users. * End manage global transactions using transaction APIs such as JTA. * <P> * According to section 12.3 of the JDBC 3.0 spec, there is a * 1:1 correspondence between XAConnection and XAResource, and * <I>A given XAConnection object may be associated with at most one * transaction at a time</I>. * Therefore, there may be at any time at most one transaction * managed by a XAResource object. * One implication is, the XAResource can track the current transaction * state with a scalar. * Another implication is, the Xids for most of the XAResource interface * methods just introduce unnecessary complexity and an unnecessary point * of failure-- there can be only one transaction for this object, so * why track another identifier for it. * My strategy is to just "validate" that the Xid does not change * within a transaction. * Exceptions to this are the commit and rollback methods, which the * JDBC spec says can operate against any XAResource instance from * the same XADataSource. * N.b. The JDBC Spec does not state whether the prepare and forget * methods are XAResource-specific or XADataSource-specific. * * @version 2.3.3 * @since 2.0.0 * @author Blaine Simpson (blaine dot simpson at admc dot com) * @see javax.transaction.xa.XAResource */
public class JDBCXAResource implements XAResource {
@todo: Make thread safe. Figure out how to ensure that orphaned transactions to do not make a memory leak in JDBCXADataSource.resources. I.e., JDBCXADataSource.removeResource() must be called even for all transactions, even aborted ones. Maybe tx managers are already obligated to call one of commit/forget/rollback for even transactions for which they have called start???... TEST THIS. (They may only need to commit/forget/rollback if prepare has been called?). The answer may be to implement Timeouts.
/** * @todo: * Make thread safe. * Figure out how to ensure that orphaned transactions to do not make * a memory leak in JDBCXADataSource.resources. I.e., * JDBCXADataSource.removeResource() must be called even for all * transactions, even aborted ones. Maybe tx managers are * already obligated to call one of commit/forget/rollback for * even transactions for which they have called start???... TEST THIS. * (They may only need to commit/forget/rollback if prepare has been * called?). * The answer may be to implement Timeouts. */
private JDBCConnection connection; private boolean originalAutoCommitMode; static int XA_STATE_INITIAL = 0; static int XA_STATE_STARTED = 1; static int XA_STATE_ENDED = 2; static int XA_STATE_PREPARED = 3; static int XA_STATE_DISPOSED = 4; int state = XA_STATE_INITIAL; private JDBCXADataSource xaDataSource; Xid xid = null; public boolean withinGlobalTransaction() { return state == XA_STATE_STARTED; }
Params:
  • xid – Xid
Throws:
  • XAException – if the given Xid is the not the Xid of the current transaction for this XAResource object.
/** * * @throws XAException if the given Xid is the not the Xid of the current * transaction for this XAResource object. * @param xid Xid */
private void validateXid(Xid xid) throws XAException { if (xid == null) { throw new XAException("Null Xid"); } if (this.xid == null) { throw new XAException( "There is no live transaction for this XAResource"); } if (!xid.equals(this.xid)) { throw new XAException( "Given Xid is not that associated with this XAResource object"); } }
Constructs a resource using the given data source and connection.
Params:
  • xaDataSource – JDBCXADataSource
  • connection – A non-wrapped JDBCConnection which we need in order to do real (non-wrapped) commits, rollbacks, etc. This is not for the end user. We need the real thing.
/** * Constructs a resource using the given data source and connection. * * @param xaDataSource JDBCXADataSource * @param connection A non-wrapped JDBCConnection which we need in order to * do real (non-wrapped) commits, rollbacks, etc. This is not for the end * user. We need the real thing. */
public JDBCXAResource(JDBCXADataSource xaDataSource, JDBCConnection connection) { this.connection = connection; this.xaDataSource = xaDataSource; } JDBCXADataSource getXADataSource() { return xaDataSource; }
Per the JDBC 3.0 spec, this commits the transaction for the specified Xid, not necessarily for the transaction associated with this XAResource object.
Params:
  • xid – Xid
  • onePhase – boolean
Throws:
/** * Per the JDBC 3.0 spec, this commits the transaction for the specified * Xid, not necessarily for the transaction associated with this XAResource * object. * * @param xid Xid * @param onePhase boolean * @throws XAException on error */
public void commit(Xid xid, boolean onePhase) throws XAException { // Comment out following debug statement before public release: /* System.err.println("Performing a " + (onePhase ? "1-phase" : "2-phase") + " commit on " + xid); */ JDBCXAResource resource = xaDataSource.getResource(xid); if (resource == null) { throw new XAException("The XADataSource has no such Xid: " + xid); } resource.commitThis(onePhase); }
This commits the connection associated with this XAResource.
Params:
  • onePhase – boolean
Throws:
  • XAException – generically, since the more specific exceptions require a JTA API to compile.
/** * This commits the connection associated with <i>this</i> XAResource. * * @throws XAException generically, since the more specific exceptions * require a JTA API to compile. * @param onePhase boolean */
public void commitThis(boolean onePhase) throws XAException { if (onePhase && state == XA_STATE_PREPARED) { throw new XAException( "Transaction is in a 2-phase state when 1-phase is requested"); } if ((!onePhase) && state != XA_STATE_PREPARED) { throw new XAException("Attempt to do a 2-phase commit when " + "transaction is not prepared"); } //if (!onePhase) { // throw new XAException( // "Sorry. HSQLDB has not implemented 2-phase commits yet"); //} try { /** * @todo: Determine if work was committed, rolled back, or both, * and return appropriate Heuristic*Exception. * connection.commit(); * Commits the real, physical conn. */ connection.commit(); } catch (SQLException se) { throw new XAException(se.toString()); } dispose(); } private void dispose() throws XAException { state = XA_STATE_DISPOSED; xaDataSource.removeResource(xid); xid = null; try { connection.setAutoCommit(originalAutoCommitMode); // real/phys. } catch (SQLException se) { throw new XAException(se.toString()); } } public void end(Xid xid, int flags) throws XAException { validateXid(xid); if (state != XA_STATE_STARTED) { throw new XAException("Invalid XAResource state"); } /** @todo - probably all flags can be ignored */ if (flags == XAResource.TMSUCCESS) {} state = XA_STATE_ENDED; }
The XAResource API spec indicates implies that this is only for 2-phase transactions. I guess that one-phase transactions need to call rollback() to abort. I think we want this JDBCXAResource instance to be garbage-collectable after (a) this method is called, and (b) the tx manager releases its handle to it.
Params:
  • xid – Xid
Throws:
See Also:
  • forget.forget(Xid)
/** * The XAResource API spec indicates implies that this is only for 2-phase * transactions. I guess that one-phase transactions need to call rollback() * to abort. I think we want this JDBCXAResource instance to be * garbage-collectable after (a) this method is called, and (b) the tx * manager releases its handle to it. * * @see javax.transaction.xa.XAResource#forget(Xid) * @param xid Xid * @throws XAException on error */
public void forget(Xid xid) throws XAException { /** * Should this method not attempt to clean up the aborted * transaction by rolling back or something? Maybe the * tx manager will already have called rollback() if * it were necessary? */ validateXid(xid); if (state != XA_STATE_PREPARED) { throw new XAException( "Attempted to forget a XAResource that " + "is not in a heuristically completed state"); } dispose(); state = XA_STATE_INITIAL; }
Throws:
  • XAException – on error
Returns:int
/** * * @throws XAException on error * @return int */
public int getTransactionTimeout() throws XAException { throw new XAException("Transaction timeouts not implemented yet"); }
Stub. See implementation comment in the method for why this is not implemented yet.
Params:
  • xares – XAResource
Throws:
Returns:false.
/** * Stub. See implementation comment in the method for why this is not * implemented yet. * * @param xares XAResource * @return false. * @throws XAException on error */
public boolean isSameRM(XAResource xares) throws XAException { if (!(xares instanceof JDBCXAResource)) { return false; } return xaDataSource == ((JDBCXAResource) xares).getXADataSource(); }
Vote on whether to commit the global transaction. We assume Xid may be different from this, as in commit() method.
Params:
  • xid – Xid
Throws:
  • XAException – to vote negative.
Returns:commitType of XA_RDONLY or XA_OK. (Actually only XA_OK now).
/** * Vote on whether to commit the global transaction. We assume Xid may be * different from this, as in commit() method. * * @throws XAException to vote negative. * @return commitType of XA_RDONLY or XA_OK. (Actually only XA_OK now). * @param xid Xid */
public int prepare(Xid xid) throws XAException { JDBCXAResource resource = xaDataSource.getResource(xid); if (resource == null) { throw new XAException("The XADataSource has no such Xid: " + xid); } return resource.prepareThis(); } public int prepareThis() throws XAException { /** * @todo: This is where the real 2-phase work should be done to * determine if a commit done here would succeed or not. */ /** * @todo: May improve performance to return XA_RDONLY whenever * possible, but I don't know. * Could determine this by checking if DB instance is in RO mode, * or perhaps (with much difficulty) to determine if there have * been any modifications performed. */ if (state != XA_STATE_ENDED) { throw new XAException("Invalid XAResource state"); } try { connection.getSession().prepareCommit(); } catch (HsqlException e) { state = XA_STATE_PREPARED; // ??? didn't prepare throw new XAException(e.getMessage()); } state = XA_STATE_PREPARED; return XA_OK; // As noted above, should check non-committed work. }
Obtain a list of Xids of the current resource manager for XAResources currently in the 'prepared' * state. According to the JDBC 3.0 spec, the Xids of a specific resource manager are those of the same XADataSource.
Params:
  • flag – int
Throws:
Returns:Xid[]
/** * Obtain a list of Xids of the current <i>resource manager</i> for * XAResources currently in the 'prepared' * state. According to the JDBC * 3.0 spec, the Xids of a specific resource manager are those of the same * XADataSource. * * @param flag int * @throws XAException on error * @return Xid[] */
public Xid[] recover(int flag) throws XAException { return xaDataSource.getPreparedXids(); }
Per the JDBC 3.0 spec, this rolls back the transaction for the specified Xid, not necessarily for the transaction associated with this XAResource object.
Params:
  • xid – Xid
Throws:
/** * Per the JDBC 3.0 spec, this rolls back the transaction for the specified * Xid, not necessarily for the transaction associated with this XAResource * object. * * @param xid Xid * @throws XAException on error */
public void rollback(Xid xid) throws XAException { JDBCXAResource resource = xaDataSource.getResource(xid); if (resource == null) { throw new XAException( "The XADataSource has no such Xid in prepared state: " + xid); } resource.rollbackThis(); }
This rolls back the connection associated with this XAResource.
Throws:
  • XAException – generically, since the more specific exceptions require a JTA API to compile.
/** * This rolls back the connection associated with <i>this</i> XAResource. * * @throws javax.transaction.xa.XAException generically, since the more * specific exceptions require a JTA API to compile. */
/* @throws javax.transaction.HeuristicCommitException * if work was committed. * @throws javax.transaction.HeuristicMixedException * if some work was committed and some work was rolled back */ public void rollbackThis() throws XAException { if (state != XA_STATE_PREPARED && state != XA_STATE_ENDED) { throw new XAException("Invalid XAResource state"); } try { /** * @todo: Determine if work was committed, rolled back, or both, * and return appropriate Heuristic Exception. */ connection.rollback(); // real/phys. } catch (SQLException se) { throw new XAException(se.toString()); } dispose(); }
Params:
  • seconds – int
Throws:
Returns:boolean
/** * * @param seconds int * @throws XAException on error * @return boolean */
public boolean setTransactionTimeout(int seconds) throws XAException { return false; } public void start(Xid xid, int flags) throws XAException { // Comment out following debug statement before public release: /* System.err.println("STARTING NEW Xid: " + xid); */ if (state != XA_STATE_INITIAL && state != XA_STATE_DISPOSED && state != XA_STATE_ENDED) { throw new XAException("Invalid XAResource state"); } if (xaDataSource == null) { throw new XAException( "JDBCXAResource has not been associated with a XADataSource"); } if (xid == null) { // This block asserts that all JDBCXAResources with state // >= XA_STATE_STARTED have a non-null xid. throw new XAException("Null Xid"); } try { if (connection.getAutoCommit()) { originalAutoCommitMode = true; // real/phys. connection.setAutoCommit(false); // real/phys. } } catch (SQLException se) { throw new XAException(se.toString()); } if (!xid.equals(this.xid)) { this.xid = xid; xaDataSource.addResource(this.xid, this); } state = XA_STATE_STARTED; // N.b. The DataSource does not have this XAResource in its list // until right here. We can't tell DataSource before our start() // method, because we don't know our Xid before now. } JDBCConnection getConnection() { return this.connection; } void setConnection(JDBCConnection userConnection) { connection = userConnection; } }