/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2003-2008, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library 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 * Lesser General Public License for more details. */ package org.geotools.data.jdbc; import java.sql.Connection; import java.sql.SQLException; import java.util.Iterator; import java.util.LinkedList; import java.util.logging.Level; import java.util.logging.Logger; import javax.sql.ConnectionEvent; import javax.sql.ConnectionEventListener; import javax.sql.ConnectionPoolDataSource; import javax.sql.DataSource; import javax.sql.PooledConnection; import org.geotools.data.jdbc.datasource.DataSourceFinder; import org.geotools.data.jdbc.datasource.DataSourceUtil; /** * Provides a ConnectionPool that can be used by multiple data sources to get connections to a * single database. * * <p> * This class should not be subclassed. * </p> * * @author Sean Geoghegan, Defence Science and Technology Organisation * @author Chris Holmes * * @source $URL$ * @version $Id$ * @deprecated Use {@link DataSource}, {@link DataSourceUtil} and {@link DataSourceFinder} instead * * @deprecated scheduled for removal in 2.7, use classes in org.geotools.jdbc */ public final class ConnectionPool { /** A logger */ private static final Logger LOGGER = org.geotools.util.logging.Logging.getLogger("org.geotools.data.jdbc"); /** The default wait time for cleaning the Pool - defaults to 30 secs */ private static final long DEFAULT_POOL_CLEANER_WAIT = 30000; /** A mutex for synchronizing */ private Object mutex = new Object(); /** The data source we get the pooled connections from */ private ConnectionPoolDataSource cpDataSource; /** List of available connections */ private LinkedList availableConnections = new LinkedList(); /** This of connections that are in use */ private LinkedList usedConnections = new LinkedList(); /** Handles ConnectionEvents and manages the lists */ private ConnectionListManager listManager = new ConnectionListManager(); /** Cleans the list of dead connections */ private ConnectionPoolCleaner poolCleaner; /** Indicates that this Connection Pool is closed and it should not * return connections on calls to getConnection() */ private boolean closed = false; /** * Creates a new Connection Pool using a ConnectionPoolDataSource. * * <p> * This constructor will also spawn a thread for cleaning the connection pool every 30 seconds. * </p> * * @param cpDataSource The Connection Pool Data Source to get PooledConnections from. */ public ConnectionPool(ConnectionPoolDataSource cpDataSource) { this.cpDataSource = cpDataSource; poolCleaner = new ConnectionPoolCleaner(DEFAULT_POOL_CLEANER_WAIT); Thread cleanerThread = new Thread(poolCleaner); cleanerThread.setDaemon(true); cleanerThread.start(); } /** * Gets a Connection from the Connection Pool. * * <p> * If no available connections exist a new connection will be created and added to the pool. * When the returned connection is closed it will be added to the connection pool for other * requests to this method. * </p> * * @return A Connection from the ConnectionPool. * * @throws SQLException If an error occurs getting the connection or if the * connection pool has been closed by a previous call to close(). */ public Connection getConnection() throws SQLException { if (closed) { throw new SQLException("The ConnectionPool has been closed."); } Connection conn = null; synchronized (mutex) { if (availableConnections.size() > 0) { LOGGER.fine("Getting available connection."); ManagedPooledConnection mConn = (ManagedPooledConnection) availableConnections.removeFirst(); conn = mConn.pooledConn.getConnection(); mConn.lastUsed = System.currentTimeMillis(); mConn.inUse = true; usedConnections.add(mConn); } else { LOGGER.fine("No available connections, creating a new one."); PooledConnection pConn = cpDataSource.getPooledConnection(); conn = pConn.getConnection(); pConn.addConnectionEventListener(listManager); ManagedPooledConnection mConn = new ManagedPooledConnection(pConn); mConn.inUse = true; mConn.lastUsed = System.currentTimeMillis(); usedConnections.add(mConn); } } return conn; } /** * Helper method to get the ManagedPooledConnection for a given PooledConnection. * * @param conn The PooledConnection * * @return The ManagedPooledConnection that contains the PooledConnection. */ private ManagedPooledConnection getInUseManagedPooledConnection(PooledConnection conn) { ManagedPooledConnection returnConn = null; for (Iterator iter = usedConnections.iterator(); iter.hasNext();) { ManagedPooledConnection mConn = (ManagedPooledConnection) iter.next(); if (mConn.pooledConn == conn) { returnConn = mConn; } } return returnConn; } /** Closes all the PooledConnections in the the ConnectionPool. * The current behaviour is to first close all the used connections, * then close all the available connections. This method will also set * the state of the ConnectionPool to closed, caused any future calls * to getConnection to throw an SQLException. */ public void close() { if (closed) { return; } synchronized (mutex) { int size = usedConnections.size(); for (int i = 0; i < size; i++) { ManagedPooledConnection mPool = (ManagedPooledConnection) usedConnections.removeFirst(); mPool.pooledConn.removeConnectionEventListener(listManager); try { mPool.pooledConn.close(); } catch (SQLException e) { LOGGER.warning("Failed to close PooledConnection: " + e); } } size = availableConnections.size(); for (int i = 0; i < size; i++) { ManagedPooledConnection mPool = (ManagedPooledConnection) availableConnections.removeFirst(); mPool.pooledConn.removeConnectionEventListener(listManager); try { mPool.pooledConn.close(); } catch (SQLException e) { LOGGER.warning("Failed to close PooledConnection: " + e); } } closed = true; } } /** Checks whether the ConnectionPool has been closed. * * @return True if the connection pool is closed. If the Pool is closed * future calls to getConnection will throw an SQLException. */ public boolean isClosed() { return closed; } /** * A ConnectionEventListener for managing the list of connections in the pool. * * @author Sean Geoghegan, Defence Science and Technology Organisation * @author Chris Holmes * @version $Id$ */ private class ConnectionListManager implements ConnectionEventListener { /** * This is called when a logical connection is closed. The pooled connection is returned * to the list of available connections. * * @param event The Connection event. * * @see javax.sql.ConnectionEventListener#connectionClosed(javax.sql.ConnectionEvent) */ public void connectionClosed(ConnectionEvent event) { LOGGER.fine("Connection closed - adding to available connections."); PooledConnection conn = (PooledConnection) event.getSource(); synchronized (mutex) { ManagedPooledConnection mConn = getInUseManagedPooledConnection(conn); mConn.inUse = false; usedConnections.remove(mConn); availableConnections.addLast(mConn); } } /** * Called when an error occurs on the Connection. This removes the connection from the * pool. * * @param event The ConnectionEvent indicating an error. * * @see javax.sql.ConnectionEventListener#connectionErrorOccurred(javax.sql.ConnectionEvent) */ public void connectionErrorOccurred(ConnectionEvent event) { PooledConnection conn = (PooledConnection) event.getSource(); synchronized (mutex) { ManagedPooledConnection mConn = getInUseManagedPooledConnection(conn); conn.removeConnectionEventListener(this); try { conn.close(); } catch (SQLException e) { // don't need to do anything here, just log it LOGGER.log(Level.WARNING, "Error closing a connection", e); } usedConnections.remove(mConn); } } } /** * Runnable class that handles cleaning of invalid connections from the connection pool. A * connection is removed from the pool when its isValid method return false. The Constructor * for the ConnectionPoolCleaner has a parameter that defines how often the pool cleaner will * run. * * <p> * The Pool Cleaner has come to clean ze pool. * </p> * * @author Sean Geoghegan, Defence Science and Technology Organisation * @author Chris Holmes * @version $Id$ */ private class ConnectionPoolCleaner implements Runnable { /** Time to wait between cleaning */ private long waitTime; /** Run loop flag */ private boolean active = true; /** Creates a new ConnectionPoolCleaner * * @param waitTime The frequency of the cleaning. */ ConnectionPoolCleaner(long waitTime) { this.waitTime = waitTime; } /** Stops the Pool cleaner. * */ void disable() { active = false; } /** * Executes the Connection Pool Cleaning. */ public void run() { while (active) { synchronized (mutex) { for (Iterator iter = availableConnections.iterator(); iter.hasNext();) { ManagedPooledConnection conn = (ManagedPooledConnection) iter.next(); conn.pooledConn.removeConnectionEventListener(listManager); if (!conn.isValid()) { LOGGER.fine("Connection invalid, removing from pool"); try { conn.pooledConn.close(); } catch (SQLException e) { LOGGER.log(Level.WARNING, "Error closing dead connection", e); } iter.remove(); } else { // Connection is fine, add the list manager again. conn.pooledConn.addConnectionEventListener(listManager); } } } try { Thread.sleep(waitTime); } catch (InterruptedException e) { LOGGER.log(Level.WARNING, "Interrupted exception when wait in Pool Cleaner", e); } } } } }