/*
* XAPool: Open Source XA JDBC Pool
* Copyright (C) 2003 Objectweb.org
* Initial Developer: Lutris Technologies Inc.
* Contact: xapool-public@lists.debian-sf.objectweb.org
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*/
package org.enhydra.jdbc.pool;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import javax.sql.ConnectionEvent;
import javax.sql.ConnectionEventListener;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.DataSource;
import javax.sql.PooledConnection;
import org.enhydra.jdbc.core.CoreDataSource;
import org.enhydra.jdbc.core.JdbcThreadFactory;
import org.enhydra.jdbc.util.Logger;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* StandardPoolDataSource class allows to make some operations on
* PooledConnection. It implements PoolHelper for the 3 methods :<p>
* create : create a PooledConnection<p>
* create(user,password) : create a PooledConnection with an other user/password<p>
* testThisObject : check if the object is still valid<p>
* checkThisObject : check if the object is closed<p>
* expire : kill the object<p>
*/
public class StandardPoolDataSource
extends CoreDataSource
implements DataSource, PoolHelper, ConnectionEventListener {
public ConnectionPoolDataSource cpds; // object to build PooledConnection
public GenericPool pool; // pool to store StandardDataSource object
public String dataSourceName; // jndi name for DataSource Factory
public String jdbcTestStmt;
// JDBC test statement for checkLevelObject=1 or 2
public boolean onOff; // If the pool is started or not
public Context ictx; // the initial context
public Log glog = LogFactory.getLog("org.enhydra.jdbc.xapool");
/**
* Constructor
*/
public StandardPoolDataSource() {
log = new Logger(glog);
super.setLogWriter(log);
pool = new GenericPool(this);
// create the pool with StandardPoolDataSource object
pool.setLogger(log);
onOff = false;
dataSourceName = null;
}
/**
* Constructor
*/
public StandardPoolDataSource(int initSize) { // with an init Max size
log = new Logger(glog);
super.setLogWriter(log);
pool = new GenericPool(this, initSize);
// create the pool with StandardPoolDataSource object
pool.setLogger(log);
onOff = false;
dataSourceName = null;
}
/**
* Constructor
*/
public StandardPoolDataSource(ConnectionPoolDataSource cc) {
cpds = cc;
log = new Logger(glog);
super.setLogWriter(log);
pool = new GenericPool(this);
// create the pool with StandardPoolDataSource object
pool.setLogger(log);
try {
cpds.setLogWriter(log);
} catch (SQLException sqle) {
}
onOff = false;
dataSourceName = null;
}
/**
* Constructor
*/
public StandardPoolDataSource(
ConnectionPoolDataSource cc,
int initSize) { // with an init Max size
cpds = cc;
log = new Logger(glog);
super.setLogWriter(log);
pool = new GenericPool(this, initSize);
// create the pool with StandardPoolDataSource object
pool.setLogger(log);
onOff = false;
dataSourceName = null;
}
/**
* Set up the data source name, get the initial context,
* and lookup in JNDI to obtain a reference of the DataSourceName
* this method must be called before a getConnection (in this case
* an exception is returned
*/
public void setDataSourceName(String dataSourceName) {
log.debug("StandardPoolDataSource:setDataSourceName");
this.dataSourceName = dataSourceName; // set up the data source name
/*
synchronized(this) {
if (onOff) {
pool.stop();
onOff = false;
}
}
*/
}
public String getDataSourceName() {
return dataSourceName; // return the dataSourceName (jndi mechanism)
}
/**
* getConnection allows to get an object from the pool and returns it
* to the user. In this case, we return an PooledConnection
*/
public Connection getConnection() throws SQLException {
return getConnection(getUser(), getPassword());
}
/**
* getConnection allows to get an object from the pool and returns it
* to the user. In this case, we return an PooledConnection
*/
public Connection getConnection(String _user, String _password)
throws SQLException {
log.debug("StandardPoolDataSource:getConnection");
Connection ret = null;
PooledConnection con = null;
synchronized (this) {
if (!onOff) {
log.debug(
"StandardPoolDataSource:getConnection must configure the pool...");
pool.start(); // the pool starts now
onOff = true; // and is initialized
log.debug(
"StandardPoolDataSource:getConnection pool config : \n"
+ pool.toString());
}
}
try {
try {
log.debug(
"StandardPoolDataSource:getConnection Try to give a "
+ "connection (checkOut)");
con = (PooledConnection) pool.checkOut(_user, _password);
// get a connection from the pool
log.debug(
"StandardPoolDataSource:getConnection checkOut return"
+ "a new connection");
} catch (Exception e) {
e.printStackTrace();
log.debug(
"StandardPoolDataSource:getConnection SQLException in StandardPoolDataSource:getConnection"
+ e);
throw new SQLException(
"SQLException in StandardPoolDataSource:getConnection no connection available "
+ e);
}
ret = con.getConnection();
} catch (Exception e) {
log.debug("StandardPoolDataSource:getConnection exception" + e);
e.printStackTrace();
SQLException sqle =
new SQLException(
"SQLException in StandardPoolDataSource:getConnection exception: "
+ e);
if (e instanceof SQLException)
sqle.setNextException((SQLException) e);
if (con != null) {
pool.checkIn(con);
}
throw sqle;
}
log.debug("StandardPoolDataSource:getConnection return a connection");
return ret;
}
/**
* connectionErrorOccurred and connectionClosed are methods
* from ConnectionEventListener interface
*
* Invoked when a fatal connection error occurs,
* just before an SQLException is thrown to the application
*/
public void connectionErrorOccurred(ConnectionEvent event) {
Object obj = event.getSource();
PooledConnection pc = (PooledConnection) obj;
pool.nextGeneration(pc);
pool.removeLockedObject(pc); // remove the object from the locked pool
expire(pc); // kill the connection (from super)
log.debug(
"StandardXAPoolDataSource:connectionErrorOccurred remove the object from the pool");
}
/**
* Invoked when the application calls close()
* on its representation of the connection
*/
public void connectionClosed(ConnectionEvent event) {
log.debug(
"StandardPoolDataSource:connectionClosed close the connection");
Object obj = event.getSource();
pool.checkIn(obj);
}
/**
* object specific work to kill the object
*/
public void expire(Object o) {
log.debug(
"StandardPoolDataSource:expire expire a connection, remove from the pool");
if (o == null)
return;
try {
PooledConnection pooledCon = (PooledConnection) o;
pooledCon.close(); // call close() of PooledConnection
pooledCon.removeConnectionEventListener(this);
log.debug("StandardPoolDataSource:expire close the connection");
} catch (java.sql.SQLException e) {
log.error(
"StandardPoolDataSource:expire Error java.sql.SQLException in StandardPoolDataSource:expire");
}
}
/**
* This method tests if a connection is closed or not
*/
public boolean checkThisObject(Object o) {
PooledConnection con;
Connection ret;
log.debug(
"StandardPoolDataSource:checkThisObject verify the current object");
try {
con = (PooledConnection) o;
ret = con.getConnection(); // get the connection from the pool
if (ret.isClosed()) {
return false;
}
try {
ret.close();
} catch (Exception e) {
log.error(
"StandardPoolDataSource:checkThisObject can't closed the connection: "
+ e);
}
return true;
} catch (java.sql.SQLException e) {
log.error(
"StandardPoolDataSource:checkThisObject Error java.sql.SQLException in StandardPoolDataSource:checkThisObject");
return false;
}
}
/**
* This method tests if a connection is valid or not
*/
public boolean testThisObject(Object o) {
Connection ret = null;
log.debug(
"StandardPoolDataSource:testThisObject verify the current object");
try {
PooledConnection con = (PooledConnection) o;
ret = con.getConnection();
Statement s = ret.createStatement();
s.execute(jdbcTestStmt);
s.close();
try {
ret.close();
} catch (Exception e) {
log.error(
"StandardPoolDataSource:checkThisObject can't closed the connection: "
+ e);
}
return true;
} catch (java.sql.SQLException e) {
log.error(
"StandardPoolDataSource:checkThisObject Error java.sql.SQLException in StandardPoolDataSource:testThisObject");
return false;
}
}
public GenerationObject create() throws SQLException {
return create(getUser(), getPassword());
}
public GenerationObject create(String _user, String _password)
throws SQLException {
log.debug(
"StandardPoolDataSource:create create a connection for the pool");
GenerationObject genObject;
PooledConnection pooledCon = cpds.getPooledConnection(_user, _password);
// get the pooled connection
pooledCon.addConnectionEventListener(this);
// add it to the event listener
log.debug("StandardPoolDataSource:create create a object for the pool");
genObject =
new GenerationObject(
pooledCon,
pool.getGeneration(),
_user,
_password);
return genObject; // return a connection
}
/**
* stop method to switch off the pool
*/
public void stopPool() {
pool.stop();
onOff = false;
log.debug("StandardPoolDataSource:stopPool stop now the pool");
}
public void shutdown(boolean force) {
stopPool();
}
/**
* set the logwriter for the current object, the logwriter will be use by
* the current object and by the generic pool
* @param logWriter a PrintWriter object
*/
public void setLogWriter(PrintWriter logWriter) {
pool.setLogger(log);
super.setLogger(log);
}
/**
* set the debug flag
* @param debug a boolean flag
*/
public void setDebug(boolean debug) {
super.setDebug(debug);
pool.setDebug(debug);
}
/**
* set the minimum size of the pool
* @param minSize minimum size of the pool
* @throws Exception
*/
public void setMinSize(int minSize) throws Exception {
pool.setMinSize(minSize);
}
/**
* set the maximum size of the pool
* @param maxSize maximum size of the pool
* @throws Exception
*/
public void setMaxSize(int maxSize) throws Exception {
pool.setMaxSize(maxSize);
}
/**
* set the life time of the pooled objects
* @param lifeTime life time of the pooled objects (in milliseconds)
*/
public void setLifeTime(long lifeTime) {
pool.setLifeTime(lifeTime);
}
/**
* set the sleep time of pooled objects
* @param sleepTime sleep time of the pooled objects (in milliseconds)
*/
public void setSleepTime(long sleepTime) {
pool.setSleepTime(sleepTime);
}
/**
* set the garbage collection option
* @param gc true: the garbage collector will be launched when clean up of the
* pool, else false
*/
public void setGC(boolean gc) {
pool.setGC(gc);
}
/**
* set the check level of the pooled object before using them
* @param checkLevelObject (<br>
* 0 = no special checking
* 1 = just a check on an object
* 2 = test the object
* 3 = just a check on an object (for all the objects)
* 4 = test the object (for all the objects)
*/
public void setCheckLevelObject(int checkLevelObject) {
pool.setCheckLevelObject(checkLevelObject);
}
/**
* set the String to test the jdbc connection before using it
* @param jdbcTestStmt an sql statement
*/
public void setJdbcTestStmt(String jdbcTestStmt) {
this.jdbcTestStmt = jdbcTestStmt;
}
/**
* set the generation number for future connection, the generation number
* is used to identify a group a created objects
* @param generation an integer value which represents a generation
*/
public void setGeneration(int generation) {
pool.setGeneration(generation);
}
/**
* set the global time the pool can wait for a free object
* @param deadLock in milliseconds
*/
public void setDeadLockMaxWait(long deadLock) {
pool.setDeadLockMaxWait(deadLock);
}
/**
* set the time before 2 tries when trying to obtain an object from the pool
* @param loopWait in milliseconds
*/
public void setDeadLockRetryWait(long loopWait) {
pool.setDeadLockRetryWait(loopWait);
}
public PrintWriter getLogWriter() {
return log;
}
public int getMinSize() {
return pool.getMinSize();
}
public int getMaxSize() {
return pool.getMaxSize();
}
public long getLifeTime() {
return pool.getLifeTime();
}
public long getSleepTime() {
return pool.getSleepTime();
}
public int getGeneration() {
return pool.generation;
}
public boolean isGC() {
return pool.isGC();
}
public int getLockedObjectCount() {
return pool.getLockedObjectCount();
}
public int getUnlockedObjectCount() {
return pool.getUnlockedObjectCount();
}
public int getCheckLevelObject() {
return pool.getCheckLevelObject();
}
public String getJdbcTestStmt() {
return jdbcTestStmt;
}
public long getDeadLockMaxWait() {
return pool.getDeadLockMaxWait();
}
public long getDeadLockRetryWait() {
return pool.getDeadLockRetryWait();
}
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("StandardPoolDataSource:\n");
sb.append(" data source name=<"+this.dataSourceName+">\n");
sb.append(" jdbc test stmt=<"+this.jdbcTestStmt+">\n");
sb.append(" user=<"+this.user+">\n");
if (this.cpds != null)
sb.append(this.cpds.toString());
sb.append(pool.toString());
return sb.toString();
}
/**
* Retrieves the Reference of this object. Used at binding time by JNDI
* to build a reference on this object.
*
* @return The non-null Reference of this object.
* @exception NamingException If a naming exception was encountered while
* retrieving the reference.
*/
public Reference getReference() throws NamingException {
log.debug(
"StandardPoolDataSource:getReference return a reference of the object");
Reference ref = super.getReference();
ref.add(
new StringRefAddr(
"checkLevelObject",
Integer.toString(getCheckLevelObject())));
ref.add(new StringRefAddr("lifeTime", Long.toString(getLifeTime())));
ref.add(new StringRefAddr("jdbcTestStmt", getJdbcTestStmt()));
ref.add(new StringRefAddr("maxSize", Integer.toString(getMaxSize())));
ref.add(new StringRefAddr("minSize", Integer.toString(getMinSize())));
ref.add(new StringRefAddr("dataSourceName", getDataSourceName()));
return ref;
}
/* (non-Javadoc)
* @see javax.naming.spi.ObjectFactory#getObjectInstance(java.lang.Object, javax.naming.Name, javax.naming.Context, java.util.Hashtable)
*/
public Object getObjectInstance(
Object refObj,
Name name,
Context nameCtx,
Hashtable env)
throws Exception {
super.getObjectInstance(refObj, name, nameCtx, env);
Reference ref = (Reference) refObj;
this.setLifeTime(
Long.parseLong((String) ref.get("lifeTime").getContent()));
this.setJdbcTestStmt((String) ref.get("jdbcTestStmt").getContent());
this.setMaxSize(
Integer.parseInt((String) ref.get("maxSize").getContent()));
this.setMinSize(
Integer.parseInt((String) ref.get("minSize").getContent()));
this.setDataSourceName((String) ref.get("dataSourceName").getContent());
InitialContext ictx = new InitialContext(env);
cpds = (ConnectionPoolDataSource) ictx.lookup(this.dataSourceName);
return this;
}
/**
* Override this so that the pool's tf gets set as well
*/
public void setThreadFactory(JdbcThreadFactory tf) {
super.setThreadFactory(tf);
pool.setThreadFactory(tf);
}
}