summaryrefslogtreecommitdiff
path: root/src/interfaces/jdbc/org/postgresql/xa/XADataSourceImpl.java
blob: b1e3f4fa0a493fc34b097c782c50f63fec8b06bb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
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() );
    }

    
}