1
0
mirror of https://github.com/postgres/postgres.git synced 2025-09-03 15:22:11 +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,495 @@
/**
* 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: ClientConnection.java,v 1.1 2000/04/17 20:07:55 peter Exp $
*/
package org.postgresql.xa;
import java.util.*;
import java.sql.*;
/**
* Encapsulates an application's view of an XA/pooled connection.
* The XA connection is managed by the application server through it's
* {@link javax.sql.XAConnection} interface. The underlying JDBC
* connection is a standard JDBC connection. The application's
* JDBC connection gives access to the underlying JDBC connection but
* is managed by the application server. The application is given an
* instance of this class and not the underlying connection directly.
*
*
* @author <a href="arkin@exoffice.com">Assaf Arkin</a>
* @version 1.0
* @see XAConnectionImpl
* @see XADataSourceImpl
* @see Connection
*/
final class ClientConnection
implements Connection
{
/**
* The pooled XA connection that created this client connection
* and should be used to report closure and fatal errors.
*/
private XAConnectionImpl _xaConn;
/**
* This identifier was handed on to use when we were created by
* {@link XAConnection}. If since then the XA connection was asked
* to create another connection or was closed, our identifier will
* no longer be valid and any call to {@link
* XAConnection#getUnderlying} will throw an exception. Previously,
* the XA connection would hold a reference to use and tell us to
* terminate, but that prevented ClientConnection from being
* finalized.
*/
private int _clientId;
/**
* Construct a new client connection to provide access to the
* underlying JDBC connection (<tt>underlying</tt>) on behalf of
* an XA/pooled connection (<tt>xaConn<tt/>). The pooled connection
* is required to notify of connection closure and fatal errors.
*
* @param xaConn The XA/pooled connection that created this
* client connection
* @param clientId A unique identifier handed to us by
* {@link XAConnection}
* @param underlying The underlying JDBC connection
*/
ClientConnection( XAConnectionImpl xaConn, int clientId )
{
_xaConn = xaConn;
_clientId = clientId;
}
public Statement createStatement()
throws SQLException
{
try {
return getUnderlying().createStatement();
} catch ( SQLException except ) {
notifyError( except );
throw except;
}
}
public Statement createStatement( int resultSetType, int resultSetConcurrency )
throws SQLException
{
try {
return getUnderlying().createStatement( resultSetType, resultSetConcurrency );
} catch ( SQLException except ) {
notifyError( except );
throw except;
}
}
public PreparedStatement prepareStatement( String sql )
throws SQLException
{
try {
return getUnderlying().prepareStatement( sql );
} catch ( SQLException except ) {
notifyError( except );
throw except;
}
}
public PreparedStatement prepareStatement( String sql, int resultSetType, int resultSetConcurrency )
throws SQLException
{
try {
return getUnderlying().prepareStatement( sql, resultSetType, resultSetConcurrency );
} catch ( SQLException except ) {
notifyError( except );
throw except;
}
}
public CallableStatement prepareCall( String sql )
throws SQLException
{
try {
return getUnderlying().prepareCall( sql );
} catch ( SQLException except ) {
notifyError( except );
throw except;
}
}
public CallableStatement prepareCall( String sql, int resultSetType, int resultSetConcurrency )
throws SQLException
{
try {
return getUnderlying().prepareCall( sql, resultSetType, resultSetConcurrency );
} catch ( SQLException except ) {
notifyError( except );
throw except;
}
}
public String nativeSQL( String sql )
throws SQLException
{
try {
return getUnderlying().nativeSQL( sql );
} catch ( SQLException except ) {
notifyError( except );
throw except;
}
}
public DatabaseMetaData getMetaData()
throws SQLException
{
try {
return getUnderlying().getMetaData();
} catch ( SQLException except ) {
notifyError( except );
throw except;
}
}
public void setCatalog( String catalog )
throws SQLException
{
try {
getUnderlying().setCatalog( catalog );
} catch ( SQLException except ) {
notifyError( except );
throw except;
}
}
public String getCatalog()
throws SQLException
{
try {
return getUnderlying().getCatalog();
} catch ( SQLException except ) {
notifyError( except );
throw except;
}
}
public SQLWarning getWarnings()
throws SQLException
{
try {
return getUnderlying().getWarnings();
} catch ( SQLException except ) {
notifyError( except );
throw except;
}
}
public void clearWarnings()
throws SQLException
{
try {
getUnderlying().clearWarnings();
} catch ( SQLException except ) {
notifyError( except );
throw except;
}
}
public Map getTypeMap()
throws SQLException
{
try {
return getUnderlying().getTypeMap();
} catch ( SQLException except ) {
notifyError( except );
throw except;
}
}
public void setTypeMap( Map map )
throws SQLException
{
try {
getUnderlying().setTypeMap( map );
} catch ( SQLException except ) {
notifyError( except );
throw except;
}
}
public void setAutoCommit( boolean autoCommit )
throws SQLException
{
// Cannot set auto-commit inside a transaction.
if ( _xaConn.insideGlobalTx() )
throw new SQLException( "Cannot commit/rollback a connection managed by the transaction manager" );
try {
getUnderlying().setAutoCommit( autoCommit );
} catch ( SQLException except ) {
notifyError( except );
throw except;
}
}
public boolean getAutoCommit()
throws SQLException
{
try {
return getUnderlying().getAutoCommit();
} catch ( SQLException except ) {
notifyError( except );
throw except;
}
}
public void commit()
throws SQLException
{
// Cannot commit directly if we're inside a global transaction.
if ( _xaConn.insideGlobalTx() )
throw new SQLException( "Cannot commit/rollback a connection managed by the transaction manager" );
// Cannot commit a read-only transaction.
if ( isReadOnly() )
throw new SQLException( "Cannot commit/rollback a read-only transaction" );
// This only occurs if not inside a local transaction.
try {
getUnderlying().commit();
} catch ( SQLException except ) {
notifyError( except );
throw except;
}
}
public void rollback()
throws SQLException
{
// Cannot commit directly if we're inside a global transaction.
if ( _xaConn.insideGlobalTx() )
throw new SQLException( "Cannot commit/rollback a connection managed by the transaction manager" );
// This only occurs if not inside a local transaction.
try {
getUnderlying().rollback();
} catch ( SQLException except ) {
notifyError( except );
throw except;
}
}
public void setReadOnly( boolean readOnly )
throws SQLException
{
try {
getUnderlying().setReadOnly( readOnly );
} catch ( SQLException except ) {
notifyError( except );
throw except;
}
}
public boolean isReadOnly()
throws SQLException
{
try {
return getUnderlying().isReadOnly();
} catch ( SQLException except ) {
notifyError( except );
throw except;
}
}
public void setTransactionIsolation( int level )
throws SQLException
{
try {
getUnderlying().setTransactionIsolation( level );
} catch ( SQLException except ) {
notifyError( except );
throw except;
}
}
public int getTransactionIsolation()
throws SQLException
{
try {
return getUnderlying().getTransactionIsolation();
} catch ( SQLException except ) {
notifyError( except );
throw except;
}
}
public synchronized void close()
throws SQLException
{
if ( _xaConn == null )
return;
// Notify the XA connection that we are no longer going
// to be used. Whether the underlying connection is released,
// held until the transaction terminates, etc is not
// a concern of us.
_xaConn.notifyClose( _clientId );
_xaConn = null;
}
public synchronized boolean isClosed()
{
// Simple way of determining if this connection is closed.
// The actual connection is never closed, it is pooled.
return ( _xaConn == null );
}
/**
* Called by {@link XAConnectionImpl} to terminate this connection
* by dissociating it from the underlying JDBC connection.
* The application would call {@link #close} but {@link
* XAConnectionImpl} cannot, since pooled connection requirements
* will cause an inifinite loop. This method should not attempt
* to notify either a closure or fatal error, but rather throw an
* exception if it fails.
*/
/* Deprecated: see XAConnection._clientId
void terminate()
{
_xaConn = null;
}
*/
protected void finalize()
throws Throwable
{
close();
}
public String toString()
{
try {
return getUnderlying().toString();
} catch ( SQLException except ) {
return "XAConnection: Connection closed";
}
}
/**
* Called when an exception is thrown by the underlying connection
* to determine whether the exception is critical or not. If the
* exception is critical, notifies the XA connection to forget
* about this connection.
*
* @param except The exception thrown by the underlying
* connection
*/
void notifyError( SQLException except )
{
if ( _xaConn != null )
_xaConn.notifyError( _clientId, except );
}
/**
* Called to retrieve the underlying JDBC connection. Actual JDBC
* operations are performed against it. Throws an SQLException if
* this connection has been closed.
*/
Connection getUnderlying()
throws SQLException
{
if ( _xaConn == null )
throw new SQLException( "This connection has been closed" );
// Must pass the client identifier so XAConnection can determine
// whether we are still valid. If it tells us we're no longer
// valid, we have little to do.
try {
return _xaConn.getUnderlying( _clientId );
} catch ( SQLException except ) {
_xaConn = null;
throw except;
}
}
}

View File

@@ -0,0 +1,377 @@
package org.postgresql.xa;
import java.sql.*;
import javax.sql.*;
import javax.transaction.xa.*;
public class Test
{
public static void main( String args[] )
{
XADataSource xaDS;
java.io.PrintWriter log;
log = new java.io.PrintWriter( System.out );
try {
xaDS = new XADataSource();
xaDS.setDatabaseName( "test" );
xaDS.setUser( "arkin" );
xaDS.setPassword( "natasha" );
xaDS.setLogWriter( log );
Thread1 thread1;
thread1 = new Thread1();
thread1.xaConn = xaDS.getXAConnection();
thread1.xid1 = new XidImpl();
Thread2 thread2;
thread2 = new Thread2();
thread1.thread2 = thread2;
thread2.thread1 = thread1;
thread2.xaConn = xaDS.getXAConnection();
thread2.xid1 = thread1.xid1;
thread2.xid2 = new XidImpl();
thread1.start();
thread2.start();
} catch ( Exception except ) {
System.out.println( except );
except.printStackTrace();
}
log.flush();
}
}
class Thread1
extends Thread
{
public void run()
{
Connection conn;
XAResource xaRes;
Statement stmt;
ResultSet rs;
try {
conn = xaConn.getConnection();
xaRes = xaConn.getXAResource();
} catch ( Exception except ) {
System.out.println( except );
return;
}
// Initially the table should have no value.
try {
stmt = conn.createStatement();
stmt.executeUpdate( "update test set text='nothing' where id=1" );
stmt.close();
} catch ( SQLException except ) {
System.out.println( except );
}
// Begin a transaction on this connection.
// Perform an update on the table.
System.out.println( "[Thread1] Starting transaction" );
try {
xaRes.start( xid1, XAResource.TMNOFLAGS );
} catch ( XAException except ) {
System.out.println( except );
return;
}
System.out.println( "[Thread1] Updating table" );
try {
stmt = conn.createStatement();
stmt.executeUpdate( "update test set text='first' where id=1" );
stmt.close();
} catch ( SQLException except ) {
System.out.println( except );
}
// Thread2 will start a new transction and attempt
// to perform an update on the table and will lock.
System.out.println( "[Thread1] Waking up Thread2" );
thread2.interrupt();
try {
sleep( Integer.MAX_VALUE );
} catch ( InterruptedException except ) { }
// Perform a select from the table just to prove
// that Thread2 failed in its update.
System.out.println( "[Thread1] Selecting from table" );
try {
stmt = conn.createStatement();
rs = stmt.executeQuery( "select text from test where id=1" );
rs.next();
System.out.println( "First = " + rs.getString( 1 ) );
rs.close();
stmt.close();
} catch ( SQLException except ) {
System.out.println( except );
}
// Thread2 will now attempt to join our transaction
// and perform an update on the table.
System.out.println( "[Thread1] Waking up Thread2" );
thread2.interrupt();
try {
sleep( Integer.MAX_VALUE );
} catch ( InterruptedException except ) { }
// Perform a select from the table to prove that
// Thread2 managed to update it.
System.out.println( "[Thread1] Selecting from table" );
try {
stmt = conn.createStatement();
rs = stmt.executeQuery( "select text from test where id=1" );
rs.next();
System.out.println( "First = " + rs.getString( 1 ) );
rs.close();
stmt.close();
} catch ( SQLException except ) {
System.out.println( except );
}
// We now end the transaction for this thread.
// We are no longer in the shared transaction.
// Perform an update on the table and the update
// will lock.
System.out.println( "[Thread1] Ending transaction" );
try {
xaRes.end( xid1, XAResource.TMSUCCESS );
} catch ( XAException except ) {
System.out.println( except );
return;
}
System.out.println( "[Thread1] Selecting from table" );
try {
stmt = conn.createStatement();
stmt.executeUpdate( "update test set text='first' where id=1" );
stmt.close();
} catch ( SQLException except ) {
System.out.println( except );
}
// Thread 2 will now end the transcation and commit it.
System.out.println( "[Thread1] Waking up Thread2" );
thread2.interrupt();
try {
sleep( Integer.MAX_VALUE );
} catch ( InterruptedException except ) { }
// Perform a select on the table to prove that it
// was only updated inside the transaction.
System.out.println( "[Thread1] Selecting from table" );
try {
stmt = conn.createStatement();
rs = stmt.executeQuery( "select text from test where id=1" );
rs.next();
System.out.println( "First = " + rs.getString( 1 ) );
rs.close();
stmt.close();
} catch ( SQLException except ) {
System.out.println( except );
}
}
javax.sql.XAConnection xaConn;
Xid xid1;
Thread thread2;
}
class Thread2
extends Thread
{
public void run()
{
Connection conn;
XAResource xaRes;
Statement stmt;
ResultSet rs;
try {
conn = xaConn.getConnection();
xaRes = xaConn.getXAResource();
} catch ( Exception except ) {
System.out.println( except );
return;
}
// Thread2 immediately goes to sleep, waits
// for Thread1 to wake it up.
try {
sleep( Integer.MAX_VALUE );
} catch ( InterruptedException except ) { }
// Begin a transaction on this connection.
// Perform an update on the table. This will
// lock since Thread1 is in a different transaction
// updating the same table.
System.out.println( "[Thread2] Starting transaction" );
try {
xaRes.start( xid2, XAResource.TMNOFLAGS );
} catch ( XAException except ) {
System.out.println( except );
return;
}
System.out.println( "[Thread2] Updating table" );
try {
stmt = conn.createStatement();
stmt.executeUpdate( "update test set text='second' where id=1" );
stmt.close();
} catch ( SQLException except ) {
System.out.println( except );
}
// Thread1 will now proof that it owns the
// transaction.
System.out.println( "[Thread2] Waking up Thread1" );
thread1.interrupt();
try {
sleep( Integer.MAX_VALUE );
} catch ( InterruptedException except ) { }
// We will now join the transaction shared with
// Thread1 and try to update the table again.
System.out.println( "[Thread2] Dumping transaction" );
try {
xaRes.end( xid2, XAResource.TMFAIL );
// xaRes.rollback( xid2 );
xaRes.forget( xid2 );
} catch ( XAException except ) {
System.out.println( except );
return;
}
System.out.println( "[Thread2] Joining transaction of Thread1" );
try {
xaRes.start( xid1, XAResource.TMJOIN );
} catch ( XAException except ) {
System.out.println( except );
return;
}
System.out.println( "[Thread2] Updating table" );
try {
stmt = conn.createStatement();
stmt.executeUpdate( "update test set text='second' where id=1" );
stmt.close();
} catch ( SQLException except ) {
System.out.println( except );
}
// Thread1 will now proof that it could update
// the table.
System.out.println( "[Thread2] Waking up Thread1" );
thread1.interrupt();
try {
sleep( Integer.MAX_VALUE );
} catch ( InterruptedException except ) { }
// We will now end the transaction and commit it.
System.out.println( "[Thread2] Commiting transaction" );
try {
xaRes.end( xid1, XAResource.TMSUCCESS );
xaRes.prepare( xid1 );
xaRes.commit( xid1, false );
xaRes.forget( xid1 );
} catch ( XAException except ) {
System.out.println( except );
return;
}
// Perform a select on the table to prove that it
// was only updated inside the transaction.
System.out.println( "[Thread2] Selecting from table" );
try {
stmt = conn.createStatement();
rs = stmt.executeQuery( "select text from test where id=1" );
rs.next();
System.out.println( "First = " + rs.getString( 1 ) );
rs.close();
stmt.close();
} catch ( SQLException except ) {
System.out.println( except );
}
// Thread1 will now proof that the table was only
// updated inside the transaction. Thread 2 will die.
System.out.println( "[Thread2] Waking up Thread1" );
thread1.interrupt();
}
javax.sql.XAConnection xaConn;
Xid xid1;
Xid xid2;
Thread thread1;
}
class XidImpl
implements Xid
{
public byte[] getBranchQualifier()
{
return null;
}
public byte[] getGlobalTransactionId()
{
return null;
}
public int getFormatId()
{
return 0;
}
}

View File

@@ -0,0 +1,116 @@
/**
* 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: TwoPhaseConnection.java,v 1.1 2000/04/17 20:07:55 peter Exp $
*/
package org.postgresql.xa;
import java.sql.SQLException;
/**
* Defines two-phase commit support for a JDBC connection used by
* {@link XAConnection}. A JDBC connection that can implement any of
* these features should extend this interface and attempt to
* implement as much as it can.
* <p>
* {@link #prepare} is used as part of the two phase commit protocol
* to determine whether the transaction can commit or must rollback.
* Failure to implement this method will cause all connections to vote
* for commit, whether or not they can actually commit, leading to
* mixed heuristics.
* <p>
* {@link #enableSQLTransactions} allows the SQL begin/commit/rollback
* commands to be disabled for the duration of a transaction managed
* through an {@link javax.transaction.xaXAResource}, preventing the
* application from demarcating transactions directly.
* <p>
* {@link #isCriticalError} is used to tell if an exception thrown by
* the connection is fatal and the connection should not be returned
* to the pool.
*
*
* @author <a href="arkin@exoffice.com">Assaf Arkin</a>
* @version 1.0
*/
public interface TwoPhaseConnection
{
/**
* Enables or disables transaction demarcation through SQL commit
* and rollback. When the connection falls under control of
* {@link XAConnection}, SQL commit/rollback commands will be
* disabled to prevent direct transaction demarcation.
*
* @param flag True to enable SQL transactions (the default)
*/
public void enableSQLTransactions( boolean flag );
/**
* Called to prepare the transaction for commit. Returns true if
* the transaction is prepared, false if the transaction is
* read-only. If the transaction has been marked for rollback,
* throws a {@link RollbackException}.
*
* @return True if can commit, false if read-only
* @throws SQLException If transaction has been marked for
* rollback or cannot commit for any other reason
*/
public boolean prepare()
throws SQLException;
/**
* Returns true if the error issued by this connection is a
* critical error and the connection should be terminated.
*
* @param except The exception thrown by this connection
*/
public boolean isCriticalError( SQLException except );
}

View File

@@ -0,0 +1,129 @@
/**
* 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: TxConnection.java,v 1.1 2000/04/17 20:07:56 peter Exp $
*/
package org.postgresql.xa;
import java.sql.Connection;
import javax.transaction.xa.Xid;
/**
* Describes an open connection associated with a transaction. When a
* transaction is opened for a connection, this record is created for
* the connection. It indicates the underlying JDBC connection and
* transaction Xid. Multiple XA connection that fall under the same
* transaction Xid will share the same TxConnection object.
*
*
* @author <a href="arkin@exoffice.com">Assaf Arkin</a>
* @version 1.0
* @see Xid
* @see XAConnectionImpl
*/
final class TxConnection
{
/**
* The Xid of the transactions. Connections that are not
* associated with a transaction are not represented here.
*/
Xid xid;
/**
* Holds the underlying JDBC connection for as long as this
* connection is useable. If the connection has been rolled back,
* timed out or had any other error, this variable will null
* and the connection is considered failed.
*/
Connection conn;
/**
* Indicates the clock time (in ms) when the transaction should
* time out. The transaction times out when
* <tt>System.currentTimeMillis() > timeout</tt>.
*/
long timeout;
/**
* Indicates the clock time (in ms) when the transaction started.
*/
long started;
/**
* Reference counter indicates how many XA connections share this
* underlying connection and transaction. Always one or more.
*/
int count;
/**
* True if the transaction has failed due to time out.
*/
boolean timedOut;
/**
* True if the transaction has already been prepared.
*/
boolean prepared;
/**
* True if the transaction has been prepared and found out to be
* read-only. Read-only transactions do not require commit/rollback.
*/
boolean readOnly;
}

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

View File

@@ -0,0 +1,459 @@
/**
* 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: XADataSourceImpl.java,v 1.1 2000/04/17 20:07:56 peter Exp $
*/
package org.postgresql.xa;
import java.io.Serializable;
import java.io.PrintWriter;
import java.util.Hashtable;
import java.util.Vector;
import java.util.Stack;
import java.util.Enumeration;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import javax.sql.PooledConnection;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.XAConnection;
import javax.sql.XADataSource;
import javax.transaction.xa.Xid;
/**
* Implements a JDBC 2.0 {@link XADataSource} for any JDBC driver
* with JNDI persistance support. The base implementation is actually
* provided by a different {@link DataSource} class; although this is
* the super class, it only provides the pooling and XA specific
* implementation.
*
*
* @author <a href="arkin@exoffice.com">Assaf Arkin</a>
* @version 1.0
*/
public abstract class XADataSourceImpl
implements DataSource, ConnectionPoolDataSource,
XADataSource, Serializable, Runnable
{
/**
* Maps underlying JDBC connections into global transaction Xids.
*/
private transient Hashtable _txConnections = new Hashtable();
/**
* This is a pool of free underlying JDBC connections. If two
* XA connections are used in the same transaction, the second
* one will make its underlying JDBC connection available to
* the pool. This is not a real connection pool, only a marginal
* efficiency solution for dealing with shared transactions.
*/
private transient Stack _pool = new Stack();
/**
* A background deamon thread terminating connections that have
* timed out.
*/
private transient Thread _background;
/**
* The default timeout for all new transactions.
*/
private int _txTimeout = DEFAULT_TX_TIMEOUT;
/**
* The default timeout for all new transactions is 10 seconds.
*/
public final static int DEFAULT_TX_TIMEOUT = 10;
/**
* Implementation details:
* If two XAConnections are associated with the same transaction
* (one with a start the other with a join) they must use the
* same underlying JDBC connection. They lookup the underlying
* JDBC connection based on the transaction's Xid in the
* originating XADataSource.
*
* Currently the XADataSource must be the exact same object,
* this should be changed so all XADataSources that are equal
* share a table of all enlisted connections
*
* To test is two connections should fall under the same
* transaction we match the resource managers by comparing the
* database/user they fall under using a comparison of the
* XADataSource properties.
*/
public XADataSourceImpl()
{
super();
// Create a background thread that will track transactions
// that timeout, abort them and release the underlying
// connections to the pool.
_background = new Thread( this, "XADataSource Timeout Daemon" );
_background.setPriority( Thread.MIN_PRIORITY );
_background.setDaemon( true );
_background.start();
}
public XAConnection getXAConnection()
throws SQLException
{
// Construct a new XAConnection with no underlying connection.
// When a JDBC method requires an underlying connection, one
// will be created. We don't create the underlying connection
// beforehand, as it might be coming from an existing
// transaction.
return new XAConnectionImpl( this, null );
}
public XAConnection getXAConnection( String user, String password )
throws SQLException
{
// Since we create the connection on-demand with newConnection
// or obtain it from a transaction, we cannot support XA
// connections with a caller specified user name.
throw new SQLException( "XAConnection does not support connections with caller specified user name" );
}
public PooledConnection getPooledConnection()
throws SQLException
{
// Construct a new pooled connection and an underlying JDBC
// connection to go along with it.
return new XAConnectionImpl( this, getConnection() );
}
public PooledConnection getPooledConnection( String user, String password )
throws SQLException
{
// Construct a new pooled connection and an underlying JDBC
// connection to go along with it.
return new XAConnectionImpl( this, getConnection( user, password ) );
}
/**
* Returns the default timeout for all transactions.
*/
public int getTransactionTimeout()
{
return _txTimeout;
}
/**
* This method is defined in the interface and implemented in the
* derived class, we re-define it just to make sure it does not
* throw an {@link SQLException} and that we do not need to
* catch one.
*/
public abstract java.io.PrintWriter getLogWriter();
/**
* Sets the default timeout for all transactions. The timeout is
* specified in seconds. Use zero for the default timeout. Calling
* this method does not affect transactions in progress.
*
* @param seconds The timeout in seconds
*/
public void setTransactionTimeout( int seconds )
{
if ( seconds <= 0 )
_txTimeout = DEFAULT_TX_TIMEOUT;
else
_txTimeout = seconds;
_background.interrupt();
}
/**
* Returns an underlying connection for the global transaction,
* if one has been associated before.
*
* @param xid The transaction Xid
* @return A connection associated with that transaction, or null
*/
TxConnection getTxConnection( Xid xid )
{
return (TxConnection) _txConnections.get( xid );
}
/**
* Associates the global transaction with an underlying connection,
* or dissociate it when null is passed.
*
* @param xid The transaction Xid
* @param conn The connection to associate, null to dissociate
*/
TxConnection setTxConnection( Xid xid, TxConnection txConn )
{
if ( txConn == null )
return (TxConnection) _txConnections.remove( xid );
else
return (TxConnection) _txConnections.put( xid, txConn );
}
/**
* Release an unused connection back to the pool. If an XA
* connection has been asked to join an existing transaction,
* it will no longer use it's own connection and make it available
* to newly created connections.
*
* @param conn An open connection that is no longer in use
*/
void releaseConnection( Connection conn )
{
_pool.push( conn );
}
/**
* Creates a new underlying connection. Used by XA connection
* that lost it's underlying connection when joining a
* transaction and is now asked to produce a new connection.
*
* @return An open connection ready for use
* @throws SQLException An error occured trying to open
* a connection
*/
Connection newConnection()
throws SQLException
{
Connection conn;
// Check in the pool first.
if ( ! _pool.empty() ) {
conn = (Connection) _pool.pop();
return conn;
}
return getConnection();
}
/**
* XXX Not fully implemented yet and no code to really
* test it.
*/
Xid[] getTxRecover()
{
Vector list;
Enumeration enum;
TxConnection txConn;
list = new Vector();
enum = _txConnections.elements();
while ( enum.hasMoreElements() ) {
txConn = (TxConnection) enum.nextElement();
if ( txConn.conn != null && txConn.prepared )
list.add( txConn.xid );
}
return (Xid[]) list.toArray();
}
/**
* Returns the transaction isolation level to use with all newly
* created transactions, or {@link Connection#TRANSACTION_NONE}
* if using the driver's default isolation level.
*/
public int isolationLevel()
{
return Connection.TRANSACTION_NONE;
}
public void run()
{
Enumeration enum;
int reduce;
long timeout;
TxConnection txConn;
while ( true ) {
// Go to sleep for the duration of a transaction
// timeout. This mean transactions will timeout on average
// at _txTimeout * 1.5.
try {
Thread.sleep( _txTimeout * 1000 );
} catch ( InterruptedException except ) {
}
try {
// Check to see if there are any pooled connections
// we can release. We release 10% of the pooled
// connections each time, so in a heavy loaded
// environment we don't get to release that many, but
// as load goes down we do. These are not actually
// pooled connections, but connections that happen to
// get in and out of a transaction, not that many.
reduce = _pool.size() - ( _pool.size() / 10 ) - 1;
if ( reduce >= 0 && _pool.size() > reduce ) {
if ( getLogWriter() != null )
getLogWriter().println( "DataSource " + toString() +
": Reducing internal connection pool size from " +
_pool.size() + " to " + reduce );
while ( _pool.size() > reduce ) {
try {
( (Connection) _pool.pop() ).close();
} catch ( SQLException except ) { }
}
}
} catch ( Exception except ) { }
// Look for all connections inside a transaction that
// should have timed out by now.
timeout = System.currentTimeMillis();
enum = _txConnections.elements();
while ( enum.hasMoreElements() ) {
txConn = (TxConnection) enum.nextElement();
// If the transaction timed out, we roll it back and
// invalidate it, but do not remove it from the transaction
// list yet. We wait for the next iteration, minimizing the
// chance of a NOTA exception.
if ( txConn.conn == null ) {
_txConnections.remove( txConn.xid );
// Chose not to use an iterator so we must
// re-enumerate the list after removing
// an element from it.
enum = _txConnections.elements();
} else if ( txConn.timeout < timeout ) {
try {
Connection underlying;
synchronized ( txConn ) {
if ( txConn.conn == null )
continue;
if ( getLogWriter() != null )
getLogWriter().println( "DataSource " + toString() +
": Transaction timed out and being aborted: " +
txConn.xid );
// Remove the connection from the transaction
// association. XAConnection will now have
// no underlying connection and attempt to
// create a new one.
underlying = txConn.conn;
txConn.conn = null;
txConn.timedOut = true;
// Rollback the underlying connection to
// abort the transaction and release the
// underlying connection to the pool.
try {
underlying.rollback();
releaseConnection( underlying );
} catch ( SQLException except ) {
if ( getLogWriter() != null )
getLogWriter().println( "DataSource " + toString() +
": Error aborting timed out transaction: " + except );
try {
underlying.close();
} catch ( SQLException e2 ) { }
}
}
} catch ( Exception except ) { }
}
}
}
}
public void debug( PrintWriter writer )
{
Enumeration enum;
TxConnection txConn;
StringBuffer buffer;
writer.println( "Debug info for XADataSource:" );
enum = _txConnections.elements();
if ( ! enum.hasMoreElements() )
writer.println( "Empty" );
while ( enum.hasMoreElements() ) {
buffer = new StringBuffer();
txConn = (TxConnection) enum.nextElement();
buffer.append( "TxConnection " );
if ( txConn.xid != null )
buffer.append( txConn.xid );
if ( txConn.conn != null )
buffer.append( ' ' ).append( txConn.conn );
buffer.append( " count: " ).append( txConn.count );
if ( txConn.prepared )
buffer.append( " prepared" );
if ( txConn.timedOut )
buffer.append( " timed-out" );
if ( txConn.readOnly )
buffer.append( " read-only" );
writer.println( buffer.toString() );
}
enum = _pool.elements();
while ( enum.hasMoreElements() )
writer.println( "Pooled underlying: " + enum.nextElement().toString() );
}
}