/** * Mad-Advertisement * Copyright (C) 2011 Thorsten Marx <thmarx@gmx.net> * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see <http://www.gnu.org/licenses/>. */ // 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 biz.source_code.miniConnectionPoolManager; 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