mirror of
https://github.com/postgres/postgres.git
synced 2025-06-16 06:01:02 +03:00
957 lines
27 KiB
Java
957 lines
27 KiB
Java
/*
|
|
* 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.3 2001/11/19 22:33:39 momjian 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;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|