summaryrefslogtreecommitdiff
path: root/src/interfaces/jdbc/org/postgresql/xa/XAConnectionImpl.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/interfaces/jdbc/org/postgresql/xa/XAConnectionImpl.java')
-rw-r--r--src/interfaces/jdbc/org/postgresql/xa/XAConnectionImpl.java855
1 files changed, 855 insertions, 0 deletions
diff --git a/src/interfaces/jdbc/org/postgresql/xa/XAConnectionImpl.java b/src/interfaces/jdbc/org/postgresql/xa/XAConnectionImpl.java
new file mode 100644
index 0000000000..896472d696
--- /dev/null
+++ b/src/interfaces/jdbc/org/postgresql/xa/XAConnectionImpl.java
@@ -0,0 +1,855 @@
+/**
+ * Redistribution and use of this software and associated documentation
+ * ("Software"), with or without modification, are permitted provided
+ * that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain copyright
+ * statements and notices. Redistributions must also contain a
+ * copy of this document.
+ *
+ * 2. 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.
+ *
+ * 3. The name "Exolab" must not be used to endorse or promote
+ * products derived from this Software without prior written
+ * permission of Exoffice Technologies. For written permission,
+ * please contact info@exolab.org.
+ *
+ * 4. Products derived from this Software may not be called "Exolab"
+ * nor may "Exolab" appear in their names without prior written
+ * permission of Exoffice Technologies. Exolab is a registered
+ * trademark of Exoffice Technologies.
+ *
+ * 5. Due credit should be given to the Exolab Project
+ * (http://www.exolab.org/).
+ *
+ * THIS SOFTWARE IS PROVIDED BY EXOFFICE TECHNOLOGIES AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESSED 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
+ * EXOFFICE TECHNOLOGIES OR ITS 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.
+ *
+ * Copyright 1999 (C) Exoffice Technologies Inc. All Rights Reserved.
+ *
+ * $Id: XAConnectionImpl.java,v 1.1 2000/04/17 20:07:56 peter Exp $
+ */
+
+
+package org.postgresql.xa;
+
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.Vector;
+import javax.sql.XAConnection;
+import javax.sql.PooledConnection;
+import javax.sql.ConnectionEvent;
+import javax.sql.ConnectionEventListener;
+import javax.transaction.RollbackException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+import javax.transaction.xa.XAException;
+
+
+/**
+ * Implements an X/A connection that can be pooled and managed from
+ * inside a transaction monitor. This is the XA connection returned
+ * to the application server from the {@link XADataSourceImpl} and
+ * will be used to obtain {@link ClientConnection} for the
+ * application.
+ * <p>
+ * If the transaction is managed through the JDBC interface, this
+ * connection will reference the underlying JDBC connection directly.
+ * If this resource is enlisted with a global transaction through
+ * the {@link XAResource} interface, it will reference a transactional
+ * connection, or {@link TxConnection}. Such a connection may be
+ * shared by two or more XA connections enlisted with the same
+ * transaction.
+ *
+ *
+ * @author <a href="arkin@exoffice.com">Assaf Arkin</a>
+ * @version 1.0
+ * @see ClientConnection
+ * @see ConnectionEventListener
+ * @see TxConnection
+ */
+public final class XAConnectionImpl
+ implements XAConnection, XAResource
+{
+
+
+ /**
+ * This is the underlying JDBC connection represented
+ * by this pooled connection. This variable may initially be null,
+ * in which case {@link #getUnderlying} will return a new
+ * connection and set this variable. This variable is mutually
+ * exclusive with {@link #_txConn} and is always null for
+ * connections inside a transaction.
+ */
+ Connection _underlying;
+
+
+ /**
+ * If this connection is part of a global transaction, this
+ * object identifies the transaction. The transaction's
+ * underlying JDBC connection is exposed through this object and
+ * {@link #_underlying} is null. If this connection is closed,
+ * then the connection has been timedout. Commit/rollback will
+ * always set this variable to null.
+ */
+ private TxConnection _txConn;
+
+
+ /**
+ * The client connection last handed to the application. If the
+ * application calls {@link #getConnection} again, we should hand
+ * out a new client connection and render the previous one closed.
+ */
+ // No longer in use, see _clientId
+ //private ClientConnection _clientConn;
+
+
+ /**
+ * An event listener can be registered and notified when the
+ * client connection has been closed by the application or a
+ * fatal error rendered it unuseable.
+ */
+ private ConnectionEventListener _listener;
+
+
+ /**
+ * The resource manager is used to share connections within the
+ * same transaction.
+ */
+ private XADataSourceImpl _resManager;
+
+
+ /**
+ * This is an identifier we hand to the client connection when we
+ * create it. When the client connection asks for the underlying
+ * connection, we compare the identifiers. If since that point we
+ * created a new client connection, we regard an old client
+ * connection as discarded and do not hand it the underlying
+ * connection.
+ * <p>
+ * Previously, when a new client connection was created, we used
+ * a reference to the old one to terminate it. This proved to
+ * not work well, since the client connection could never be
+ * finalized.
+ */
+ private int _clientId = 1;
+
+
+ /**
+ * Construct a new XA/pooled connection with the underlying JDBC
+ * connection suitable for this driver only. This is a one to one
+ * mapping between this connection and the underlying connection.
+ * The underlying connection is only provided for pooled
+ * connections. XA connections are suspect of being enlisted with
+ * a global transaction which might already bear an underlying
+ * connection. If not, one will be created later on.
+ */
+ XAConnectionImpl( XADataSourceImpl resManager,
+ Connection underlying )
+ {
+ _underlying = underlying;
+ _resManager = resManager;
+ }
+
+
+ public synchronized void close()
+ throws SQLException
+ {
+ // This is our indication that this connection has been
+ // closed programmatically.
+ if ( _resManager == null )
+ throw new SQLException( "This connection has been closed" );
+
+ // The client connection is no longer useable.
+ /* Deprecated: see _clientId
+ if ( _clientConn != null )
+ _clientConn.terminate();
+ */
+ _clientId = -1;
+
+ // The underlying connection is closed and this connection
+ // is no longer useable. This method can be called any number
+ // of times (e.g. we use it in finalizer). We do not handle
+ // transactions, we just kill the connection.
+ try {
+ if ( _underlying != null ) {
+ _underlying.commit();
+ _underlying.close();
+ } else if ( _txConn != null ) {
+ try {
+ end( _txConn.xid, TMSUCCESS );
+ } catch ( XAException except ) { }
+ }
+ } finally {
+ _resManager = null;
+ _underlying = null;
+ _txConn = null;
+ _listener = null;
+ }
+ }
+
+
+ public XAResource getXAResource()
+ {
+ // The connection acts as it's own resource manager
+ return this;
+ }
+
+
+ public synchronized void addConnectionEventListener( ConnectionEventListener listener )
+ {
+ if ( listener == null )
+ throw new NullPointerException( "XAConnection: Argument 'listener' is null" );
+ if ( _listener != null )
+ throw new IllegalStateException( "XAConnection: Only one listener supported per connection" );
+ _listener = listener;
+ }
+
+
+ public synchronized void removeConnectionEventListener( ConnectionEventListener listener )
+ {
+ if ( listener == null )
+ throw new NullPointerException( "XAConnection: Argument 'listener' is null" );
+ if ( _listener == null || _listener != listener )
+ throw new IllegalStateException( "XAConnection: Listener never registered with this pooled connection" );
+ _listener = null;
+ }
+
+
+ public synchronized java.sql.Connection getConnection()
+ throws SQLException
+ {
+ // If this pooled connection has been closed, throw an exception.
+ if ( _resManager == null )
+ throw new SQLException( "This connection has been closed" );
+
+ // If getConnection() was called before and the underlying
+ // connection was not closed, we take it away from the previous
+ // recieved as per the PooledConnection design.
+ /* Deprecated: see _clientId
+ if ( _clientConn != null )
+ _clientConn.terminate();
+ */
+
+ // If we are handling an underlying connection, we commit the
+ // old transaction and are ready to work for a new one.
+ // If we are part of a global transaction we hope that end/
+ // start were called properly, but we're not longer in that
+ // transaction.
+ if ( _underlying != null ) {
+ try {
+ _underlying.commit();
+ } catch ( SQLException except ) {
+ ConnectionEvent event;
+
+ if ( _listener != null ) {
+ event = new ConnectionEvent( this, except );
+ _listener.connectionErrorOccurred( event );
+ }
+ }
+ }
+
+ // Create a new ClientConnection which will be returned to the
+ // application. The ClientConnection cannot be closed directly
+ // and cannot manage it's own transactions.
+ /* Deprecated: see _clientId
+ _clientConn = new ClientConnection( this );
+ return _clientConn;
+ */
+ return new ClientConnection( this, ++_clientId );
+ }
+
+
+ /**
+ * Called by {@link ClientConnection} to notify that the application
+ * has attempted to close the connection. After this call, the client
+ * connection is no longer useable and this pooled connection can be
+ * reused. The event listener is notified immediately.
+ *
+ * @param clientId The {@link ClientConnection} identifier
+ */
+ synchronized void notifyClose( int clientId )
+ {
+ ConnectionEvent event;
+
+ // ClientConnection has been closed, we dissociated it from
+ // the underlying connection and notify any listener that this
+ // pooled connection can be reused.
+ /* Deprecated: see clientId
+ _clientConn.terminate();
+ _clientConn = null;
+ */
+ // We have to expect being called by a ClientConnection that we
+ // no longer regard as valid. That's acceptable, we just ignore.
+ if ( clientId != _clientId )
+ return;
+
+ // If we are handling an underlying connection, we commit the
+ // old transaction and are ready to work for a new one.
+ // If we are part of a global transaction we hope that end/
+ // start were called properly.
+ if ( _underlying != null ) {
+ try {
+ _underlying.commit();
+ } catch ( SQLException except ) {
+ if ( _listener != null ) {
+ event = new ConnectionEvent( this, except );
+ _listener.connectionErrorOccurred( event );
+ }
+ return;
+ }
+ }
+ // Notify the listener.
+ if ( _listener != null ) {
+ event = new ConnectionEvent( this );
+ _listener.connectionClosed( event );
+ }
+ }
+
+
+ /**
+ * Called by {@link ClientConnection} to notify that an error
+ * occured with the underlying connection. If the error is
+ * critical, the underlying connection is closed and the listener
+ * is notified.
+ *
+ * @param clientId The {@link ClientConnection} identifier
+ * @param except The exception raised by the underlying connection
+ */
+ synchronized void notifyError( int clientId, SQLException except )
+ {
+ ConnectionEvent event;
+
+ if ( clientId != _clientId )
+ return;
+
+ // If the connection is not two-phase commit we cannot determine
+ // whether the error is critical, we just return. If the connection
+ // is two phase commit, but the error is not critical, we return.
+ if ( _underlying != null ) {
+ if ( ! ( _underlying instanceof TwoPhaseConnection ) ||
+ ! ( (TwoPhaseConnection) _underlying ).isCriticalError( except ) )
+ return;
+ if ( _txConn.conn == null ||
+ ! ( _txConn.conn instanceof TwoPhaseConnection ) ||
+ ! ( (TwoPhaseConnection) _txConn.conn ).isCriticalError( except ) )
+ return;
+ }
+
+ // The client connection is no longer useable, the underlying
+ // connection (if used) is closed, the TxConnection (if used)
+ // is rolledback and this connection dies (but close() may
+ // still be called).
+ ++_clientId;
+ if ( _underlying != null ) {
+ try {
+ _underlying.close();
+ } catch ( SQLException e2 ) {
+ // Ignore that, we know there's an error.
+ }
+ _underlying = null;
+ } else if ( _txConn != null ) {
+ try {
+ end( _txConn.xid, TMFAIL );
+ } catch ( XAException e2 ) {
+ // Ignore that, we know there's an error.
+ }
+ _txConn = null;
+ }
+
+ // Notify the listener.
+ if ( _listener != null ) {
+ event = new ConnectionEvent( this, except );
+ _listener.connectionErrorOccurred( event );
+ }
+ }
+
+
+ protected void finalize()
+ throws Throwable
+ {
+ // We are no longer referenced by anyone (including the
+ // connection pool). Time to close down.
+ close();
+ }
+
+
+ public String toString()
+ {
+ if ( _underlying != null )
+ return "XAConnection: " + _underlying;
+ else
+ return "XAConnection: unused";
+ }
+
+
+ public synchronized void start( Xid xid, int flags )
+ throws XAException
+ {
+ // General checks.
+ if ( xid == null )
+ throw new XAException( XAException.XAER_INVAL );
+ if ( _txConn != null )
+ throw new XAException( XAException.XAER_OUTSIDE );
+
+ synchronized ( _resManager ) {
+ if ( flags == TMNOFLAGS ) {
+ // Starting a new transaction. First, make sure it is
+ // not shared with any other connection (need to join
+ // for that).
+ if ( _resManager.getTxConnection( xid ) != null )
+ throw new XAException( XAException.XAER_DUPID );
+
+ // Create a new TxConnection to describe this
+ // connection in the context of a transaction and
+ // register it with the resource manager so it can
+ // be shared.
+ try {
+ _txConn = new TxConnection();
+ if ( _underlying != null ) {
+ _txConn.conn = _underlying;
+ _underlying = null;
+ } else
+ _txConn.conn = _resManager.newConnection();
+ _txConn.xid = xid;
+ _txConn.count = 1;
+ _txConn.started = System.currentTimeMillis();
+ _txConn.timeout = _txConn.started + ( _resManager.getTransactionTimeout() * 1000 );
+ _resManager.setTxConnection( xid, _txConn );
+ } catch ( SQLException except ) {
+ // If error occured at this point, we can only
+ // report it as resource manager error.
+ if ( _resManager.getLogWriter() != null )
+ _resManager.getLogWriter().println( "XAConnection: failed to begin a transaction: " + except );
+ throw new XAException( XAException.XAER_RMERR );
+ }
+
+ try {
+ _txConn.conn.setAutoCommit( false );
+ try {
+ if ( _resManager.isolationLevel() != Connection.TRANSACTION_NONE )
+ _txConn.conn.setTransactionIsolation( _resManager.isolationLevel() );
+ } catch ( SQLException e ) {
+ // The underlying driver might not support this
+ // isolation level that we use by default.
+ }
+ if ( _txConn.conn instanceof TwoPhaseConnection )
+ ( (TwoPhaseConnection) _txConn.conn ).enableSQLTransactions( false );
+ } catch ( SQLException except ) {
+ // If error occured at this point, we can only
+ // report it as resource manager error.
+ if ( _resManager.getLogWriter() != null )
+ _resManager.getLogWriter().println( "XAConnection: failed to begin a transaction: " + except );
+ throw new XAException( XAException.XAER_RMERR );
+ }
+ } else if ( flags == TMJOIN || flags == TMRESUME ) {
+ // We are joining another transaction with an
+ // existing TxConnection.
+ _txConn = _resManager.getTxConnection( xid );
+ if ( _txConn == null )
+ throw new XAException( XAException.XAER_INVAL );
+
+ // Update the number of XAConnections sharing this
+ // transaction connection.
+ if ( flags == TMJOIN && _txConn.count == 0 )
+ throw new XAException( XAException.XAER_PROTO );
+ ++_txConn.count;
+
+ // If we already have an underlying connection (as we can
+ // expect to), we should release that underlying connection
+ // and make it available to the resource manager.
+ if ( _underlying != null ) {
+ _resManager.releaseConnection( _underlying );
+ _underlying = null;
+ }
+ } else
+ // No other flags supported in start().
+ throw new XAException( XAException.XAER_INVAL );
+ }
+ }
+
+
+ public synchronized void end( Xid xid, int flags )
+ throws XAException
+ {
+ // General checks.
+ if ( xid == null )
+ throw new XAException( XAException.XAER_INVAL );
+ // Note: we could get end with success or failure even it
+ // we were previously excluded from the transaction.
+ if ( _txConn == null && flags == TMSUSPEND )
+ throw new XAException( XAException.XAER_NOTA );
+
+ synchronized ( _resManager ) {
+ if ( flags == TMSUCCESS || flags == TMFAIL) {
+ // We are now leaving a transaction we started or
+ // joined before. We can expect any of prepare/
+ // commit/rollback to be called next, so TxConnection
+ // is still valid.
+
+ // If we were suspended from the transaction, we'll
+ // join it for the duration of this operation.
+ // Make sure the reference count reaches zero by the
+ // time we get to prepare.
+ if ( _txConn == null ) {
+ _txConn = _resManager.getTxConnection( xid );
+ if ( _txConn == null )
+ throw new XAException( XAException.XAER_NOTA );
+ } else {
+ if ( _txConn.xid != null && ! _txConn.xid.equals( xid ) )
+ throw new XAException( XAException.XAER_NOTA );
+ --_txConn.count;
+ }
+
+ // If transaction failed, we can rollback the
+ // transaction and release the underlying connection.
+ // We can expect all other resources to recieved the
+ // same end notification. We don't expect forget to happen.
+ if ( flags == TMFAIL && _txConn.conn != null ) {
+ try {
+ if ( _txConn.conn instanceof TwoPhaseConnection )
+ ( (TwoPhaseConnection) _txConn.conn ).enableSQLTransactions( true );
+ _txConn.conn.rollback();
+ _resManager.releaseConnection( _txConn.conn );
+ } catch ( SQLException except ) {
+ // There is a problem with the underlying
+ // connection, but it was not added to the poll.
+ }
+ _resManager.setTxConnection( _txConn.xid, null );
+ _txConn.conn = null;
+ _txConn.xid = null;
+ }
+
+ if ( flags == TMSUCCESS) {
+ // We should be looking for a new transaction.
+ // Next thing we might be participating in a new
+ // transaction while the current one is being
+ // rolled back.
+ _txConn = null;
+ }
+ } else if ( flags == TMSUSPEND ) {
+ // We no longer take part in this transaction.
+ // Possibly we'll be asked to resume later on, but
+ // right now we have to forget about the transaction
+ // and the underlying connection.
+ --_txConn.count;
+ _txConn = null;
+ } else
+ // No other flags supported in end().
+ throw new XAException( XAException.XAER_INVAL );
+ }
+ }
+
+
+ public synchronized void forget( Xid xid )
+ throws XAException
+ {
+ TxConnection txConn;
+
+ // General checks.
+ if ( xid == null )
+ throw new XAException( XAException.XAER_INVAL );
+ synchronized ( _resManager ) {
+ // We have to forget about the transaction, meaning the
+ // transaction no longer exists for this or any other
+ // connection. We might be called multiple times.
+ txConn = _resManager.setTxConnection( xid, null );
+ if ( _txConn == txConn )
+ _txConn = null;
+ if ( txConn != null ) {
+ if ( txConn.conn != null ) {
+ _resManager.releaseConnection( txConn.conn );
+ txConn.conn = null;
+ }
+ txConn.xid = null;
+ }
+ }
+ }
+
+
+ public synchronized int prepare( Xid xid )
+ throws XAException
+ {
+ TxConnection txConn;
+
+ // General checks.
+ if ( xid == null )
+ throw new XAException( XAException.XAER_INVAL );
+
+ synchronized ( _resManager ) {
+ // Technically, prepare may be called for any connection,
+ // not just this one.
+ txConn = _resManager.getTxConnection( xid );
+ if ( txConn == null )
+ throw new XAException( XAException.XAER_NOTA );
+
+ // This is an error and should never happen. All other
+ // parties in the transaction should have left it before.
+ if ( txConn.count > 0 )
+ throw new XAException( XAException.XAER_PROTO );
+
+ // If the transaction failed, we have to force a rollback.
+ // We track the case of failure due to a timeout.
+ if ( txConn.timedOut )
+ throw new XAException( XAException.XA_RBTIMEOUT );
+ if ( txConn.conn == null )
+ throw new XAException( XAException.XA_RBROLLBACK );
+
+ // Since there is no preparation mechanism in a generic
+ // JDBC driver, we only test for read-only transaction
+ // but do not commit at this point.
+ try {
+ txConn.prepared = true;
+ if ( txConn.conn instanceof TwoPhaseConnection ) {
+ // For 2pc connection we ask it to prepare and determine
+ // whether it's commiting or read-only. If a rollback
+ // exception happens, we report it.
+ try {
+ if ( ( (TwoPhaseConnection) txConn.conn ).prepare() )
+ return XA_OK;
+ else {
+ txConn.readOnly = true;
+ return XA_RDONLY;
+ }
+ } catch ( SQLException except ) {
+ throw new XAException( XAException.XA_RBROLLBACK );
+ }
+ } else {
+ // For standard connection we cannot prepare, we can
+ // only guess if it's read only.
+ if ( txConn.conn.isReadOnly() ) {
+ txConn.readOnly = true;
+ return XA_RDONLY;
+ }
+ return XA_OK;
+ }
+ } catch ( SQLException except ) {
+ try {
+ // Fatal error in the connection, kill it.
+ txConn.conn.close();
+ } catch ( SQLException e ) { }
+ txConn.conn = null;
+ if ( _resManager.getLogWriter() != null )
+ _resManager.getLogWriter().println( "XAConnection: failed to commit a transaction: " + except );
+ // If we cannot commit the transaction, force a rollback.
+ throw new XAException( XAException.XA_RBROLLBACK );
+ }
+ }
+ }
+
+
+ public Xid[] recover( int flags )
+ throws XAException
+ {
+ synchronized ( _resManager ) {
+ return _resManager.getTxRecover();
+ }
+ }
+
+
+ public synchronized void commit( Xid xid, boolean onePhase )
+ throws XAException
+ {
+ TxConnection txConn;
+
+ // General checks.
+ if ( xid == null )
+ throw new XAException( XAException.XAER_INVAL );
+
+ synchronized ( _resManager ) {
+ // Technically, commit may be called for any connection,
+ // not just this one.
+ txConn = _resManager.getTxConnection( xid );
+ if ( txConn == null )
+ throw new XAException( XAException.XAER_NOTA );
+
+ // If the transaction failed, we have to force
+ // a rollback.
+ if ( txConn.conn == null )
+ throw new XAException( XAException.XA_RBROLLBACK );
+
+ // If connection has been prepared and is read-only,
+ // nothing to do at this stage.
+ if ( txConn.readOnly )
+ return;
+
+ // This must be a one-phase commite, or the connection
+ // should have been prepared before.
+ if ( onePhase || txConn.prepared ) {
+ try {
+ // Prevent multiple commit attempts.
+ txConn.readOnly = true;
+ if ( txConn.conn instanceof TwoPhaseConnection )
+ ( (TwoPhaseConnection) txConn.conn ).enableSQLTransactions( true );
+ txConn.conn.commit();
+ } catch ( SQLException except ) {
+ try {
+ // Unknown error in the connection, better kill it.
+ txConn.conn.close();
+ } catch ( SQLException e ) { }
+ txConn.conn = null;
+ if ( _resManager.getLogWriter() != null )
+ _resManager.getLogWriter().println( "XAConnection: failed to commit a transaction: " + except );
+ // If we cannot commit the transaction, a heuristic tollback.
+ throw new XAException( XAException.XA_HEURRB );
+ }
+ } else {
+ // 2pc we should have prepared before.
+ if ( ! txConn.prepared )
+ throw new XAException( XAException.XAER_PROTO );
+ }
+ }
+ }
+
+
+ public synchronized void rollback( Xid xid )
+ throws XAException
+ {
+ TxConnection txConn;
+
+
+ // General checks.
+ if ( xid == null )
+ throw new XAException( XAException.XAER_INVAL );
+
+ synchronized ( _resManager ) {
+ // Technically, rollback may be called for any connection,
+ // not just this one.
+ txConn = _resManager.getTxConnection( xid );
+ if ( txConn == null )
+ throw new XAException( XAException.XAER_NOTA );
+
+ // If connection has been prepared and is read-only,
+ // nothing to do at this stage. If connection has
+ // been terminated any other way, nothing to do
+ // either.
+ if ( txConn.readOnly || txConn.conn == null )
+ return;
+
+ try {
+ txConn.prepared = false;
+ if ( txConn.conn instanceof TwoPhaseConnection )
+ ( (TwoPhaseConnection) txConn.conn ).enableSQLTransactions( true );
+ txConn.conn.rollback();
+ } catch ( SQLException except ) {
+ try {
+ // Unknown error in the connection, better kill it.
+ txConn.conn.close();
+ } catch ( SQLException e ) { }
+ txConn.conn = null;
+ if ( _resManager.getLogWriter() != null )
+ _resManager.getLogWriter().println( "XAConnection: failed to rollback a transaction: " + except );
+ // If we cannot commit the transaction, a heuristic tollback.
+ throw new XAException( XAException.XA_RBROLLBACK );
+ } finally {
+ forget( xid );
+ }
+ }
+ }
+
+
+ public synchronized boolean isSameRM( XAResource xaRes )
+ throws XAException
+ {
+ // Two resource managers are equal if they produce equivalent
+ // connection (i.e. same database, same user). If the two are
+ // equivalent they would share a transaction by joining.
+ if ( xaRes == null || ! ( xaRes instanceof XAConnectionImpl ) )
+ return false;
+ if ( _resManager.equals( ( (XAConnectionImpl) xaRes )._resManager ) )
+ return true;
+ return false;
+ }
+
+
+ public synchronized boolean setTransactionTimeout( int seconds )
+ throws XAException
+ {
+ if ( seconds < 0 )
+ throw new XAException( XAException.XAER_INVAL );
+ // Zero resets to the default for all transactions.
+ if ( seconds == 0 )
+ seconds = _resManager.getTransactionTimeout();
+ // If a transaction has started, change it's timeout to the new value.
+ if ( _txConn != null ) {
+ _txConn.timeout = _txConn.started + ( seconds * 1000 );
+ return true;
+ }
+ return false;
+ }
+
+
+ public int getTransactionTimeout()
+ {
+ long timeout;
+
+ if ( _txConn == null )
+ return 0;
+ return (int) ( _txConn.timeout - _txConn.started ) / 1000;
+ }
+
+
+ /**
+ * Returns true if this connection is inside a global transaction.
+ * If the connection is inside a global transaction it will not
+ * allow commit/rollback directly from the {@link
+ * java.sql.Connection} interface.
+ */
+ boolean insideGlobalTx()
+ {
+ return ( _txConn != null );
+ }
+
+
+ /**
+ * Called to obtain the underlying connections. If this connection
+ * is part of a transaction, the transction's underlying connection
+ * is returned, or an exception is thrown if the connection was
+ * terminated due to timeout. If this connection is not part of a
+ * transaction, a non-transactional connection is returned.
+ *
+ * @param clientId The {@link ClientConnection} identifier
+ */
+ Connection getUnderlying( int clientId )
+ throws SQLException
+ {
+ // If we were notified of the client closing, or have been
+ // requested to have a new client connection since then,
+ // the client id will not match to that of the caller.
+ // We use that to decide that the caller has been closed.
+ if ( clientId != _clientId )
+ throw new SQLException( "This application connection has been closed" );
+
+ if ( _txConn != null ) {
+ if ( _txConn.timedOut )
+ throw new SQLException( "The transaction has timed out and has been rolledback and closed" );
+ if ( _txConn.conn == null )
+ throw new SQLException( "The transaction has been terminated and this connection has been closed" );
+ return _txConn.conn;
+ }
+ if ( _underlying == null ) {
+ _underlying = _resManager.newConnection();
+ _underlying.setAutoCommit( true );
+ }
+ return _underlying;
+ }
+
+
+}
+
+
+