package sanpai.db;
import java.io.*;
import java.sql.*;
import java.util.*;
import java.util.date;
/**
* this class is a singleton that provides access to one or many
* connection pools defined in a property file. a client gets
* access to the single instance through the static getinstance()
* method and can then check-out and check-in connections from a pool.
* when the client shuts down it should call the release() method
* to close all open connections and do other clean up.
*/
public class dbconnectionmanager {
static private dbconnectionmanager instance; // the single instance
static private int clients;
private vector drivers = new vector();
private printwriter log;
private hashtable pools = new hashtable();
/**
* this inner class represents a connection pool. it creates new
* connections on demand, up to a max number if specified.
* it also makes sure a connection is still open before it is
* returned to a client.
*/
class dbconnectionpool {
private int checkedout;
private vector freeconnections = new vector();
private int maxconn;
private string name;
private string password;
private string url;
private string user;
/**
* creates new connection pool.
*
* @param name the pool name
* @param url the jdbc url for the database
* @param user the database user, or null
* @param password the database user password, or null
* @param maxconn the maximal number of connections, or 0
* for no limit
*/
public dbconnectionpool(string name, string url, string user, string password,
int maxconn) {
this.name = name;
this.url = url;
this.user = user;
this.password = password;
this.maxconn = maxconn;
}
/**
* checks in a connection to the pool. notify other threads that
* may be waiting for a connection.
*
* @param con the connection to check in
*/
public synchronized void freeconnection(connection con) {
// put the connection at the end of the vector
freeconnections.addelement(con);
checkedout–;
notifyall();
}
/**
* checks out a connection from the pool. if no free connection
* is available, a new connection is created unless the max
* number of connections has been reached. if a free connection
* has been closed by the database, its removed from the pool
* and this method is called again recursively.
*/
public synchronized connection getconnection()throws sqlexception {
connection con = null;
if (freeconnections.size() > 0) {
// pick the first connection in the vector
// to get round-robin usage
con = (connection) freeconnections.firstelement();
freeconnections.removeelementat(0);
try {
if (con.isclosed()) {
log("removed bad connection from " + name);
// try again recursively
con = getconnection();
}
}
catch (sqlexception e) {
log("removed bad connection from " + name);
// try again recursively
con = getconnection();
}
}
else if (maxconn == 0 || checkedout < maxconn) {
con = newconnection();
}
if (con != null) {
checkedout++;
}
return con;
}
/**
* checks out a connection from the pool. if no free connection
* is available, a new connection is created unless the max
* number of connections has been reached. if a free connection
* has been closed by the database, its removed from the pool
* and this method is called again recursively.
*
* if no connection is available and the max number has been
* reached, this method waits the specified time for one to be
* checked in.
*
* @param timeout the timeout value in milliseconds
*/
public synchronized connection getconnection(long timeout) throws sqlexception {
long starttime = new date().gettime();
connection con;
while ((con = getconnection()) == null) {
try {
wait(timeout);
}
catch (interruptedexception e) {}
if ((new date().gettime() – starttime) >= timeout) {
// timeout has expired
return null;
}
}
return con;
}
/**
* closes all available connections.
*/
public synchronized void release() {
enumeration allconnections = freeconnections.elements();
while (allconnections.hasmoreelements()) {
connection con = (connection) allconnections.nextelement();
try {
con.close();
log("closed connection for pool " + name);
}
catch (sqlexception e) {
log(e, "cant close connection for pool " + name);
}
}
freeconnections.removeallelements();
}
/**
* creates a new connection, using a userid and password
* if specified.
*/
private connection newconnection() throws sqlexception {
connection con = null;
try {
if (user == null) {
con = drivermanager.getconnection(url);
}
else {
con = drivermanager.getconnection(url, user, password);
}
log("created a new connection in pool " + name);
}
catch (sqlexception e) {
log(e, "cant create a new connection for " + url);
throw e;
}
return con;
}
}
/**
* a private constructor since this is a singleton
*/
private dbconnectionmanager() {
init();
}
/**
* creates instances of dbconnectionpool based on the properties.
* a dbconnectionpool can be defined with the following properties:
*
* <poolname>.url the jdbc url for the database
* <poolname>.user a database user (optional)
* <poolname>.password a database user password (if user specified)
* <poolname>.maxconn the maximal number of connections (optional)
*
*
* @param props the connection pool properties
*/
private void createpools(properties props) {
enumeration propnames = props.propertynames();
while (propnames.hasmoreelements()) {
string name = (string) propnames.nextelement();
if (name.endswith(".url")) {
string poolname = name.substring(0, name.lastindexof("."));
string url = props.getproperty(poolname + ".url");
if (url == null) {
log("no url specified for " + poolname);
continue;
}
string user = props.getproperty(poolname + ".user");
string password = props.getproperty(poolname + ".password");
string maxconn = props.getproperty(poolname + ".maxconn", "0");
int max;
try {
max = integer.valueof(maxconn).intvalue();
}
catch (numberformatexception e) {
log("invalid maxconn value " + maxconn + " for " + poolname);
max = 0;
}
dbconnectionpool pool =
new dbconnectionpool(poolname, url, user, password, max);
pools.put(poolname, pool);
log("initialized pool " + poolname);
}
}
}
/**
* returns a connection to the named pool.
*
* @param name the pool name as defined in the properties file
* @param con the connection
*/
public void freeconnection(string name, connection con) {
dbconnectionpool pool = (dbconnectionpool) pools.get(name);
if (pool != null) {
pool.freeconnection(con);
}
}
/**
* returns an open connection. if no one is available, and the max
* number of connections has not been reached, a new connection is
* created.
*
* @param name the pool name as defined in the properties file
* @return connection the connection or null
*/
public connection getconnection(string name) throws sqlexception {
dbconnectionpool pool = (dbconnectionpool) pools.get(name);
if (pool != null) {
return pool.getconnection();
}
return null;
}
/**
* returns an open connection. if no one is available, and the max
* number of connections has not been reached, a new connection is
* created. if the max number has been reached, waits until one
* is available or the specified time has elapsed.
*
* @param name the pool name as defined in the properties file
* @param time the number of milliseconds to wait
* @return connection the connection or null
*/
public connection getconnection(string name, long time) throws sqlexception {
dbconnectionpool pool = (dbconnectionpool) pools.get(name);
if (pool != null) {
return pool.getconnection(time);
}
return null;
}
/**
* returns the single instance, creating one if its the
* first time this method is called.
*
* @return dbconnectionmanager the single instance.
*/
static synchronized public dbconnectionmanager getinstance() {
if (instance == null) {
instance = new dbconnectionmanager();
}
clients++;
return instance;
}
/**
* loads properties and initializes the instance with its values.
*/
private void init() {
inputstream is = getclass().getresourceasstream("/db.properties");
properties dbprops = new properties();
try {
dbprops.load(is);
}
catch (exception e) {
system.err.println("cant read the properties file. " +
"make sure db.properties is in the classpath");
return;
}
string logfile = dbprops.getproperty("logfile", "dbconnectionmanager.log");
try {
log = new printwriter(new filewriter(logfile, true), true);
}
catch (ioexception e) {
system.err.println("cant open the log file: " + logfile);
log = new printwriter(system.err);
}
loaddrivers(dbprops);
createpools(dbprops);
}
/**
* loads and registers all jdbc drivers. this is done by the
* dbconnectionmanager, as opposed to the dbconnectionpool,
* since many pools may share the same driver.
*
* @param props the connection pool properties
*/
private void loaddrivers(properties props) {
string driverclasses = props.getproperty("drivers");
stringtokenizer st = new stringtokenizer(driverclasses);
while (st.hasmoreelements()) {
string driverclassname = st.nexttoken().trim();
try {
driver driver = (driver)
class.forname(driverclassname).newinstance();
drivermanager.registerdriver(driver);
drivers.addelement(driver);
log("registered jdbc driver " + driverclassname);
}
catch (exception e) {
log("cant register jdbc driver: " +
driverclassname + ", exception: " + e);
}
}
}
/**
* writes a message to the log file.
*/
private void log(string msg) {
log.println(new date() + ": " + msg);
}
/**
* writes a message with an exception to the log file.
*/
private void log(throwable e, string msg) {
log.println(new date() + ": " + msg);
e.printstacktrace(log);
}
/**
* closes all open connections and deregisters all drivers.
*/
public synchronized void release() {
// wait until called by the last client
if (–clients != 0) {
return;
}
enumeration allpools = pools.elements();
while (allpools.hasmoreelements()) {
dbconnectionpool pool = (dbconnectionpool) allpools.nextelement();
pool.release();
}
enumeration alldrivers = drivers.elements();
while (alldrivers.hasmoreelements()) {
driver driver = (driver) alldrivers.nextelement();
try {
drivermanager.deregisterdriver(driver);
log("deregistered jdbc driver " + driver.getclass().getname());
}
catch (sqlexception e) {
log(e, "cant deregister jdbc driver: " + driver.getclass().getname());
}
}
}
/**
* release all resources when this object is destroyed.
*/
public void finalize() {
release();
}}