,
* for Silverwrist Design Studios. Portions created by Eric J. Bowersox are
* Copyright (C) 2001 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved.
*
* Contributor(s):
*/
package com.silverwrist.venice.db;
import java.util.*;
import java.sql.*;
import org.apache.log4j.*;
import org.w3c.dom.*;
import com.silverwrist.util.DOMElementHelper;
import com.silverwrist.venice.except.ConfigException;
/**
* A simple pooling system for JDBC connections, which allows connections to be kept in a pool until
* they're needed, avoiding the overhead of opening and closing connections for each transaction, or
* keeping one database connection open for each user.
* Based on some code from Marty Hall's Core Servlets and Java Server Pages (Prentice Hall/
* Sun Microsystems, 2000).
*
* @author Eric J. Bowersox <erbo@silcom.com>
* @version X
*/
public class DataPool implements Runnable
{
/*--------------------------------------------------------------------------------
* Static data values
*--------------------------------------------------------------------------------
*/
private static Category logger = Category.getInstance(DataPool.class);
/*--------------------------------------------------------------------------------
* Attributes
*--------------------------------------------------------------------------------
*/
private String driver; // name of JDBC driver class
private String url; // URL for JDBC connection
private String username; // username for JDBC connection
private String password; // password for JDBC connection
private int max_conns; // maximum number of possible connections
private boolean wait_if_busy; // do we wait for a connection if none is available?
private boolean pending = false; // pending connection being created?
private Vector avail_connections; // connections which are available for use
private Vector busy_connections; // connections which are currently in use
/*--------------------------------------------------------------------------------
* Constructor
*--------------------------------------------------------------------------------
*/
/**
* Creates and configures a new database pool, based on a section from the Venice configuration file.
*
* @param cfg The <database/> section of the configuration file.
* @exception com.silverwrist.venice.core.ConfigException The configuration is not correct in some way.
* @exception java.sql.SQLException If the initial connections could not be created.
*/
public DataPool(Element cfg) throws ConfigException, SQLException
{
// Make sure they passed the section to us.
if (!(cfg.getTagName().equals("database")))
{ // not the right section name
logger.fatal("configuration section is not ");
throw new ConfigException("improper configuration section",cfg);
} // end if
// Use a DOMElementHelper to read off the configuration for the DataPool.
DOMElementHelper cfgx = new DOMElementHelper(cfg);
driver = cfgx.getSubElementText("driver");
if (driver==null)
{ // no driver found!
logger.fatal("no name inside section");
throw new ConfigException("no name specified",cfg);
} // end if
if (logger.isDebugEnabled())
logger.debug("DataPool driver: " + driver);
url = cfgx.getSubElementText("uri");
if (url==null)
{ // no database URI found!
logger.fatal("no name inside section");
throw new ConfigException("no database specified",cfg);
} // end if
if (logger.isDebugEnabled())
logger.debug("DataPool URI: " + url);
username = cfgx.getSubElementText("username");
if (username==null)
{ // no database username found
logger.fatal("no inside section");
throw new ConfigException("no database specified",cfg);
} // end if
if (logger.isDebugEnabled())
logger.debug("DataPool username: " + url);
password = cfgx.getSubElementText("password");
if (password==null)
{ // no database password found
logger.fatal("no user inside section");
throw new ConfigException("no database specified",cfg);
} // end if
int initial_conns = 0;
try
{ // retrieve the initial-conns figure and make it an integer
String tmp = cfgx.getSubElementText("initial-conns");
if (tmp==null)
{ // the connections value was not specified
logger.fatal("database value not specified");
throw new ConfigException("no value specified",cfg);
} // end if
initial_conns = Integer.parseInt(tmp);
if (initial_conns<0)
{ // the connections value was out of range
logger.fatal("database value was out of range");
throw new ConfigException(" value out of range",cfg);
} // end if
} // end try
catch (NumberFormatException e)
{ // the value wasn't a valid integer
logger.fatal("database value was not a number");
throw new ConfigException(" value is not a number",e,cfg);
} // end catch
if (logger.isDebugEnabled())
logger.debug("DataPool initial connections: " + String.valueOf(initial_conns));
try
{ // retrieve the max-conns figure and make it an integer
String tmp = cfgx.getSubElementText("max-conns");
if (tmp==null)
{ // maximum connections value not specified
logger.fatal("database value not specified");
throw new ConfigException("no value specified",cfg);
} // end if
max_conns = Integer.parseInt(tmp);
if (max_conns<=0)
{ // value must be greater than 0!
logger.fatal("database value was out of range");
throw new ConfigException(" value out of range",cfg);
} // end if
} // end try
catch (NumberFormatException e)
{ // the value wasn't a valid integer
logger.fatal("database value was not a number");
throw new ConfigException(" value is not a number",e,cfg);
} // end catch
if (logger.isDebugEnabled())
logger.debug("DataPool maximum connections: " + String.valueOf(max_conns));
wait_if_busy = cfgx.hasChildElement("wait-if-busy");
if (logger.isDebugEnabled())
logger.debug("DataPool wait if busy: " + String.valueOf(wait_if_busy));
if (initial_conns>max_conns)
{ // fix initial value if above maximum
if (logger.isDebugEnabled())
logger.debug("N.B.: reducing configured initial connections");
initial_conns = max_conns;
} // end if
// Create the vectors that hold connections.
avail_connections = new Vector(initial_conns);
busy_connections = new Vector();
// Populate the "available connection" vector.
for (int i=0; i " + String.valueOf(rc));
return rc;
} // end numConnections
/**
* Attempts to get a database connection from the pool, waiting for one if the "wait-if-busy" flag was set
* in the data pool's configuration.
*
* @return A new database connection.
* @exception java.sql.SQLException The connection limit was reached, or an error prevented the creation
* of another connection.
*/
public synchronized Connection getConnection() throws SQLException
{
for (;;)
{ // loop until we get a connection or throw an exception
if (avail_connections.isEmpty())
{ // no connections available - we may need to make a new one
if (logger.isDebugEnabled())
logger.debug("no connections available - looking to get one");
if ((numConnections() < max_conns) && !pending)
makeBackgroundConnection(); // try to create a new connection
else if (!wait_if_busy)
{ // don't want to wait? tough, we're h0sed!
logger.error("exhausted maximum connection limit (" + String.valueOf(max_conns) + ")");
throw new SQLException("connection limit reached");
} // end else if
// Wait for the background connect attempt to finish, or for
// someone to return a connection.
try
{ // park the thread here until we know what's up
wait();
} // end try
catch (InterruptedException e)
{ // do nothing
} // end catch
// now fall through the loop and try again
} // end if
else
{ // pull the last connection off the available list...
int ndx = avail_connections.size() - 1;
Connection rc = (Connection)(avail_connections.elementAt(ndx));
avail_connections.removeElementAt(ndx);
if (rc.isClosed())
{ // this connection is closed - discard it, and notify any waiters that a slot
// has opened up
if (logger.isDebugEnabled())
logger.debug("discarding closed connection");
notifyAll();
rc = null;
// fall out to the end of the loop and try again
} // end if
else
{ // this connection is OK - return it
busy_connections.addElement(rc);
return rc;
} // end else
} // end else (at least one connection available)
} // end for (ever)
} // end getConnection
/**
* Returns a database connection to the pool.
*
* @param c The connection to be returned to the pool.
*/
public synchronized void releaseConnection(Connection c)
{
if (c!=null)
{ // move from one vector to another
busy_connections.removeElement(c);
avail_connections.addElement(c);
notifyAll(); // wake up! Got a new connection for you!
} // end if
} // end releaseConnection
/**
* Closes all connections managed by this data pool.
*
* @see #closeConnections(java.util.Vector)
*/
public synchronized void closeAllConnections()
{
if (logger.isDebugEnabled())
logger.debug("closing ALL connections!");
closeConnections(avail_connections);
avail_connections = new Vector();
closeConnections(busy_connections);
busy_connections = new Vector();
} // end closeAllConnections
/**
* Returns a string reflecting the current state of the data pool, commonly used for debugging.
*
* @return The debugging string for this pool.
*/
public synchronized String toString()
{
StringBuffer info = new StringBuffer();
info.append("DataPool(\"").append(url).append("\",\"").append(username).append("\"), ");
info.append(avail_connections.size()).append(" avail, ").append(busy_connections.size());
info.append(" busy, ").append(max_conns).append(" max");
return info.toString();
} // end toString
} // end class DataPool