the lowest level component, now contains the DataPool...will gradually supplant EnvEngine and the rest
509 lines
17 KiB
Java
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 <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 <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
|