/** * This file is part of Waarp Project. * * Copyright 2009, Frederic Bregier, and individual contributors by the @author tags. See the * COPYRIGHT.txt in the distribution for a full listing of individual contributors. * * All Waarp Project 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. * * Waarp 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 Waarp. If not, see * <http://www.gnu.org/licenses/>. */ package org.waarp.common.database; import java.sql.Connection; import java.sql.SQLException; import java.util.ArrayDeque; import java.util.Iterator; import java.util.Queue; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import javax.sql.ConnectionEvent; import javax.sql.ConnectionEventListener; import javax.sql.ConnectionPoolDataSource; import javax.sql.PooledConnection; /** * * A simple standalone JDBC connection pool manager. * <p/> * The public methods of this class are thread-safe. * <p/> * Nothe that JDBC4 is needed and isValid() must be implemented (not yet in PostGre in April 2012) * <p/> * * @author Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland<br> * Multi-licensed: EPL/LGPL/MPL. * @author Frederic Bregier <br> * Add TimerTask support to close after some "delay" any still connected sessions * */ public class DbConnectionPool { private ConnectionPoolDataSource dataSource; private int maxConnections; private int timeout; private long timeOutForceClose = 300000; // 5 minutes // private PrintWriter logWriter; private Semaphore semaphore; private Queue<Con> recycledConnections; private int activeConnections; private PoolConnectionEventListener poolConnectionEventListener; private boolean isDisposed; static class Con { final PooledConnection pooledCon; long lastRecyle; Con(PooledConnection pooledCon) { this.pooledCon = pooledCon; lastRecyle = System.currentTimeMillis(); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || !(o instanceof Con)) return false; Con con = (Con) o; return pooledCon.equals(con.pooledCon); } @Override public int hashCode() { return pooledCon.hashCode(); } } /** * Class to check validity of connections in the pool * * @author Frederic Bregier * */ private static class TimerTaskCheckConnections extends TimerTask { DbConnectionPool pool; Timer timer; long delay; /** * * @param timer * @param delay * @param pool */ private TimerTaskCheckConnections(Timer timer, long delay, DbConnectionPool pool) { if (pool == null || timer == null || delay < 1000) { throw new IllegalArgumentException( "Invalid values. Need pool, timer and delay >= 1000"); } this.pool = pool; this.timer = timer; this.delay = delay; } public void run() { Iterator<Con> conIterator = pool.recycledConnections.iterator(); long now = System.currentTimeMillis(); while (conIterator.hasNext()) { Con c = conIterator.next(); if (c.lastRecyle + pool.timeOutForceClose < now) { conIterator.remove(); pool.closeConnectionNoEx(c.pooledCon); } else { try { if (!c.pooledCon.getConnection().isValid(DbConstant.VALIDTESTDURATION)) { conIterator.remove(); pool.closeConnectionNoEx(c.pooledCon); } } catch (SQLException e) { conIterator.remove(); pool.closeConnectionNoEx(c.pooledCon); } } } timer.schedule(this, delay); } } /** * Release all idle connections */ public synchronized void freeIdleConnections() { Iterator<Con> conIterator = recycledConnections.iterator(); long now = System.currentTimeMillis(); while (conIterator.hasNext()) { Con c = conIterator.next(); if (c.lastRecyle + timeOutForceClose < now) { conIterator.remove(); closeConnectionNoEx(c.pooledCon); } } } /** * Thrown in 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."); } } /** * Constructs a MiniConnectionPoolManager object with no timeout and no limit. * * @param dataSource * the data source for the connections. */ public DbConnectionPool(ConnectionPoolDataSource dataSource) { this(dataSource, 0, DbConstant.DELAYMAXCONNECTION); } /** * Constructs a MiniConnectionPoolManager object with no timeout and no limit. * * @param dataSource * the data source for the connections. * @param timer * @param delay * in ms period of time to check existing connections and limit to get a new * connection * */ public DbConnectionPool(ConnectionPoolDataSource dataSource, Timer timer, long delay) { this(dataSource, 0, (int) (delay / 1000)); timer.schedule(new TimerTaskCheckConnections(timer, delay, this), delay); } /** * Constructs a MiniConnectionPoolManager object with a timeout of DbConstant.DELAYMAXCONNECTION * seconds. * * @param dataSource * the data source for the connections. * @param maxConnections * the maximum number of connections. 0 means no limit */ public DbConnectionPool(ConnectionPoolDataSource dataSource, int maxConnections) { this(dataSource, maxConnections, DbConstant.DELAYMAXCONNECTION); } /** * Constructs a ConnectionPool object. * * @param dataSource * the data source for the connections. * @param maxConnections * the maximum number of connections. 0 means no limit * @param timeout * the maximum time in seconds to wait for a free connection. */ public DbConnectionPool(ConnectionPoolDataSource dataSource, int maxConnections, int timeout) { this.dataSource = dataSource; this.maxConnections = maxConnections; this.timeout = timeout; if (maxConnections != 0) { // if (maxConnections < 1) throw new // IllegalArgumentException("Invalid maxConnections value."); if (timeout <= 0) { throw new IllegalArgumentException("Invalid timeout value."); } semaphore = new Semaphore(maxConnections, true); } recycledConnections = new ArrayDeque<Con>(); poolConnectionEventListener = new PoolConnectionEventListener(); } public void resetPoolDataSource(ConnectionPoolDataSource dataSource) { this.dataSource = dataSource; } /** * * @return the max number of connections */ public int getMaxConnections() { return this.maxConnections; } /** * * @return the Login Timeout in second */ public long getLoginTimeout() { return this.timeout; } /** * * @return the Force Close Timeout in ms */ public long getTimeoutForceClose() { return this.timeOutForceClose; } /** * Closes all unused pooled connections. * * @throws java.sql.SQLException * // */ public synchronized void dispose() throws SQLException { if (isDisposed) return; isDisposed = true; SQLException e = null; while (!recycledConnections.isEmpty()) { Con c = recycledConnections.remove(); PooledConnection pconn = c.pooledCon; try { pconn.close(); } catch (SQLException e2) { if (e == null) e = e2; } } if (e != null) throw e; } /** * Retrieves a connection from the connection pool. 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 Connection object. * @throws TimeoutException * when no connection becomes available within <code>timeout</code> seconds. * @throws java.sql.SQLException * // */ public Connection getConnection() throws SQLException { // This routine is unsynchronized, because semaphore.tryAcquire() may // block. synchronized (this) { if (isDisposed) throw new IllegalStateException( "Connection pool has been disposed."); } if (semaphore != null) { try { if (!semaphore.tryAcquire(timeout, TimeUnit.SECONDS)) throw new TimeoutException(); } catch (InterruptedException e) { throw new RuntimeException( "Interrupted while waiting for a database connection.", e); } } boolean ok = false; try { Connection conn = getConnection2(); ok = true; return conn; } finally { if (semaphore != null) { if (!ok) semaphore.release(); } } } private synchronized Connection getConnection2() throws SQLException { if (isDisposed) throw new IllegalStateException( "Connection pool has been disposed."); // test again with // lock long time = System.currentTimeMillis() + timeout * 1000; while (true) { PooledConnection pconn; if (!recycledConnections.isEmpty()) { pconn = recycledConnections.remove().pooledCon; } else { pconn = dataSource.getPooledConnection(); } Connection conn = pconn.getConnection(); if (conn.isValid(DbConstant.VALIDTESTDURATION)) { activeConnections++; pconn.addConnectionEventListener(poolConnectionEventListener); assertInnerState(); return conn; } if (time > System.currentTimeMillis()) { // too long break; } } throw new SQLException("Could not get a valid connection before timeout"); } private synchronized void recycleConnection(PooledConnection pconn) { if (isDisposed) { disposeConnection(pconn); return; } try { if (!pconn.getConnection().isValid(DbConstant.VALIDTESTDURATION)) { disposeConnection(pconn); return; } } catch (SQLException e) { disposeConnection(pconn); return; } if (activeConnections <= 0) throw new AssertionError(); activeConnections--; if (semaphore != null) { semaphore.release(); } recycledConnections.add(new Con(pconn)); assertInnerState(); } private synchronized void disposeConnection(PooledConnection pconn) { if (activeConnections <= 0) throw new AssertionError(); activeConnections--; if (semaphore != null) { semaphore.release(); } closeConnectionNoEx(pconn); assertInnerState(); } private void closeConnectionNoEx(PooledConnection pconn) { try { pconn.close(); } catch (SQLException e) { // } } private void assertInnerState() { if (activeConnections < 0) throw new AssertionError(); if (semaphore != null) { 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(); pconn.removeConnectionEventListener(this); recycleConnection(pconn); } public void connectionErrorOccurred(ConnectionEvent event) { PooledConnection pconn = (PooledConnection) event.getSource(); pconn.removeConnectionEventListener(this); disposeConnection(pconn); } } /** * Returns the number of active (open) connections of this pool. 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; } }