Eric J. Bowersox 128c33cd9b beginning the underlying service architecture migration - the "global site" is
the lowest level component, now contains the DataPool...will gradually
supplant EnvEngine and the rest
2002-05-04 08:20:13 +00:00

509 lines
17 KiB
Java

/*
* The contents of this file are subject to the Mozilla Public License Version 1.1
* (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at <http://www.mozilla.org/MPL/>.
*
* Software distributed under the License is distributed on an "AS IS" basis, WITHOUT
* WARRANTY OF ANY KIND, either express or implied. See the License for the specific
* language governing rights and limitations under the License.
*
* The Original Code is the Venice Web Communities System.
*
* The Initial Developer of the Original Code is Eric J. Bowersox <erbo@silcom.com>,
* for Silverwrist Design Studios. Portions created by Eric J. Bowersox are
* Copyright (C) 2001-02 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.<P>
* Based on some code from Marty Hall's <EM>Core Servlets and Java Server Pages</EM> (Prentice Hall/
* Sun Microsystems, 2000).
*
* @author Eric J. Bowersox &lt;erbo@silcom.com&gt;
* @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 &lt;database/&gt; 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 <database/> section to us.
if (!(cfg.getTagName().equals("database")))
{ // not the right section name
logger.fatal("configuration section is not <database/>");
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 <driver/> name inside <database/> section");
throw new ConfigException("no <driver/> 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 <uri/> name inside <database/> section");
throw new ConfigException("no database <uri/> 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 <username/> inside <database/> section");
throw new ConfigException("no database <username/> 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 <password/> inside <database/> section");
throw new ConfigException("no database <password/> 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 <initial-conns/> value not specified");
throw new ConfigException("no <initial-conns/> value specified",cfg);
} // end if
initial_conns = Integer.parseInt(tmp);
if (initial_conns<0)
{ // the connections value was out of range
logger.fatal("database <initial-conns/> value was out of range");
throw new ConfigException("<initial-conns/> value out of range",cfg);
} // end if
} // end try
catch (NumberFormatException e)
{ // the value wasn't a valid integer
logger.fatal("database <initial-conns/> value was not a number");
throw new ConfigException("<initial-conns/> 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 <max-conns/> value not specified");
throw new ConfigException("no <max-conns/> value specified",cfg);
} // end if
max_conns = Integer.parseInt(tmp);
if (max_conns<=0)
{ // value must be greater than 0!
logger.fatal("database <max-conns/> value was out of range");
throw new ConfigException("<max-conns/> value out of range",cfg);
} // end if
} // end try
catch (NumberFormatException e)
{ // the value wasn't a valid integer
logger.fatal("database <max-conns/> value was not a number");
throw new ConfigException("<max-conns/> 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<initial_conns; i++)
avail_connections.addElement(makeNewConnection());
logger.info("DataPool initialized");
} // end constructor
/*--------------------------------------------------------------------------------
* finalize() function
*--------------------------------------------------------------------------------
*/
/**
* Called by the garbage collector on an object when garbage collection determines that there are no
* more references to the object.
*/
protected void finalize()
{
closeConnections(avail_connections);
closeConnections(busy_connections);
} // end finalize
/*--------------------------------------------------------------------------------
* Internal functions
*--------------------------------------------------------------------------------
*/
/**
* Creates and returns a new database connection.
*
* @return The new database connection object.
* @exception java.sql.SQLException An error prevented the creation of the connection.
*/
private final Connection makeNewConnection() throws SQLException
{
try
{ // make the connection and return it
Class.forName(driver).newInstance(); // preload driver class
// the newInstance() call above is to work around some broken Java implementations
return DriverManager.getConnection(url,username,password);
} // end try
catch (ClassNotFoundException e)
{ // convert a ClassNotFoundException to a nicer SQLException
logger.error("JDBC driver class \"" + driver + "\" not found",e);
throw new SQLException("Can't find class for driver: " + driver);
} // end catch
catch (IllegalAccessException e2)
{ // convert this to a nicer SQLException
logger.fatal("Can't access \"" + driver + "\" constructor: " + e2.getMessage(),e2);
throw new SQLException("Can't access class for driver: " + driver);
} // end catch
catch (InstantiationException e3)
{ // convert this to a nicer SQLException
logger.fatal("Can't create instance of \"" + driver + "\": " + e3.getMessage(),e3);
throw new SQLException("Can't create class for driver: " + driver);
} // end catch
} // end makeNewConnection
/**
* Spins off a background thread to create a new database connection.
*
* @see #run()
*/
private final void makeBackgroundConnection()
{
pending = true;
try
{ // spin off the connection attempt to the background
Thread thrd = new Thread(this);
thrd.start();
} // end try
catch (OutOfMemoryError e)
{ // give up on new connection
logger.warn("memory failure spinning off background-connect thread");
pending = false;
} // end catch
} // end makeBackgroundConnection
/**
* Closes all database connections in the specified vector.
*
* @param vconn Vector of connections to be closed.
*/
private final static void closeConnections(Vector vconn)
{
if (logger.isDebugEnabled())
logger.debug("closeConnections(" + String.valueOf(vconn.size()) + " to be closed)");
for (int i=0; i<vconn.size(); i++)
{ // loop over the entire vector
try
{ // take each connection and close it
Connection c = (Connection)(vconn.get(i));
if (!(c.isClosed()))
c.close();
} // end try
catch (SQLException e)
{ // what do we care, this connection is dying anyhow
logger.warn("got a SQLException in closeConnections (ignored): " + e.getMessage());
} // end catch
} // end for
} // end closeConnections
/*--------------------------------------------------------------------------------
* Implementations from interface Runnable
*--------------------------------------------------------------------------------
*/
/**
* The thread function which creates a new connection and adds it to the list of available
* connections "in the background." It then notifies all threads which are waiting for a
* new connection.
*
* @see #makeBackgroundConnection()
* @see #makeNewConnection()
*/
public void run()
{
try
{ // attempt to create another connection in the background
Connection c = makeNewConnection();
synchronized (this)
{ // we've got a new available connection - let everyone know
if (logger.isDebugEnabled())
logger.debug("created new connection in the background");
avail_connections.addElement(c);
pending = false;
notifyAll();
} // end synchronized block
} // end try
catch (Exception e)
{ // caught an SQLException or an OutOfMemory exception
logger.warn("background connection thread caught " + e.getClass().getName() + ": " + e.getMessage());
// ditch this new connection and wait until an existing one frees up
} // end catch
} // end run
/*--------------------------------------------------------------------------------
* External operations
*--------------------------------------------------------------------------------
*/
/**
* Returns the number of connections currently managed by this pool.
*
* @return The number of connections currently managed by this pool.
*/
public synchronized int numConnections()
{
int rc = avail_connections.size() + busy_connections.size();
if (logger.isDebugEnabled())
logger.debug("numConnections() => " + 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 new WrappedConnection(this,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.
*/
synchronized void releaseConnection(Connection c)
{
if (c!=null)
{ // can release this connection
if (c instanceof WrappedConnection)
{ // close our wrapped connection
try
{ // close the connection
c.close();
} // end try
catch (SQLException e)
{ // log exception and continue
logger.error("c.close() exception",e);
} // end catch
return;
} // end if
// 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