/* * 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 . * * 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 , * 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.

* 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 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