/* * Copyright (C) 2006-2008 Alfresco Software Limited. * * 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 2 * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * As a special exception to the terms and conditions of version 2.0 of * the GPL, you may redistribute this Program in connection with Free/Libre * and Open Source Software ("FLOSS") applications as described in Alfresco's * FLOSS exception. You should have recieved a copy of the text describing * the FLOSS exception, and it is also available here: * http://www.alfresco.com/legal/licensing" */ package org.alfresco.jlan.util.db; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.Enumeration; import java.util.Hashtable; import java.util.Vector; import org.alfresco.jlan.debug.Debug; /** * Database Connection Pool Class * * @author gkspencer */ public class DBConnectionPool { // Permanent lease time public final static long PermanentLease = -1L; // Maximum/minimum connections public final static int MinimumConnections = 5; public final static int MaximumConnections = 500; // Default pool size public final static int DefaultMinSize = 5; public final static int DefaultMaxSize = 10; // Default connection lease time private final static long DefaultLease = 30000; // 30 seconds // Period that the database reaper checks the free connections to see if they have timed out private final static int ConnectionCheck = 20; // x lease timeout // Database connection details private String m_dbDriver; private String m_dsn; private String m_user; private String m_password; // Minimum/maximum number of connections to pool private int m_minPoolSize = DefaultMinSize; private int m_maxPoolSize = DefaultMaxSize; // Connection lease time, in milliseconds private long m_leaseTime = DefaultLease; // Free/in use connection pools private Vector<Connection> m_freePool; private Hashtable<Connection, Long> m_allocPool; // Connection reaper thread private DBConnectionReaper m_reaper; private Thread m_reaperThread; // Database connection status private boolean m_online; // Database connection pool event listener private DBConnectionPoolListener m_dbListener; /** * Database Connection Reaper Thread Class * * <p>Check for connections whose lease has expired and return them to the free connection * pool. */ protected class DBConnectionReaper implements Runnable { // Reaper wakeup interval private long m_wakeup; // Shutdown request flag private boolean m_shutdown = false; // Database online check interval, as number of thread wakeups private int m_onlineCheckInterval = ConnectionCheck; /** * Class constructor * * @param intvl long */ public DBConnectionReaper(long intvl) { m_wakeup = intvl; } /** * Shutdown the connection reaper */ public final void shutdownRequest() { m_shutdown = true; m_reaperThread.interrupt(); } /** * Set the database online check interval * * @param interval int */ public final void setOnlineCheckInterval( int interval) { m_onlineCheckInterval = interval; } /** * Connection reaper thread */ public void run() { // Load the initial free connection pool synchronized ( m_freePool) { try { // Allocate a pool of connections while ( m_freePool.size() < getMinimumPoolSize()) { Connection conn = createConnection(); if ( conn != null) m_freePool.add(conn); } // Indicate that the connection pool is online m_online = true; notifyConnectionPoolState(); } catch (SQLException ex) { } } // Loop forever, or until shutdown int loopCnt = 1; while ( m_shutdown == false) { // Sleep for a while try { Thread.sleep(m_wakeup); } catch(InterruptedException ex) { } // Check if there is a shutdown pending if ( m_shutdown == true) break; // Update the loop counter loopCnt++; // Check for expired connection leases synchronized ( m_allocPool) { // DEBUG // Debug.println("DBConnectionReaper allocPool=" + m_allocPool.size() + ", freePool=" + m_freePool.size()); // Get the current time long timeNow = System.currentTimeMillis(); // Enumerate the allocated connection pool Enumeration<Connection> enm = m_allocPool.keys(); boolean removeConn = false; while ( enm.hasMoreElements()) { // Get the connection Connection conn = enm.nextElement(); Long expire = m_allocPool.get(conn); // Check if the connection lease has expired if ( expire.longValue() != PermanentLease && expire.longValue() < timeNow) removeConn = true; else if ( expire.longValue() == PermanentLease) { try { // Check if the connection has been closed or the server is down if ( conn.isClosed()) removeConn = true; else { // Toggle the auto-commit flag on the connection, this will check the connection on most // databases boolean autoCommit = conn.getAutoCommit(); conn.setAutoCommit( true); conn.setAutoCommit( autoCommit); } } catch (SQLException ex) { removeConn = true; } // DEBUG if ( removeConn == true) Debug.println("DBConnectionReaper Permanent lease connection error"); } // Check if the connection should be closed if ( removeConn == true) { // Connection lease has expired, remove from the allocated list and close the connection m_allocPool.remove(conn); try { conn.close(); Debug.println("DBConnectionReaper closed expired connection, conn=" + conn); } catch (SQLException ex) { } } } } // Check if the free pool has grown too far synchronized ( m_freePool) { // Release connections from the free pool until below the maximum connection limit while ( m_freePool.size() > getMaximumPoolSize()) { // DEBUG Debug.println("DBConnectionReaper trimming free pool, " + m_freePool.size() + "/" + getMaximumPoolSize()); // Remove a connection from the free pool and close the connection Connection conn = m_freePool.remove( 0); if ( conn != null) { try { conn.close(); } catch (SQLException ex) { } } } } // Check the connections in the free pool to see if they have been timed out by the server if ( loopCnt % m_onlineCheckInterval == 0 || isOnline() == false) { synchronized ( m_freePool) { // DEBUG // Debug.println( "DBConnectionReaper Checking free pool connection status ..."); // Check if the connections in the free pool are still connected/valid int idx = 0; while ( idx < m_freePool.size()) { // Get the current connection Connection conn = m_freePool.get( idx); try { // Check if the connection is closed if ( conn.isClosed()) { // Remove the connection from the free pool m_freePool.remove( idx); // DEBUG // Debug.println( "DBConnectionReaper Removed closed connection from free pool"); } else { // Toggle the auto-commit flag on the connection, this will check the connection on most // databases boolean autoCommit = conn.getAutoCommit(); conn.setAutoCommit( true); conn.setAutoCommit( autoCommit); // Update the connection index idx++; } } catch (SQLException ex) { // Remove the current connection from the free pool try { m_freePool.remove( idx); conn.close(); } catch (Exception ex2) { } // DEBUG // Debug.println( "DBConnectionReaper Removed closed connection from free pool (exception)"); } } if ( isOnline()) { // Check if the free and allocated pools are empty, this indicates that the database server // is offline if ( m_freePool.size() == 0 && m_allocPool.size() == 0) { // Database server appears to be offline m_online = false; notifyConnectionPoolState(); } } else { // Try and get a connection from the pool, this will check if the database server is back online Connection conn = getConnection(); if ( conn != null) releaseConnection( conn); } // DEBUG // Debug.println( "DBConnectionReaper Free pool check done."); } } } } }; /** * Class constructor * * @param driver String * @param dsn String * @param user String * @param pwd String */ public DBConnectionPool(String driver, String dsn, String user, String pwd) throws Exception { // Set the JDBC connection details m_dbDriver = driver; m_dsn = dsn; m_user = user; m_password = pwd; // Call the common constructor code commonConstructor(); } /** * Class constructor * * @param driver String * @param dsn String * @param user String * @param pwd String * @param initConns int * @param maxConns int */ public DBConnectionPool(String driver, String dsn, String user, String pwd, int initConns, int maxConns) throws Exception { // Set the JDBC connection details m_dbDriver = driver; m_dsn = dsn; m_user = user; m_password = pwd; // Set the pool size if ( initConns > 0) m_minPoolSize = initConns; if ( maxConns > 0) m_maxPoolSize = maxConns; // Call the common constructor code commonConstructor(); } /** * Common constructor code */ private void commonConstructor() throws Exception { // Load the database driver class Class.forName(m_dbDriver).newInstance(); // Allocate the free and in use connection pools m_freePool = new Vector<Connection>(); m_allocPool = new Hashtable<Connection, Long>(); // Start the connection reaper thread m_reaper = new DBConnectionReaper(getLeaseTime()); m_reaperThread = new Thread(m_reaper); m_reaperThread.setDaemon(true); m_reaperThread.setName("DBConnectionReaper"); m_reaperThread.start(); } /** * Get the JDBC driver details * * @return String */ public final String getDriver() { return m_dbDriver; } /** * Get the connection details * * @return String */ public final String getDSN() { return m_dsn; } /** * Get the user name * * @return String */ public final String getUserName() { return m_user; } /** * Get the password * * @return String */ public final String getPassword() { return m_password; } /** * Get the minimum pool size * * @return int */ public final int getMinimumPoolSize() { return m_minPoolSize; } /** * Get the maximum pool size * * @return int */ public final int getMaximumPoolSize() { return m_maxPoolSize; } /** * Get the connection lease time, in milliseconds * * @return long */ public final long getLeaseTime() { return m_leaseTime; } /** * Get the available connection count * * @return int */ public final synchronized int getAvailableConnections() { return m_freePool.size(); } /** * Get the in use connection count * * @return int */ public final synchronized int getAllocatedConnections() { return m_allocPool.size(); } /** * Get a connection from the pool * * @return Connection */ public final Connection getConnection() { // Get a timestamp for the connection long expireTime = System.currentTimeMillis() + getLeaseTime(); return getConnection(expireTime); } /** * Check if the connection pool is online * * @return boolean */ public final boolean isOnline() { return m_online; } /** * Get a connection from the pool with the specified lease time. A lease time of -1 indicates that * the connection lease is permanent. * * @param expireTime long * @return Connection */ public final Connection getConnection(long expireTime) { // Check for a connection in the free pool Connection conn = null; synchronized ( m_freePool) { // Check if the free pool has a connection if ( m_freePool.size() > 0) { // Get the connection conn = m_freePool.remove(0); try { if ( conn.isClosed()) conn = null; } catch (SQLException ex) { conn = null; Debug.println("%%%%% SQL Connection Error: " + ex.toString()); } } else if ( isOnline() == false) { // Try and create a new connection, if we succeed then the database server is back online try { // Create a new database connection conn = createConnection(); } catch ( SQLException ex) { } } } // Check if the database server is back online if ( isOnline() == false) { if ( conn != null) { m_online = true; notifyConnectionPoolState(); } else return null; } // Allocate a new connection if there are spare slots available synchronized ( m_allocPool) { // If the connection is valid add it to the allocated pool if ( conn != null) m_allocPool.put(conn, new Long(expireTime)); else { // If a connection has not been allocated and there are spare slots available then // create a new connection. if ( m_allocPool.size() < getMaximumPoolSize()) { try { // Create a new connection conn = createConnection(); // Add the connection to the in use list m_allocPool.put(conn, new Long(expireTime)); } catch (SQLException ex) { conn = null; Debug.println("%%%%% SQL Connection Error: " + ex.toString()); } } } } // Return the connection return conn; } /** * Release a connection back to the free pool * * @param conn Connection */ public final void releaseConnection(Connection conn) { // Remove the connection from the in use pool and put it back in the free list synchronized ( m_allocPool) { Object curConn = m_allocPool.remove(conn); if ( curConn == null) return; } // Add the connection to the free pool synchronized ( m_freePool) { // Add the connection back to the free pool, if not closed try { if ( conn.isClosed() == false) { m_freePool.add(conn); } else Debug.println("***** Connection closed *****"); } catch (Exception ex) { } } } /** * Renew a lease on a connection for the default lease time * * @param conn */ public final void renewLease(Connection conn) { renewLease(conn, System.currentTimeMillis() + getLeaseTime()); } /** * Renew a lease on a connection to hold onto the connection for longer * * @param conn Connection * @param expireTime long */ public final void renewLease(Connection conn, long expireTime) { synchronized ( m_allocPool) { // Check that the connection is in the allocated pool if ( m_allocPool.remove(conn) == null) return; // Update the expire time for the lease and add back to the allocated pool m_allocPool.put(conn, new Long(expireTime)); } } /** * Close the connection pool */ public final void closePool() { // Shutdown the connection reaper thread m_reaper.shutdownRequest(); // Close all allocated database connections if ( m_allocPool.size() > 0) { // Close all allocated connections Enumeration<Connection> enm = m_allocPool.keys(); while ( enm.hasMoreElements()) { // Close the connection Connection conn = enm.nextElement(); try { conn.close(); } catch (SQLException ex) { } } // Clear the allocated pool m_allocPool.clear(); } // Close all free database connections if ( m_freePool.size() > 0) { // Close the free pool connections for ( int i = 0; i < m_freePool.size(); i++) { // Get the current connection Connection conn = m_freePool.get(i); try { conn.close(); } catch (SQLException ex) { } } // Clear the free pool m_freePool.removeAllElements(); } } /** * Set the connection pool initial and maximum sizes * * @param initSize int * @param maxSize int */ public final void setPoolSize(int initSize, int maxSize) { m_minPoolSize = initSize; m_maxPoolSize = maxSize; } /** * Set the default connection lease time * * @param leaseTime long */ public final void setDefaultLeaseTime(long leaseTime) { m_leaseTime = leaseTime; } /** * Set the online check interval, in seconds * * @param interval int */ public final void setOnlineCheckInterval( int interval) { // Set the reaper thread online check interval, convert seconds to thread wakeups int wakeups = interval / (int) DefaultLease; if ( wakeups < 1) wakeups = 1; m_reaper.setOnlineCheckInterval( wakeups); } /** * Check if there is a connection pool listener * * @return boolean */ public final boolean hasConnectionPoolListener() { return m_dbListener != null ? true : false; } /** * Add a database connection pool listener * * @param l DBConnectionPoolListener */ public final void addConnectionPoolListener( DBConnectionPoolListener l) { m_dbListener = l; } /** * Remove a database connection pool listener */ public final DBConnectionPoolListener removeConnectionPoolListener() { DBConnectionPoolListener l = m_dbListener; m_dbListener = null; return l; } /** * Create a new database connection * * @return Connection * @exception SQLException */ protected final Connection createConnection() throws SQLException { // Create a new database connection return DriverManager.getConnection(getDSN(), getUserName(), getPassword()); } /** * Notify the connection pool listener of an online/offline state change */ protected final void notifyConnectionPoolState() { // DEBUG Debug.println( "DBConnectionPool: Database server is " + ( isOnline() ? "OnLine" : "OffLine")); // Inform the connection pool listener if ( hasConnectionPoolListener()) m_dbListener.databaseOnlineStatus( isOnline()); else Debug.println("DBConnectionPool: No listener"); } }