Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
405 additions
and
77 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,7 @@ nbproject/** | |
dist/** | ||
nbproject/** | ||
build.xml | ||
jarlib/** | ||
\.orig$ | ||
\.orig\..*$ | ||
\.chg\..*$ | ||
|
314 changes: 314 additions & 0 deletions
314
src/uk/org/whoami/authme/datasource/MiniConnectionPoolManager.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,314 @@ | ||
// Copyright 2007-2011 Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland | ||
// www.source-code.biz, www.inventec.ch/chdh | ||
// | ||
// This module is multi-licensed and may be used under the terms | ||
// of any of the following licenses: | ||
// | ||
// EPL, Eclipse Public License, http://www.eclipse.org/legal | ||
// LGPL, GNU Lesser General Public License, http://www.gnu.org/licenses/lgpl.html | ||
// MPL, Mozilla Public License 1.1, http://www.mozilla.org/MPL | ||
// | ||
// Please contact the author if you need another license. | ||
// This module is provided "as is", without warranties of any kind. | ||
|
||
package uk.org.whoami.authme.datasource; | ||
|
||
import java.io.PrintWriter; | ||
import java.sql.Connection; | ||
import java.sql.SQLException; | ||
import java.util.concurrent.Semaphore; | ||
import java.util.concurrent.TimeUnit; | ||
import java.util.LinkedList; | ||
import javax.sql.ConnectionEvent; | ||
import javax.sql.ConnectionEventListener; | ||
import javax.sql.ConnectionPoolDataSource; | ||
import javax.sql.PooledConnection; | ||
|
||
/** | ||
* A lightweight standalone JDBC connection pool manager. | ||
* | ||
* <p>The public methods of this class are thread-safe. | ||
* | ||
* <p>Home page: <a href="http://www.source-code.biz/miniconnectionpoolmanager">www.source-code.biz/miniconnectionpoolmanager</a><br> | ||
* Author: Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland<br> | ||
* Multi-licensed: EPL / LGPL / MPL. | ||
*/ | ||
public class MiniConnectionPoolManager { | ||
|
||
private ConnectionPoolDataSource dataSource; | ||
private int maxConnections; | ||
private long timeoutMs; | ||
private PrintWriter logWriter; | ||
private Semaphore semaphore; | ||
private LinkedList<PooledConnection> recycledConnections; | ||
private int activeConnections; | ||
private PoolConnectionEventListener poolConnectionEventListener; | ||
private boolean isDisposed; | ||
private boolean doPurgeConnection; | ||
|
||
/** | ||
* Thrown in {@link #getConnection()} or {@link #getValidConnection()} when no free connection becomes | ||
* available within <code>timeout</code> seconds. | ||
*/ | ||
public static class TimeoutException extends RuntimeException { | ||
private static final long serialVersionUID = 1; | ||
public TimeoutException () { | ||
super("Timeout while waiting for a free database connection."); } | ||
public TimeoutException (String msg) { | ||
super(msg); }} | ||
|
||
/** | ||
* Constructs a MiniConnectionPoolManager object with a timeout of 60 seconds. | ||
* | ||
* @param dataSource | ||
* the data source for the connections. | ||
* @param maxConnections | ||
* the maximum number of connections. | ||
*/ | ||
public MiniConnectionPoolManager (ConnectionPoolDataSource dataSource, int maxConnections) { | ||
this(dataSource, maxConnections, 60); } | ||
|
||
/** | ||
* Constructs a MiniConnectionPoolManager object. | ||
* | ||
* @param dataSource | ||
* the data source for the connections. | ||
* @param maxConnections | ||
* the maximum number of connections. | ||
* @param timeout | ||
* the maximum time in seconds to wait for a free connection. | ||
*/ | ||
public MiniConnectionPoolManager (ConnectionPoolDataSource dataSource, int maxConnections, int timeout) { | ||
this.dataSource = dataSource; | ||
this.maxConnections = maxConnections; | ||
this.timeoutMs = timeout * 1000L; | ||
try { | ||
logWriter = dataSource.getLogWriter(); } | ||
catch (SQLException e) {} | ||
if (maxConnections < 1) { | ||
throw new IllegalArgumentException("Invalid maxConnections value."); } | ||
semaphore = new Semaphore(maxConnections,true); | ||
recycledConnections = new LinkedList<PooledConnection>(); | ||
poolConnectionEventListener = new PoolConnectionEventListener(); } | ||
|
||
/** | ||
* Closes all unused pooled connections. | ||
*/ | ||
public synchronized void dispose() throws SQLException { | ||
if (isDisposed) { | ||
return; } | ||
isDisposed = true; | ||
SQLException e = null; | ||
while (!recycledConnections.isEmpty()) { | ||
PooledConnection pconn = recycledConnections.remove(); | ||
try { | ||
pconn.close(); } | ||
catch (SQLException e2) { | ||
if (e == null) { | ||
e = e2; }}} | ||
if (e != null) { | ||
throw e; }} | ||
|
||
/** | ||
* Retrieves a connection from the connection pool. | ||
* | ||
* <p>If <code>maxConnections</code> connections are already in use, the method | ||
* waits until a connection becomes available or <code>timeout</code> seconds elapsed. | ||
* When the application is finished using the connection, it must close it | ||
* in order to return it to the pool. | ||
* | ||
* @return | ||
* a new <code>Connection</code> object. | ||
* @throws TimeoutException | ||
* when no connection becomes available within <code>timeout</code> seconds. | ||
*/ | ||
public Connection getConnection() throws SQLException { | ||
return getConnection2(timeoutMs); } | ||
|
||
private Connection getConnection2 (long timeoutMs) throws SQLException { | ||
// This routine is unsynchronized, because semaphore.tryAcquire() may block. | ||
synchronized (this) { | ||
if (isDisposed) { | ||
throw new IllegalStateException("Connection pool has been disposed."); }} | ||
try { | ||
if (!semaphore.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) { | ||
throw new TimeoutException(); }} | ||
catch (InterruptedException e) { | ||
throw new RuntimeException("Interrupted while waiting for a database connection.",e); } | ||
boolean ok = false; | ||
try { | ||
Connection conn = getConnection3(); | ||
ok = true; | ||
return conn; } | ||
finally { | ||
if (!ok) { | ||
semaphore.release(); }}} | ||
|
||
private synchronized Connection getConnection3() throws SQLException { | ||
if (isDisposed) { | ||
throw new IllegalStateException("Connection pool has been disposed."); } // test again with lock | ||
PooledConnection pconn; | ||
if (!recycledConnections.isEmpty()) { | ||
pconn = recycledConnections.remove(); } | ||
else { | ||
pconn = dataSource.getPooledConnection(); | ||
pconn.addConnectionEventListener(poolConnectionEventListener); } | ||
Connection conn = pconn.getConnection(); | ||
activeConnections++; | ||
assertInnerState(); | ||
return conn; } | ||
|
||
/** | ||
* Retrieves a connection from the connection pool and ensures that it is valid | ||
* by calling {@link Connection#isValid(int)}. | ||
* | ||
* <p>If a connection is not valid, the method tries to get another connection | ||
* until one is valid (or a timeout occurs). | ||
* | ||
* <p>Pooled connections may become invalid when e.g. the database server is | ||
* restarted. | ||
* | ||
* <p>This method is slower than {@link #getConnection()} because the JDBC | ||
* driver has to send an extra command to the database server to test the connection. | ||
* | ||
* | ||
* <p>This method requires Java 1.6 or newer. | ||
* | ||
* @throws TimeoutException | ||
* when no valid connection becomes available within <code>timeout</code> seconds. | ||
*/ | ||
public Connection getValidConnection() { | ||
long time = System.currentTimeMillis(); | ||
long timeoutTime = time + timeoutMs; | ||
int triesWithoutDelay = getInactiveConnections() + 1; | ||
while (true) { | ||
Connection conn = getValidConnection2(time, timeoutTime); | ||
if (conn != null) { | ||
return conn; } | ||
triesWithoutDelay--; | ||
if (triesWithoutDelay <= 0) { | ||
triesWithoutDelay = 0; | ||
try { | ||
Thread.sleep(250); } | ||
catch (InterruptedException e) { | ||
throw new RuntimeException("Interrupted while waiting for a valid database connection.", e); }} | ||
time = System.currentTimeMillis(); | ||
if (time >= timeoutTime) { | ||
throw new TimeoutException("Timeout while waiting for a valid database connection."); }}} | ||
|
||
private Connection getValidConnection2 (long time, long timeoutTime) { | ||
long rtime = Math.max(1, timeoutTime - time); | ||
Connection conn; | ||
try { | ||
conn = getConnection2(rtime); } | ||
catch (SQLException e) { | ||
return null; } | ||
rtime = timeoutTime - System.currentTimeMillis(); | ||
int rtimeSecs = Math.max(1, (int)((rtime+999)/1000)); | ||
try { | ||
if (conn.isValid(rtimeSecs)) { | ||
return conn; }} | ||
catch (SQLException e) {} | ||
// This Exception should never occur. If it nevertheless occurs, it's because of an error in the | ||
// JDBC driver which we ignore and assume that the connection is not valid. | ||
// When isValid() returns false, the JDBC driver should have already called connectionErrorOccurred() | ||
// and the PooledConnection has been removed from the pool, i.e. the PooledConnection will | ||
// not be added to recycledConnections when Connection.close() is called. | ||
// But to be sure that this works even with a faulty JDBC driver, we call purgeConnection(). | ||
purgeConnection(conn); | ||
return null; } | ||
|
||
// Purges the PooledConnection associated with the passed Connection from the connection pool. | ||
private synchronized void purgeConnection (Connection conn) { | ||
try { | ||
doPurgeConnection = true; | ||
// (A potential problem of this program logic is that setting the doPurgeConnection flag | ||
// has an effect only if the JDBC driver calls connectionClosed() synchronously within | ||
// Connection.close().) | ||
conn.close(); } | ||
catch (SQLException e) {} | ||
// ignore exception from close() | ||
finally { | ||
doPurgeConnection = false; }} | ||
|
||
private synchronized void recycleConnection (PooledConnection pconn) { | ||
if (isDisposed || doPurgeConnection) { | ||
disposeConnection(pconn); | ||
return; } | ||
if (activeConnections <= 0) { | ||
throw new AssertionError(); } | ||
activeConnections--; | ||
semaphore.release(); | ||
recycledConnections.add(pconn); | ||
assertInnerState(); } | ||
|
||
private synchronized void disposeConnection (PooledConnection pconn) { | ||
pconn.removeConnectionEventListener(poolConnectionEventListener); | ||
if (!recycledConnections.remove(pconn)) { | ||
// If the PooledConnection is not in the recycledConnections list, | ||
// we assume that the connection was active. | ||
if (activeConnections <= 0) { | ||
throw new AssertionError(); } | ||
activeConnections--; | ||
semaphore.release(); } | ||
closeConnectionAndIgnoreException(pconn); | ||
assertInnerState(); } | ||
|
||
private void closeConnectionAndIgnoreException (PooledConnection pconn) { | ||
try { | ||
pconn.close(); } | ||
catch (SQLException e) { | ||
log("Error while closing database connection: "+e.toString()); }} | ||
|
||
private void log (String msg) { | ||
String s = "MiniConnectionPoolManager: "+msg; | ||
try { | ||
if (logWriter == null) { | ||
System.err.println(s); } | ||
else { | ||
logWriter.println(s); }} | ||
catch (Exception e) {}} | ||
|
||
private void assertInnerState() { | ||
if (activeConnections < 0) { | ||
throw new AssertionError(); } | ||
if (activeConnections + recycledConnections.size() > maxConnections) { | ||
throw new AssertionError(); } | ||
if (activeConnections + semaphore.availablePermits() > maxConnections) { | ||
throw new AssertionError(); }} | ||
|
||
private class PoolConnectionEventListener implements ConnectionEventListener { | ||
public void connectionClosed (ConnectionEvent event) { | ||
PooledConnection pconn = (PooledConnection)event.getSource(); | ||
recycleConnection(pconn); } | ||
public void connectionErrorOccurred (ConnectionEvent event) { | ||
PooledConnection pconn = (PooledConnection)event.getSource(); | ||
disposeConnection(pconn); }} | ||
|
||
/** | ||
* Returns the number of active (open) connections of this pool. | ||
* | ||
* <p>This is the number of <code>Connection</code> objects that have been | ||
* issued by {@link #getConnection()}, for which <code>Connection.close()</code> | ||
* has not yet been called. | ||
* | ||
* @return | ||
* the number of active connections. | ||
**/ | ||
public synchronized int getActiveConnections() { | ||
return activeConnections; } | ||
|
||
/** | ||
* Returns the number of inactive (unused) connections in this pool. | ||
* | ||
* <p>This is the number of internally kept recycled connections, | ||
* for which <code>Connection.close()</code> has been called and which | ||
* have not yet been reused. | ||
* | ||
* @return | ||
* the number of inactive connections. | ||
**/ | ||
public synchronized int getInactiveConnections() { | ||
return recycledConnections.size(); } | ||
|
||
} // end class MiniConnectionPoolManager |
Oops, something went wrong.