/*
* DatabaseConnectionPool.java
*
* This work 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 work 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*
* Copyright (c) 2004 Per Cederberg. All rights reserved.
*/
package org.liquidsite.util.db;
import java.util.ArrayList;
import org.liquidsite.util.log.Log;
/**
* A database connection pool.
*
* @author Per Cederberg, <per at percederberg dot net>
* @version 1.0
*/
public class DatabaseConnectionPool {
/**
* The class logger.
*/
private static final Log LOG = new Log(DatabaseConnectionPool.class);
/**
* The database connector.
*/
private DatabaseConnector db;
/**
* The minimum pool size.
*/
private int minSize = 0;
/**
* The maximum pool size. If this value is negative, the pool has
* an infinite size.
*/
private int maxSize = -1;
/**
* The list of connections in the pool.
*/
private ArrayList connections = new ArrayList();
/**
* Creates a new database connection pool. The JDBC driver should
* have been loaded prior to calling this constructor, as no
* database connections can be created otherwise.
*
* @param db the database connector to use
*/
public DatabaseConnectionPool(DatabaseConnector db) {
this.db = db;
LOG.info("created connection pool for " + db);
}
/**
* Returns the current connection pool size. This method is
* synchronized to guarantee that no concurrent operation is
* being made to the connection list.
*
* @return the current connection pool size
*/
public synchronized int getCurrentSize() {
return connections.size();
}
/**
* Returns the minium connection pool size. By default the
* connection pool minimum size is zero (0).
*
* @return the minimum connection pool size
*
* @see #setMinimumSize
*/
public int getMinimumSize() {
return minSize;
}
/**
* Sets the minimum connection pool size. This method will not
* create new database connections, but only register the new
* minimum count.
*
* @param size the new minimum pool size
*
* @see #getMinimumSize
* @see #update
*/
public void setMinimumSize(int size) {
LOG.info("new connection pool min size: " + size +
", was: " + minSize + ", for " + db);
this.minSize = size;
}
/**
* Returns the maximum connection pool size. By default the
* connection pool has no maximum size.
*
* @return the maximum connection pool size, or
* a negative value for unlimited
*
* @see #setMaximumSize
*/
public int getMaximumSize() {
return maxSize;
}
/**
* Sets the maximum connection pool size. This method will not
* close any previously open database connections, but only
* register the new maximum count.
*
* @param size the new maximum pool size, or
* a negative value for unlimited
*
* @see #getMaximumSize
* @see #update
*/
public void setMaximumSize(int size) {
LOG.info("new connection pool max size: " + size +
", was: " + maxSize + ", for " + db);
this.maxSize = size;
}
/**
* Returns a database connection from the pool. If there is none
* available, a new connection will be created.
*
* @return the database connection
*
* @throws DatabaseConnectionException if a new database
* connection couldn't be created
*
* @see #returnConnection
*/
public DatabaseConnection getConnection()
throws DatabaseConnectionException {
DatabaseConnection con;
LOG.trace("getting pooled connection for " + db + "...");
try {
con = checkOut();
if (con == null) {
con = create();
}
} catch (DatabaseConnectionException e) {
LOG.warning("failed getting pooled connection for " + db, e);
throw e;
}
LOG.trace("got pooled connection");
return con;
}
/**
* Returns a database connection to the pool. If the connection
* isn't already in the pool, nothing happens.
*
* @param con the database connection
*
* @see #getConnection
*/
public void returnConnection(DatabaseConnection con) {
LOG.trace("returning pooled connection for " + db + "...");
checkIn(con);
LOG.trace("returned pooled connection");
}
/**
* Updates the connection pool. This method will step through all
* available database connections in the pool, removing all
* broken or timed out connections. The connection pool size may
* also be adjusted to fit in between the minimum and maximum
* sizes.
*
* Note that any call to this method should be made from a
* background thread, as this method may get stuck waiting for
* I/O timeouts.
*
* @throws DatabaseConnectionException if new database
* connections couldn't be created
*/
public void update() throws DatabaseConnectionException {
ArrayList list = new ArrayList();
DatabaseConnection con;
int i;
// Find invalid or old connections
LOG.info("closing old connections in pool for " + db + "...");
synchronized (this) {
for (i = 0; i < connections.size(); i++) {
con = (DatabaseConnection) connections.get(i);
if (con.isReserved()) {
// Do nothing
} else if (!con.isValid() || con.isExpired()) {
con.setReserved(true);
list.add(con);
}
}
}
// Destroy invalid connections
for (i = 0; i < list.size(); i++) {
con = (DatabaseConnection) list.get(i);
destroy(con);
}
LOG.info("closed old connections in pool, count: " + list.size());
// Create minimum number of connections
LOG.info("creating new connections in pool for " + db);
for (i = 0; getCurrentSize() < minSize; i++) {
try {
con = create();
} catch (DatabaseConnectionException e) {
LOG.warning("failed creating new connections in pool for " +
db,
e);
throw e;
}
checkIn(con);
}
LOG.info("created new connections in pool, count: " + i);
}
/**
* Creates a new database connection. The connection will be
* reserved and added to the connection pool. This method also
* checks for the maximum size of the connection pool.
*
* @return a new, reserved and pooled connection
*
* @throws DatabaseConnectionException if a new database
* connection couldn't be created
*/
private DatabaseConnection create()
throws DatabaseConnectionException {
DatabaseConnection con;
String msg;
if (maxSize > 0 && getCurrentSize() >= maxSize) {
msg = "cannot create new database connection, " +
"pool size maximum of " + maxSize +
" already reached";
LOG.warning(msg);
throw new DatabaseConnectionException(msg);
}
con = new DatabaseConnection(db);
con.setReserved(true);
add(con);
return con;
}
/**
* Destroys a database connection. The connection will be removed
* from the pool and closed.
*
* @param con the database connection
*/
private void destroy(DatabaseConnection con) {
remove(con);
con.close();
}
/**
* Checks out a connection from the pool. This method will return
* a connection from the pool that is currently unused.
*
* @return the first unused connection in the pool, or
* null if no connection found
*
* @throws DatabaseConnectionException if the database connection
* couldn't be reestablished
*/
private DatabaseConnection checkOut()
throws DatabaseConnectionException {
return checkOut(0);
}
/**
* Checks out a connection from the pool. This method will return
* a connection from the pool that is currently unused. It is
* synchronized to guarantee that no concurrent operation is
* being made to the connection list.
*
* @param start the starting position in the list
*
* @return the first unused connection in the pool, or
* null if no connection found
*
* @throws DatabaseConnectionException if the database connection
* couldn't be reestablished
*/
private synchronized DatabaseConnection checkOut(int start)
throws DatabaseConnectionException {
DatabaseConnection con;
for (int i = start; i < connections.size(); i++) {
con = (DatabaseConnection) connections.get(i);
if (!con.isReserved() && con.isValid() && !con.isExpired()) {
con.setReserved(true);
con.reset();
return con;
}
}
return null;
}
/**
* Checks in a connection to the pool. This method will mark the
* connection as unused if it already exists in the pool. It is
* synchronized to guarantee that no concurrent operation is
* being made to the connection list.
*
* @param con the connection to check in
*/
private synchronized void checkIn(DatabaseConnection con) {
if (connections.contains(con)) {
con.setReserved(false);
} else {
con.close();
}
}
/**
* Adds a new connection to the pool. This method is synchronized
* to guarantee that no concurrent operation is being made to the
* connection list.
*
* @param con the connection to add
*/
private synchronized void add(DatabaseConnection con) {
if (!connections.contains(con)) {
connections.add(con);
LOG.info("added connection to pool for " + db +
", new size: " + connections.size());
}
}
/**
* Removes a connection from the pool. Note that this will not
* close the connection itself, only remove it from the pool.
* This method is synchronized to guarantee that no concurrent
* operation is being made to the connection list.
*
* @param con the connection to add
*/
private synchronized void remove(DatabaseConnection con) {
connections.remove(con);
LOG.info("removed connection from pool for " + db +
", new size: " + connections.size());
}
}