blob: 8376839ab3513599fe3a80c6e7e45a70abc8c455 [file] [log] [blame]
/* Copyright (c) 2001-2010, The HSQL Development Group
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of the HSQL Development Group nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.hsqldb.jdbc.pool;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import javax.sql.ConnectionEvent;
import javax.sql.ConnectionEventListener;
import javax.sql.PooledConnection;
import org.hsqldb.jdbc.Util;
/* $Id: ManagedPoolDataSource.java 3481 2010-02-26 18:05:06Z fredt $ */
// boucherb@users 20051207 - patch 1.8.0.x initial JDBC 4.0 support work
// boucherb@users 20060523 - patch 1.9.0 full synch up to Mustang Build 84
// Revision 1.23 2006/07/12 12:45:54 boucherb
// patch 1.9.0
// - full synch up to Mustang b90
/** @todo configurable: rollback() exceptions passed on to the client? */
/**
* Connection pool manager.
*
* @author Jakob Jenkov
*/
public class ManagedPoolDataSource implements javax.sql.DataSource,
ConnectionEventListener {
/**
* The default connection count in the pool.
* Since HSQLDB has database level locking (only 1 thread in the database at a time) there is
* no reason to default to a large number of connections in the connection pool. All
* other threads will be waiting for the other connections to finish anyways. A large number
* of connections will only be a waste of resources in the HSQLDB server.
*
* I'm raising the default max size because
* (a) HSQLDB will have a multi-threaded core shortly.
* (b) The number of connections is not determined only by performance
* considerations, but also by transaction isolation requirements.
* Web apps often require a separate conn. app per HTTP Session or
* Request. It is, indeed, the standard paradigm for J2EE web apps.
* - blaine
*/
private static final int DEFAULT_MAX_POOL_SIZE = 8;
private boolean isPoolClosed = false;
private int sessionTimeout = 0;
private JDBCConnectionPoolDataSource connectionPoolDataSource = null;
private Set connectionsInUse = new HashSet();
private List connectionsInactive = new ArrayList();
private Map sessionConnectionWrappers = new HashMap();
private int maxPoolSize = DEFAULT_MAX_POOL_SIZE;
private ConnectionDefaults connectionDefaults = null;
private boolean initialized = false;
private int initialSize = 0;
// The doReset* settings say whether to automatically apply the
// relevant setting to physical Connections when they are (re)-established.
boolean doResetAutoCommit = false;
boolean doResetReadOnly = false;
boolean doResetTransactionIsolation = false;
boolean doResetCatalog = false;
// The default values below will only be enforced if the user "checks"
// the values by using a getter().
// If user uses neither a getter nor a setter, no resetting or
// enforcement of these settings will be done.
boolean isAutoCommit = true;
boolean isReadOnly = false;
int transactionIsolation = Connection.TRANSACTION_READ_COMMITTED;
String catalog = null;
/** Optional query to validate new Connections before returning them. */
private String validationQuery = null;
/**
* Base no-arg constructor.
* Useful for JavaBean repositories and IOC frameworks.
*/
public ManagedPoolDataSource() {
this.connectionPoolDataSource = new JDBCConnectionPoolDataSource();
}
/**
* Base constructor that handles all parameters.
*/
public ManagedPoolDataSource(
String url, String user, String password, int maxPoolSize,
ConnectionDefaults connectionDefaults) throws SQLException {
this.connectionPoolDataSource = new JDBCConnectionPoolDataSource(url,
user, password, connectionDefaults);
this.maxPoolSize = maxPoolSize;
}
/**
* Convience constructor wrapper.
*
* @see #ManagedPoolDataSource()
*/
public ManagedPoolDataSource(String url, String user,
String password) throws SQLException {
this(url, user, password, DEFAULT_MAX_POOL_SIZE, null);
}
/**
* Convience constructor wrapper.
*
* @see #ManagedPoolDataSource()
*/
public ManagedPoolDataSource(
String url, String user, String password,
ConnectionDefaults connectionDefaults) throws SQLException {
this(url, user, password, DEFAULT_MAX_POOL_SIZE, connectionDefaults);
}
/**
* Convience constructor wrapper.
*
* @see #ManagedPoolDataSource()
*/
public ManagedPoolDataSource(String url, String user, String password,
int maxPoolSize) throws SQLException {
this(url, user, password, maxPoolSize, null);
}
public ConnectionDefaults getConnectionDefaults() {
return connectionDefaults;
}
public synchronized String getUrl() {
return this.connectionPoolDataSource.getUrl();
}
public synchronized void setUrl(String url) {
this.connectionPoolDataSource.setUrl(url);
}
public synchronized String getUser() {
return this.connectionPoolDataSource.getUser();
}
public synchronized void setUser(String user) {
this.connectionPoolDataSource.setUser(user);
}
public synchronized String getPassword() {
return this.connectionPoolDataSource.getPassword();
}
public synchronized void setPassword(String password) {
this.connectionPoolDataSource.setPassword(password);
}
public synchronized int getSessionTimeout() {
return sessionTimeout;
}
public synchronized void setSessionTimeout(int sessionTimeout) {
this.sessionTimeout = sessionTimeout;
}
public synchronized int getMaxPoolSize() {
return maxPoolSize;
}
public synchronized void setMaxPoolSize(int maxPoolSize) {
this.maxPoolSize = maxPoolSize;
}
/**
* @return seconds Time, in seconds.
* @see JDBCConnectionPoolDataSource#getLoginTimeout()
*/
public synchronized int getLoginTimeout() throws SQLException {
return connectionPoolDataSource.getLoginTimeout();
}
/**
* @param seconds Time, in seconds.
* @see JDBCConnectionPoolDataSource#setLoginTimeout(int)
*/
public synchronized void setLoginTimeout(int seconds) throws SQLException {
connectionPoolDataSource.setLoginTimeout(seconds);
}
/**
* @see JDBCConnectionPoolDataSource#getLogWriter()
*/
public synchronized PrintWriter getLogWriter() throws SQLException {
return connectionPoolDataSource.getLogWriter();
}
/**
* @see JDBCConnectionPoolDataSource#setLogWriter(PrintWriter)
*/
public synchronized void setLogWriter(
PrintWriter out) throws SQLException {
connectionPoolDataSource.setLogWriter(out);
}
/**
* Performs a getConnection() after validating the given username
* and password.
*
* @param user String which must match the 'user' configured for this
* ManagedPoolDataSource.
* @param password String which must match the 'password' configured
* for this ManagedPoolDataSource.
*
* @see #getConnection()
*/
public Connection getConnection(String user,
String password) throws SQLException {
String managedPassword = getPassword();
String managedUser = getUsername();
if (((user == null && managedUser != null)
|| (user != null && managedUser == null)) || (user != null
&& !user.equals(managedUser)) || ((password == null
&& managedPassword != null) || (password != null
&& managedPassword == null)) || (password != null
&& !password.equals(managedPassword))) {
throw new SQLException(
"Connection pool manager user/password validation failed");
}
return getConnection();
}
/**
* This method will return a valid connection as fast as possible. Here is the sequence of events:
*
* <ol>
* <li>First it will try to take a ready connection from the pool.</li>
* <li>If the pool doesn't have any ready connections this method will check
* if the pool has space for new connections. If yes, a new connection is created and inserted
* into the pool.</li>
* <li>If the pool is already at maximum size this method will check for abandoned
* connections among the connections given out earlier.
* If any of the connections are abandoned it will be reclaimed, reset and
* given out.</li>
* <li>If there are no abandoned connections this method waits until a connection becomes
* available in the pool, or until the login time out time has passed (if login timeout set).</li>
* </ol>
*
* This sequence means that the pool will not grow till max size as long as there are available connections
* in the pool. Only when no connections are available will it spend time creating new connections.
* In addition it will not spend
* time reclaiming abandoned connections until it's absolutely necessary. Only when no new connections
* can be created. Ultimately it only blocks if there really are no available connections,
* none can be created and none can be reclaimed. Since the
* pool is passive this is the sequence of events that should give the highest performance possible
* for giving out connections from the pool.
*
* <br/><br/>
* Perhaps it is faster to reclaim a connection if possible than to create a new one. If so, it would be faster
* to reclaim existing connections before creating new ones. It may also preserve resources better.
* However, assuming that there will not often be abandoned connections, that programs most often
* remember to close them, on the average the reclaim check is only going to be "wasted overhead".
* It will only rarely result in a usable connection. In that perspective it should be faster
* to only do the reclaim check when no connections can be created.
*
* @throws SQLException
*/
public Connection getConnection() throws SQLException {
PooledConnection pooledConnection = null;
synchronized (this) {
if (!initialized) {
if (initialSize > maxPoolSize) {
throw new SQLException("Initial size of " + initialSize
+ " exceeds max. pool size of " + maxPoolSize);
}
logInfo("Pre-initializing " + initialSize
+ " physical connections");
for (int i = 0; i < initialSize; i++) {
connectionsInactive.add(createNewConnection());
}
initialized = true;
}
long loginTimeoutExpiration = calculateLoginTimeoutExpiration();
//each waiting thread will spin around in this loop until either
//
// a) a connection becomes available
// b) the login timeout passes. Results in SQLException.
// c) the thread is interrupted while waiting for an available connection
// d) the connection pool is closed. Results in SQLException.
//
// the reason the threads spin in a loop is that they should always check all options
// (available + space for new + abandoned) before waiting. In addition the threads will
// repeat the close check before spinning a second time. That way all threads waiting
// for a connection when the connection is closed will get an SQLException.
while (pooledConnection == null) {
if (this.isPoolClosed) {
throw new SQLException(
"The pool is closed. You cannot get anymore connections from it.");
}
// 1) Check if any connections available in the pool.
pooledConnection = dequeueFirstIfAny();
if (pooledConnection != null) {
return wrapConnectionAndMarkAsInUse(pooledConnection);
}
// 2) Check if the pool has space for new connections and create one if it does.
if (poolHasSpaceForNewConnections()) {
pooledConnection = createNewConnection();
return wrapConnectionAndMarkAsInUse(pooledConnection);
}
// 3) Check if connection pool has session timeout. If yes,
// check if any connections has timed out. Return one if there are any.
if (this.sessionTimeout > 0) {
reclaimAbandonedConnections();
//check if any connections were closed during the time out check.
pooledConnection = dequeueFirstIfAny();
if (pooledConnection != null) {
return wrapConnectionAndMarkAsInUse(pooledConnection);
}
}
// 4) wait until a connection becomes available or the login timeout passes (if set).
doWait(loginTimeoutExpiration);
}
return wrapConnectionAndMarkAsInUse(pooledConnection);
}
}
//------------------------- JDBC 4.0 -----------------------------------
/**
* Creates a concrete implementation of a Query interface using the JDBC drivers <code>QueryObjectGenerator</code>
* implementation.
* If the JDBC driver does not provide its own <code>QueryObjectGenerator</code>, the <code>QueryObjectGenerator</code>
* provided with J2SE will be used.
* <p>
* @param ifc The Query interface that will be created
* @return A concrete implementation of a Query interface
* @exception SQLException if a database access error occurs.
* @since JDK 1.6, HSQLDB 1.8.x
*/
//#ifdef JAVA6BETA
/*
public <T extends BaseQuery> T createQueryObject(Class<T> ifc) throws SQLException {
return QueryObjectFactory.createDefaultQueryObject(ifc, this);
}
*/
//#endif JAVA6BETA
/**
* Creates a concrete implementation of a Query interface using the JDBC drivers <code>QueryObjectGenerator</code>
* implementation.
* <p>
* If the JDBC driver does not provide its own <code>QueryObjectGenerator</code>, the <code>QueryObjectGenerator</code>
* provided with Java SE will be used.
* <p>
* This method is primarly for developers of Wrappers to JDBC implementations.
* Application developers should use <code>createQueryObject(Class&LT;T&GT; ifc).
* <p>
* @param ifc The Query interface that will be created
* @param ds The <code>DataSource</code> that will be used when invoking methods that access
* the data source. The QueryObjectGenerator implementation will use
* this <code>DataSource</code> without any unwrapping or modications
* to create connections to the data source.
*
* @return An concrete implementation of a Query interface
* @exception SQLException if a database access error occurs.
* @since 1.6
*/
//#ifdef JAVA6BETA
/*
public <T extends BaseQuery> T createQueryObject(Class<T> ifc, javax.sql.DataSource ds) throws SQLException {
return QueryObjectFactory.createQueryObject(ifc, ds);
}
*/
//#endif JAVA6BETA
/**
* Retrieves the QueryObjectGenerator for the given JDBC driver. If the
* JDBC driver does not provide its own QueryObjectGenerator, NULL is
* returned.
* @return The QueryObjectGenerator for this JDBC Driver or NULL if the driver does not provide its own
* implementation
* @exception SQLException if a database access error occurs
* @since JDK 1.6, HSQLDB 1.8.x
*/
//#ifdef JAVA6BETA
/*
public QueryObjectGenerator getQueryObjectGenerator() throws SQLException {
return null;
}
*/
//#endif JAVA6BETA
/**
* Returns an object that implements the given interface to allow access to non-standard methods,
* or standard methods not exposed by the proxy.
* The result may be either the object found to implement the interface or a proxy for that object.
* If the receiver implements the interface then that is the object. If the receiver is a wrapper
* and the wrapped object implements the interface then that is the object. Otherwise the object is
* the result of calling <code>unwrap</code> recursively on the wrapped object. If the receiver is not a
* wrapper and does not implement the interface, then an <code>SQLException</code> is thrown.
*
* @param iface A Class defining an interface that the result must implement.
* @return an object that implements the interface. May be a proxy for the actual implementing object.
* @throws java.sql.SQLException If no object found that implements the interface
* @since JDK 1.6, HSQLDB 1.8.x
*/
//#ifdef JAVA6
public <T>T unwrap(java.lang.Class<T> iface) throws java.sql.SQLException {
if (isWrapperFor(iface)) {
return (T) this;
}
throw Util.invalidArgument("iface: " + iface);
}
//#endif JAVA6
/**
* Returns true if this either implements the interface argument or is directly or indirectly a wrapper
* for an object that does. Returns false otherwise. If this implements the interface then return true,
* else if this is a wrapper then return the result of recursively calling <code>isWrapperFor</code> on the wrapped
* object. If this does not implement the interface and is not a wrapper, return false.
* This method should be implemented as a low-cost operation compared to <code>unwrap</code> so that
* callers can use this method to avoid expensive <code>unwrap</code> calls that may fail. If this method
* returns true then calling <code>unwrap</code> with the same argument should succeed.
*
* @param iface a Class defining an interface.
* @return true if this implements the interface or directly or indirectly wraps an object that does.
* @throws java.sql.SQLException if an error occurs while determining whether this is a wrapper
* for an object with the given interface.
* @since JDK 1.6, HSQLDB 1.8.x
*/
//#ifdef JAVA6
public boolean isWrapperFor(
java.lang.Class<?> iface) throws java.sql.SQLException {
return (iface != null && iface.isAssignableFrom(this.getClass()));
}
//#endif JAVA6
// ------------------------ internal implementation ------------------------
private void doWait(long loginTimeoutExpiration) throws SQLException {
try {
if (loginTimeoutExpiration > 0) {
long timeToWait = loginTimeoutExpiration
- System.currentTimeMillis();
if (timeToWait > 0) {
this.wait(timeToWait);
} else {
throw new SQLException(
"No connections available within the given login timeout: "
+ getLoginTimeout());
}
} else {
this.wait();
}
} catch (InterruptedException e) {
throw new SQLException(
"Thread was interrupted while waiting for available connection");
}
}
private PooledConnection createNewConnection() throws SQLException {
PooledConnection pooledConnection;
// I have changed "size() + 1" to "size()". I don't know why
// we would want to report 1 more than the actual pool size,
// so I am assuming that this is a coding error. (The size
// method does return the actual size of an array). -blaine
logInfo("Connection created since no connections available and "
+ "pool has space for more connections. Pool size: " + size());
pooledConnection = this.connectionPoolDataSource.getPooledConnection();
pooledConnection.addConnectionEventListener(this);
return pooledConnection;
}
private void reclaimAbandonedConnections() {
long now = System.currentTimeMillis();
long sessionTimeoutMillis = ((long) sessionTimeout) * 1000L;
Iterator iterator = this.connectionsInUse.iterator();
List abandonedConnections = new ArrayList();
while (iterator.hasNext()) {
PooledConnection connectionInUse =
(PooledConnection) iterator.next();
SessionConnectionWrapper sessionWrapper =
(SessionConnectionWrapper) this.sessionConnectionWrappers.get(
connectionInUse);
if (isSessionTimedOut(now, sessionWrapper, sessionTimeoutMillis)) {
abandonedConnections.add(sessionWrapper);
}
}
//The timed out sessions are added to a list before being closed to avoid
//ConcurrentModificationException. When the session wrapper is closed the underlying
// connection will eventually call connectionClosed() on this class. connectionClosed()
// will in turn try to remove the pooled connection from the connectionsInUse set. So,
// if the sessionWrapper is closed while iterating the connectionsInUse it would result
// in a ConcurrentModificationException.
iterator = abandonedConnections.iterator();
while (iterator.hasNext()) {
SessionConnectionWrapper sessionWrapper =
(SessionConnectionWrapper) iterator.next();
closeSessionWrapper(
sessionWrapper,
"Error closing abandoned session connection wrapper.");
}
//if there are more than one abandoned connection then other waiting threads might
//now have a chance to get a connections. therefore we notify all waiting threads.
if (abandonedConnections.size() > 1) {
abandonedConnections.clear();
this.notifyAll();
}
}
/**
* Closes the session wrapper. The call to close() will in turn result in a call to this pools
* connectionClosed or connectionErrorOccurred method. These methods will remove the PooledConnection
* and SessionConnectionWrapper instances from the connectionsInUse and sessionConnectionWrappers collections,
* and do any necessary cleanup afterwards. That is why this method only calls sessionWrapper.close();
* @param sessionWrapper The session wrapper to close.
* @param logText The text to write to the log if the close fails.
*/
private void closeSessionWrapper(SessionConnectionWrapper sessionWrapper,
String logText) {
try {
sessionWrapper.close();
} catch (SQLException e) {
//ignore exception. The connection will automatically be removed from the pool.
logInfo(logText, e);
}
}
private long calculateLoginTimeoutExpiration() throws SQLException {
long loginTimeoutExpiration = 0;
if (getLoginTimeout() > 0) {
loginTimeoutExpiration = 1000L * ((long) getLoginTimeout());
}
return loginTimeoutExpiration;
}
private void enqueue(PooledConnection connection) {
this.connectionsInactive.add(connection);
this.notifyAll();
}
/**
* Dequeues first available connection if any. If no available connections it returns null.
* @return The first available connection if any. Null if no connections are available.
*/
private PooledConnection dequeueFirstIfAny() {
if (this.connectionsInactive.size() <= 0) {
return null;
}
return (PooledConnection) this.connectionsInactive.remove(0);
}
public synchronized int size() {
return this.connectionsInUse.size() + this.connectionsInactive.size();
}
private Connection wrapConnectionAndMarkAsInUse(
PooledConnection pooledConnection) throws SQLException {
pooledConnection = assureValidConnection(pooledConnection);
Connection conn = pooledConnection.getConnection();
if (doResetAutoCommit) {
conn.setAutoCommit(isAutoCommit);
}
if (doResetReadOnly) {
conn.setReadOnly(isReadOnly);
}
if (doResetTransactionIsolation) {
conn.setTransactionIsolation(transactionIsolation);
/* TESING ONLY!!
System.err.println("<<<<<<<<< ISO LVL => " + transactionIsolation
+ " >>>>>>>>>>>>");
*/
}
if (doResetCatalog) {
conn.setCatalog(catalog);
/* TESTING ONLY!
System.err.println("<<<<<<<<< CAT => " + catalog
+ " >>>>>>>>>>>>");
*/
}
if (validationQuery != null) {
// End-to-end test before return the Connection.
java.sql.ResultSet rs = null;
try {
rs = conn.createStatement().executeQuery(validationQuery);
if (!rs.next()) {
throw new SQLException("0 rows returned");
}
} catch (SQLException se) {
closePhysically(pooledConnection,
"Closing non-validating pooledConnection.");
throw new SQLException("Validation query failed: "
+ se.getMessage());
} finally {
if (rs != null) {
rs.close();
}
}
}
this.connectionsInUse.add(pooledConnection);
SessionConnectionWrapper sessionWrapper =
new SessionConnectionWrapper(pooledConnection.getConnection());
this.sessionConnectionWrappers.put(pooledConnection, sessionWrapper);
return sessionWrapper;
}
private PooledConnection assureValidConnection(
PooledConnection pooledConnection) throws SQLException {
if (isInvalid(pooledConnection)) {
closePhysically(pooledConnection,
"closing invalid pooledConnection.");
return this.connectionPoolDataSource.getPooledConnection();
}
return pooledConnection;
}
private boolean isInvalid(PooledConnection pooledConnection) {
/** @todo: add || pooledConnection.getConnection.isValid() when JDBC 4.0 arrives. */
try {
return pooledConnection.getConnection().isClosed();
} catch (SQLException e) {
logInfo(
"Error calling pooledConnection.getConnection().isClosed(). Connection will be removed from pool.",
e);
return false;
}
}
private boolean isSessionTimedOut(long now,
SessionConnectionWrapper sessionWrapper,
long sessionTimeoutMillis) {
return now - sessionWrapper.getLatestActivityTime()
>= sessionTimeoutMillis;
}
/**
* Tells if a connection has space for new connections
* (fx. if a connection leaked from the pool - not properly returned),
* by comparing the total number of
* connections supposed to be in the pool with the number of connections currently
* in the pool + the number of connections currently being used.
* @return True if the number of connections supposed to be in the pool is higher
* than the number of connections in the pool + the number of connections currently
* in use. False if not.
*/
private boolean poolHasSpaceForNewConnections() {
return this.maxPoolSize > size();
}
public synchronized void connectionClosed(ConnectionEvent event) {
PooledConnection connection = (PooledConnection) event.getSource();
this.connectionsInUse.remove(connection);
this.sessionConnectionWrappers.remove(connection);
if (!this.isPoolClosed) {
enqueue(connection);
logInfo("Connection returned to pool.");
} else {
closePhysically(connection, "closing returned connection.");
logInfo(
"Connection returned to pool was closed because pool is closed.");
this.notifyAll(); //notifies evt. threads waiting for connection or for the pool to close.
}
}
/**
*
* A fatal error has occurred and the connection cannot be used anymore.
* A close event from such a connection should be ignored. The connection should not be reused.
* A new connection will be created to replace the invalid connection, when the next client
* calls getConnection().
*/
public synchronized void connectionErrorOccurred(ConnectionEvent event) {
PooledConnection connection = (PooledConnection) event.getSource();
connection.removeConnectionEventListener(this);
this.connectionsInUse.remove(connection);
this.sessionConnectionWrappers.remove(connection);
logInfo(
"Fatal exception occurred on pooled connection. Connection is removed from pool: ");
logInfo(event.getSQLException());
closePhysically(connection, "closing invalid, removed connection.");
//notify threads waiting for connections or for the pool to close.
//one waiting thread can now create a new connection since the pool has space for a new connection.
//if a thread waits for the pool to close this could be the last unclosed connection in the pool.
this.notifyAll();
}
/**
* Closes this connection pool. No further connections can be obtained from it after this.
* All inactive connections are physically closed before the call returns.
* Active connections are not closed.
* There may still be active connections in use after this method returns.
* When these connections are closed and returned to the pool they will be
* physically closed.
*/
public synchronized void close() {
this.isPoolClosed = true;
while (this.connectionsInactive.size() > 0) {
PooledConnection connection = dequeueFirstIfAny();
if (connection != null) {
closePhysically(
connection,
"closing inactive connection when connection pool was closed.");
}
}
}
/**
* Closes this connection pool. All inactive connections in the pool are closed physically immediatedly.
* Waits until all active connections are returned to the pool and closed physically before returning.
* @throws InterruptedException If the thread waiting is interrupted before all connections are returned
* to the pool and closed.
*/
public synchronized void closeAndWait() throws InterruptedException {
close();
while (size() > 0) {
this.wait();
}
}
/**
* Closes this connection
*/
public synchronized void closeImmediatedly() {
close();
Iterator iterator = this.connectionsInUse.iterator();
while (iterator.hasNext()) {
PooledConnection connection = (PooledConnection) iterator.next();
SessionConnectionWrapper sessionWrapper =
(SessionConnectionWrapper) this.sessionConnectionWrappers.get(
connection);
closeSessionWrapper(
sessionWrapper,
"Error closing session wrapper. Connection pool was shutdown immediatedly.");
}
}
private void closePhysically(PooledConnection source, String logText) {
try {
source.close();
} catch (SQLException e) {
logInfo("Error " + logText, e);
}
}
/**
* @see JDBCConnectionPoolDataSource#logInfo(String)
*/
private void logInfo(String message) {
/* For external unit tests, temporarily change visibility to public.*/
connectionPoolDataSource.logInfo(message);
}
/**
* @see JDBCConnectionPoolDataSource#logInfo(Throwable)
*/
private void logInfo(Throwable t) {
/* For external unit tests, temporarily change visibility to public.*/
connectionPoolDataSource.logInfo(t);
}
/**
* @see JDBCConnectionPoolDataSource#logInfo(String, Throwable)
*/
private void logInfo(String message, Throwable t) {
/* For external unit tests, temporarily change visibility to public.*/
connectionPoolDataSource.logInfo(message, t);
}
/**
* Sets auto-commit mode for every new connection that we provide.
*
* This is very useful to enforce desired JDBC environments when
* using containers (app servers being the most popular).
*/
public void setDefaultAutoCommit(boolean defaultAutoCommit) {
isAutoCommit = defaultAutoCommit;
doResetAutoCommit = true;
}
/**
* Sets read-only mode for every new connection that we provide
*
* This is an easy way to ensure a safe test environment.
* By making one container setting, you can be sure that the
* DB will not be updated.
*/
public void setDefaultReadOnly(boolean defaultReadOnly) {
isReadOnly = defaultReadOnly;
doResetReadOnly = true;
}
/**
* Sets transaction level for every new connection that we provide.
*
* For portability purposes, this has no effect on HSQLDB right now.
* We anticipate this working intuitively for release 1.9.0 of HSQLDB.
*/
public void setDefaultTransactionIsolation(
int defaultTransactionIsolation) {
transactionIsolation = defaultTransactionIsolation;
doResetTransactionIsolation = true;
}
/**
* Sets catalog for every new connection that we provide.
*
* For portability purposes, this has no effect on HSQLDB right now.
* Don't know yet when HSQLDB will have catalog-switching implemented.
*/
public void setDefaultCatalog(String defaultCatalog) {
catalog = defaultCatalog;
doResetCatalog = true;
}
/**
* @see #setDefaultAutoCommit(boolean)
*/
public boolean getDefaultAutoCommit() {
doResetAutoCommit = true;
return isAutoCommit;
}
/**
* @see #setDefaultCatalog(String)
*/
public String getDefaultCatalog() {
doResetCatalog = true;
return catalog;
}
/**
* @see #setDefaultReadOnly(boolean)
*/
public boolean getDefaultReadOnly() {
doResetReadOnly = true;
return isReadOnly;
}
/**
* @see #setDefaultTransactionIsolation(int)
*/
public int getDefaultTransactionIsolation() {
doResetTransactionIsolation = true;
return transactionIsolation;
}
/**
* For compatibility.
*
* @param driverClassName must be the main JDBC driver class name.
*/
public void setDriverClassName(String driverClassName) {
if (driverClassName.equals(JDBCConnectionPoolDataSource.driver)) {
return;
}
/** @todo: Use a HSQLDB RuntimeException subclass */
throw new RuntimeException("This class only supports JDBC driver '"
+ JDBCConnectionPoolDataSource.driver
+ "'");
}
/**
* For compatibility.
*
* @see #setDriverClassName(String)
*/
public String getDriverClassName() {
return JDBCConnectionPoolDataSource.driver;
}
/**
* Call this method to pre-initialize some physical connections.
*
* You must call this method before your first getConnection() call,
* or there will be no effect.
* N.b. that regardless of the initialSize, no physical connections
* will be established until the first call to getConnection().
*
* @param initialSize Pre-initialize this number of physical
* connections upon first call to getConnection().
* The default is 0, which means, no pre-allocation.
* @see #getConnection()
*/
public void setInitialSize(int initialSize) {
this.initialSize = initialSize;
}
/**
* This wrapper is to conform with section 11.7 of the JDBC 3.0 Spec.
*
* @see #setInitialSize(int)
*/
public int getInitialPoolSize() {
return getInitialSize();
}
/**
* This wrapper is to conform with section 11.7 of the JDBC 3.0 Spec.
*
* @see #getInitialSize()
*/
public void setInitialPoolSize(int initialSize) {
setInitialSize(initialSize);
}
/**
* @see #setInitialPoolSize(int)
*/
public int getInitialSize() {
return initialSize;
}
/**
* @todo: Implement
* public void setMaxIdle(int maxIdle) {
* }
* public int getMaxIdle() {
* return maxIdle;
* }
*/
/**
* @todo: Implement
* public void setMaxWait(long maxWait) {
* }
*/
/**
* @todo: Implement
* public void setMinIdle(int minIdle) {
* }
*/
/**
* The current number of active connections that have been allocated from
* this data source
*/
public int getNumActive() {
return connectionsInUse.size();
}
/**
* The current number of idle connections that are waiting to be allocated
* from this data source
*/
public int getNumIdle() {
return connectionsInactive.size();
}
/**
* Wrapper.
*
* @see #setUser(String)
*/
public void setUsername(String username) {
setUser(username);
}
/**
* Wrapper.
*
* @see #getUser()
*/
public String getUsername() {
return getUser();
}
/**
* Wrapper.
*
* @see #setMaxPoolSize(int)
*/
public void setMaxActive(int maxActive) {
setMaxPoolSize(maxActive);
}
/**
* Wrapper.
*
* @see #getMaxPoolSize()
*/
public int getMaxActive() {
return getMaxPoolSize();
}
/**
* Set a query that always returns at least one row.
*
* This is a standard and important connection pool manager feature.
* This is used to perform end-to-end validation before returning
* a Connection to a user.
*/
public void setValidationQuery(String validationQuery) {
this.validationQuery = validationQuery;
}
/**
* @see #setValidationQuery(String)
*/
public String getValidationQuery() {
return validationQuery;
}
/**
* Sets JDBC Connection Properties to be used when physical
* connections are obtained for the pool.
*/
public void addConnectionProperty(String name, String value) {
this.connectionPoolDataSource.setConnectionProperty(name, value);
}
/**
* Removes JDBC Connection Properties.
*
* @see #addConnectionProperty(String, String)
*/
public void removeConnectionProperty(String name) {
this.connectionPoolDataSource.removeConnectionProperty(name);
}
/**
* @see #addConnectionProperty(String, String)
*/
public Properties getConnectionProperties() {
return this.connectionPoolDataSource.getConnectionProperties();
}
/**
* Dumps current state of this Manager.
*/
public String toString() throws RuntimeException {
int timeout = 0;
try {
timeout = getLoginTimeout();
} catch (SQLException se) {
throw new RuntimeException(
"Failed to retrieve the Login Timeout value");
}
StringBuffer sb =
new StringBuffer(ManagedPoolDataSource.class.getName()
+ " instance:\n User: " + getUsername()
+ "\n Url: " + getUrl()
+ "\n Login Timeout: " + timeout
+ "\n Num ACTIVE: " + getNumActive()
+ "\n Num IDLE: " + getNumIdle());
if (doResetAutoCommit) {
sb.append("\n Default auto-commit: " + getDefaultAutoCommit());
}
if (doResetReadOnly) {
sb.append("\n Default read-only: " + getDefaultReadOnly());
}
if (doResetTransactionIsolation) {
sb.append("\n Default trans. lvl.: "
+ getDefaultTransactionIsolation());
}
if (doResetCatalog) {
sb.append("\n Default catalog: " + getDefaultCatalog());
}
/** @todo: Add report for max and min settings which aren't implemented yet. */
return sb.toString() + "\n Max Active: " + getMaxActive()
+ "\n Init Size: " + getInitialSize() + "\n Conn Props: "
+ getConnectionProperties() + "\n Validation Query: "
+ validationQuery + '\n';
}
}