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:
495
src/interfaces/jdbc/org/postgresql/xa/ClientConnection.java
Normal file
495
src/interfaces/jdbc/org/postgresql/xa/ClientConnection.java
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
377
src/interfaces/jdbc/org/postgresql/xa/Test.java
Normal file
377
src/interfaces/jdbc/org/postgresql/xa/Test.java
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
116
src/interfaces/jdbc/org/postgresql/xa/TwoPhaseConnection.java
Normal file
116
src/interfaces/jdbc/org/postgresql/xa/TwoPhaseConnection.java
Normal 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 );
|
||||
|
||||
|
||||
}
|
129
src/interfaces/jdbc/org/postgresql/xa/TxConnection.java
Normal file
129
src/interfaces/jdbc/org/postgresql/xa/TxConnection.java
Normal 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;
|
||||
|
||||
|
||||
}
|
||||
|
855
src/interfaces/jdbc/org/postgresql/xa/XAConnectionImpl.java
Normal file
855
src/interfaces/jdbc/org/postgresql/xa/XAConnectionImpl.java
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
459
src/interfaces/jdbc/org/postgresql/xa/XADataSourceImpl.java
Normal file
459
src/interfaces/jdbc/org/postgresql/xa/XADataSourceImpl.java
Normal 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() );
|
||||
}
|
||||
|
||||
|
||||
}
|
Reference in New Issue
Block a user