1
0
mirror of https://github.com/postgres/postgres.git synced 2025-06-17 17:02:08 +03:00

Another attempt at 7.0

This commit is contained in:
Peter Mount
2000-04-17 20:07:56 +00:00
parent aafff4af16
commit 25dadc8514
38 changed files with 17359 additions and 0 deletions

View File

@ -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;
}
}