/* * 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.4 2001/11/19 23:16:46 momjian 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 Assaf Arkin * @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() ); } }