All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openrdf.util.jdbc.ConnectionPool Maven / Gradle / Ivy

The newest version!
/*  Sesame - Storage and Querying architecture for RDF and RDF Schema
 *  Copyright (C) 2001-2006 Aduna
 *
 *  Contact:
 *  	Aduna
 *  	Prinses Julianaplein 14 b
 *  	3817 CS Amersfoort
 *  	The Netherlands
 *  	tel. +33 (0)33 465 99 87
 *  	fax. +33 (0)33 465 99 87
 *
 *  	http://aduna-software.com/
 *  	http://www.openrdf.org/
 *
 *  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; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  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.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.openrdf.util.jdbc;

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Savepoint;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class ConnectionPool {

/*--------------------------------------------------+
| Variables                                         |
+--------------------------------------------------*/

	protected List _connections;

	protected String _url;
	protected String _user;
	protected String _password;

	/**
	 * Indicates whether the ConnectionPool should check the status of
	 * connections (closed, has warnings) before they are returned.
	 **/
	protected boolean _checkConnections = true;

	protected long _cleaningInterval =  30 * 1000; // 30 seconds
	protected long _maxIdleTime = 30 * 1000; // 30 seconds

	protected long _maxUseTime = -1; // disabled by default

	protected boolean _draining = false;

	protected PoolCleaner _cleaner;

/*--------------------------------------------------+
| Constructors                                      |
+--------------------------------------------------*/

	public ConnectionPool(String url, String user, String password) {
		_url = url;
		_user = user;
		_password = password;

		_connections = new ArrayList();
	}

	/**
	 * Sets the flag that determines whether the the status of connections
	 * (closed, has warnings) is checked before they are returned by
	 * getConnection(). With some jdbc-drivers, the extra checks can have
	 * a large performance penalty. Default value is 'true'.
	 **/
	public void setCheckConnections(boolean checkConnections) {
		_checkConnections = checkConnections;
	}

	/**
	 * Sets the interval for the pool cleaner to come into action. The pool
	 * cleaner checks the connection pool every so many milliseconds for
	 * connections that should be removed. The default interval is 30 seconds.
	 * @param cleaningInterval The interval in milliseconds.
	 **/
	public void setCleaningInterval(long cleaningInterval) {
		_cleaningInterval = cleaningInterval;
	}

	/**
	 * Sets the maximum time that a connection is allowed to be idle. A
	 * connection that has been idle for a longer time will be removed
	 * by the pool cleaner the next time it check the pool. The default
	 * value is 30 seconds.
	 *
	 * @param maxIdleTime The maximum idle time in milliseconds.
	 **/
	public void setMaxIdleTime(long maxIdleTime) {
		_maxIdleTime = maxIdleTime;
	}

	/**
	 * Sets the maximum time that a connection is allowed to be used. A
	 * connection that has been used for a longer time will be forced to
	 * close itself, even if it is still in use. Normally, this time should
	 * only be reached in case an program "forgets" to close a connection.
	 * The maximum time is switched of by default.
	 *
	 * @param maxUseTime The maximum time a connection can be used in
	 * milliseconds, or a negative value if there is no maximum.
	 **/
	public void setMaxUseTime(long maxUseTime) {
		_maxUseTime = maxUseTime;
	}

/*--------------------------------------------------+
| Methods                                           |
+--------------------------------------------------*/

	public Connection getConnection()
		throws SQLException
	{
		if (_draining) {
			throw new SQLException("ConnectionPool was drained.");
		}

		// Try reusing an existing Connection
		synchronized (_connections) {
			PoolConnection pc = null;

			for (int i = 0; i < _connections.size(); i++) {
				pc = (PoolConnection)_connections.get(i);

				if (pc.lease()) {
					// PoolConnection is available

					if (!_checkConnections) {
						return pc;
					}
					else {
						// Check the status of the connection
						boolean isHealthy = true;

						try {
							if (pc.isClosed() && pc.getWarnings() != null) {
								// If something happend to the connection, we
								// don't want to use it anymore.
								isHealthy = false;
							}
						}
						catch(SQLException sqle) {
							// If we can't even ask for that information, we
							// certainly don't want to use it anymore.
							isHealthy = false;
						}

						if (isHealthy) {
							return pc;
						}
						else {
							try {
								pc.expire();
							}
							catch(SQLException sqle) {
								// ignore
							}
							_connections.remove(i);
						}
					}
				}
			}
		}

		// Create a new Connection
		Connection con = DriverManager.getConnection(_url, _user, _password);
		PoolConnection pc = new PoolConnection(con);
		pc.lease();

		// Add it to the pool
		synchronized (_connections) {
			_connections.add(pc);

			if (_cleaner == null) {
				// Put a new PoolCleaner to work
				_cleaner = new PoolCleaner(_cleaningInterval);
				_cleaner.start();
			}
		}

		return pc;
	}

	public void removeExpired() {
		PoolConnection pc;

		long maxIdleDeadline = System.currentTimeMillis() - _maxIdleTime;
		long maxUseDeadline = System.currentTimeMillis() - _maxUseTime;

		synchronized (_connections) {
			// Check all connections
			for (int i = _connections.size() - 1; i >= 0; i--) {
				pc = (PoolConnection)_connections.get(i);

				if (!pc.inUse() && pc.getTimeClosed() < maxIdleDeadline) {
					// Connection has been idle too long, close it.
					_connections.remove(i);
					try {
						pc.expire();
					}
					catch (SQLException ignore) {
					}
				}
				else if (
					_maxUseTime >= 0 && // don't check if disabled
					pc.inUse() &&
					pc.getTimeOpened() < maxUseDeadline)
				{
					// Connection has been used too long, close it.

					// Print the location where the connetion was acquired
					// as it probably forgot to close the connection (which
					// is a bug).
					System.err.println("Warning: forced closing of a connection that has been in use too long.");
					System.err.println("Connection was acquired in:");
					pc.printStackTrace();
					System.err.println();

					_connections.remove(i);
					try {
						pc.expire();
					}
					catch (SQLException ignore) {
					}
				}
			}

			// Stop the PoolCleaner if the pool is empty.
			if (_connections.size() == 0 && _cleaner != null) {
				_cleaner.halt();
				_cleaner = null;
			}
		}
	}

	public int getPoolSize() {
		synchronized (_connections) {
			return _connections.size();
		}
	}

	/**
	 * Drains the pool. After the ConnectionPool has been drained it will not
	 * give out any more connections and all existing connections will be
	 * closed. This action cannot be reversed, so a ConnectionPool will become
	 * unusable once it has been drained.
	 **/
	public void drain() {
		_draining = true;

		if (_cleaner != null) {
			_cleaner.halt();
		}

		synchronized (_connections) {
			for (int i = _connections.size() - 1; i >= 0; i--) {
				PoolConnection pc = (PoolConnection)_connections.get(i);

				if (pc.inUse()) {
					System.err.println("Warning: forced closing of a connection still in use.");
					System.err.println("Connection was acquired in:");
					pc.printStackTrace();
					System.err.println();
				}

				_connections.remove(i);
				try {
					pc.expire();
				}
				catch (SQLException ignore) {
				}
			}
		}
	}

	protected void finalize() {
		drain();
	}

/*--------------------------------------------+
| inner class PoolConnection                  |
+--------------------------------------------*/

	/**
	 * Wrapper around java.sql.Connection
	 **/
	static class PoolConnection implements Connection {

	/*----------------------------------+
	| Variables                         |
	+----------------------------------*/

		protected Connection _conn;

		protected boolean _inUse;

		protected boolean _autoCommit;

		/** Time stamp for the last time the connection was opened. **/
		protected long _timeOpened;

		/** Time stamp for the last time the connection was closed. **/
		protected long _timeClosed;

		private Throwable _throwable;

	/*----------------------------------+
	| Constructors                      |
	+----------------------------------*/

		public PoolConnection(Connection conn) {
			_conn = conn;
			_inUse = false;
			_autoCommit = true;
		}

	/*----------------------------------+
	| PoolConnection specific methods   |
	+----------------------------------*/

		/**
		 * Tries to lease this connection. If the attempt was successful (the
		 * connection was available), a flag will be set marking this connection
		 * "in use", and this method will return 'true'. If the connection was
		 * already in use, this method will return 'false'.
		 **/
		public synchronized boolean lease() {
			if (_inUse) {
				return false;
			}
			else {
				_inUse = true;
				_timeOpened = System.currentTimeMillis();
				return true;
			}
		}

		/**
		 * Checks if the connection currently is used by someone.
		 **/
		public boolean inUse() {
			return _inUse;
		}

		/**
		 * Returns the time stamp of the last time this connection was
		 * opened/leased.
		 **/
		public synchronized long getTimeOpened() {
			return _timeOpened;
		}

		/**
		 * Returns the time stamp of the last time this connection was
		 * closed.
		 **/
		public synchronized long getTimeClosed() {
			return _timeClosed;
		}

		/**
		 * Expires this connection and closes the underlying connection to the
		 * database. Once expired, a connection can no longer be used.
		 **/
		public void expire()
			throws SQLException
		{
			_conn.close();
			_conn = null;
		}

		public void printStackTrace() {
			_throwable.printStackTrace(System.err);
		}

	/*----------------------------------+
	| Wrapping methods for Connection   |
	+----------------------------------*/

		public synchronized void close()
			throws SQLException
		{
			// Multiple calls to close?
			if (_inUse) {
				_timeClosed = System.currentTimeMillis();
				_inUse = false;

				if (_autoCommit == false) {
					// autoCommit has been set to false by this user,
					// restore the default "autoCommit = true"
					setAutoCommit(true);
				}
			}
		}

		public Statement createStatement()
			throws SQLException
		{
			_throwable = new Throwable();
			return _conn.createStatement();
		}

		public PreparedStatement prepareStatement(String sql)
			throws SQLException
		{
			_throwable = new Throwable();
			return _conn.prepareStatement(sql);
		}

		public CallableStatement prepareCall(String sql)
			throws SQLException
		{
			return _conn.prepareCall(sql);
		}

		public String nativeSQL(String sql)
			throws SQLException
		{
			return _conn.nativeSQL(sql);
		}

		public void setAutoCommit(boolean autoCommit)
			throws SQLException
		{
			_conn.setAutoCommit(autoCommit);
			_autoCommit = _conn.getAutoCommit();
		}

		public boolean getAutoCommit()
			throws SQLException
		{
			return _conn.getAutoCommit();
		}

		public void commit()
			throws SQLException
		{
			_conn.commit();
		}

		public void rollback()
			throws SQLException
		{
			_conn.rollback();
		}

		public boolean isClosed()
			throws SQLException
		{
			return _conn.isClosed();
		}

		public DatabaseMetaData getMetaData()
			throws SQLException
		{
			return _conn.getMetaData();
		}

		public void setReadOnly(boolean readOnly)
			throws SQLException
		{
			_conn.setReadOnly(readOnly);
		}

		public boolean isReadOnly()
			throws SQLException
		{
			return _conn.isReadOnly();
		}

		public void setCatalog(String catalog)
			throws SQLException
		{
			_conn.setCatalog(catalog);
		}

		public String getCatalog()
			throws SQLException
		{
			return _conn.getCatalog();
		}

		public void setTransactionIsolation(int level)
			throws SQLException
		{
			_conn.setTransactionIsolation(level);
		}

		public int getTransactionIsolation()
			throws SQLException
		{
			return _conn.getTransactionIsolation();
		}

		public SQLWarning getWarnings()
			throws SQLException
		{
			return _conn.getWarnings();
		}

		public void clearWarnings()
			throws SQLException
		{
			_conn.clearWarnings();
		}

		public Statement createStatement(
			int resultSetType, int resultSetConcurrency)
			throws SQLException
		{
			return _conn.createStatement(resultSetType, resultSetConcurrency);
		}

		public PreparedStatement prepareStatement(
			String sql, int resultSetType, int resultSetConcurrency)
			throws SQLException
		{
			return _conn.prepareStatement(sql, resultSetType, resultSetConcurrency);
		}

		public CallableStatement prepareCall(
			String sql, int resultSetType, int resultSetConcurrency)
			throws SQLException
		{
			return _conn.prepareCall(sql, resultSetType, resultSetConcurrency);
		}

		public Map getTypeMap()
			throws SQLException
		{
			return _conn.getTypeMap();
		}

		public void setTypeMap(Map map)
			throws SQLException
		{
			_conn.setTypeMap(map);
		}

/*
 * The following methods are new methods from java.sql.Connection that
 * were added in JDK1.4. These additions are incompatible with older JDK
 * versions.
 */

		public void setHoldability(int holdability)
			throws SQLException
		{
			_conn.setHoldability(holdability);
		}

		public int getHoldability()
			throws SQLException
		{
			return _conn.getHoldability();
		}

		public Savepoint setSavepoint()
			throws SQLException
		{
			return _conn.setSavepoint();
		}

		public Savepoint setSavepoint(String name)
			throws SQLException
		{
			return _conn.setSavepoint(name);
		}

		public void rollback(Savepoint savepoint)
			throws SQLException
		{
			_conn.rollback(savepoint);
		}

		public void releaseSavepoint(Savepoint savepoint)
			throws SQLException
		{
			_conn.releaseSavepoint(savepoint);
		}

		public Statement createStatement(
				int resultSetType,
				int resultSetConcurrency,
				int resultSetHoldability)
			throws SQLException
		{
			return _conn.createStatement(
				resultSetType, resultSetConcurrency, resultSetHoldability);
		}

		public PreparedStatement prepareStatement(
				String sql,
				int resultSetType,
				int resultSetConcurrency,
				int resultSetHoldability)
			throws SQLException
		{
			return _conn.prepareStatement(
				sql, resultSetType, resultSetConcurrency, resultSetHoldability);
		}

		public CallableStatement prepareCall(
				String sql,
				int resultSetType,
				int resultSetConcurrency,
				int resultSetHoldability)
			throws SQLException
		{
			return _conn.prepareCall(
				sql, resultSetType, resultSetConcurrency, resultSetHoldability);
		}

		public PreparedStatement prepareStatement(
				String sql, int autoGenerateKeys)
			throws SQLException
		{
			return _conn.prepareStatement(sql, autoGenerateKeys);
		}

		public PreparedStatement prepareStatement(
				String sql, int[] columnIndexes)
			throws SQLException
		{
			return _conn.prepareStatement(sql, columnIndexes);
		}

		public PreparedStatement prepareStatement(
				String sql, String[] columnNames)
			throws SQLException
		{
			return _conn.prepareStatement(sql, columnNames);
		}
	}

/*--------------------------------------------+
| inner class PoolCleaner                     |
+--------------------------------------------*/

	class PoolCleaner extends Thread {

		protected long _cleaningInterval;
		protected boolean _mustStop;

		public PoolCleaner(long cleaningInterval) {
			if (cleaningInterval < 0) {
				throw new IllegalArgumentException("cleaningInterval must be >= 0");
			}
			_mustStop = false;
			_cleaningInterval = cleaningInterval;

			setDaemon(true);
		}

		public void run() {
			while (!_mustStop) {
				try {
					sleep(_cleaningInterval);
				}
				catch (InterruptedException ignore) {
				}

				if (_mustStop) {
					break;
				}

				removeExpired();
			}
		}

		public void halt() {
			_mustStop = true;
			synchronized (this) {
				this.interrupt();
			}
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy