JdbcDataSourcepublic class JdbcDataSource extends org.apache.avalon.framework.logger.AbstractLogEnabled implements org.apache.avalon.framework.activity.Disposable, org.apache.avalon.excalibur.datasource.DataSourceComponent, Runnable, org.apache.avalon.framework.configuration.Configurable
This is a reliable DataSource implementation, based on the pooling logic written for Town and the configuration found in Avalon's excalibur
code.
This uses the normal java.sql.Connection object and
java.sql.DriverManager . The Configuration is like this:
<jdbc>
<pool-controller min="5" max="10" connection-class="my.overrided.ConnectionClass">
<keep-alive>select 1</keep-alive>
</pool-controller>
<driver>com.database.jdbc.JdbcDriver</driver>
<dburl>jdbc:driver://host/mydb</dburl>
<user>username</user>
<password>password</password>
</jdbc>
|
Fields Summary |
---|
public static final long | ACTIVE_CONN_TIME_LIMIT | public static final long | ACTIVE_CONN_HARD_TIME_LIMIT | public static final long | CONN_IDLE_LIMIT | private static final boolean | DEEP_DEBUG | private static int | total_served | private int | connCreationsInProgress | private String | connErrorMessage | private long | connLastCreated | private int | connectionCount | private String | jdbcDriver | private String | jdbcPassword | private String | jdbcURL | private String | jdbcUsername | private int | maxConn | private ArrayList | pool | private Thread | reaper | private boolean | reaperActive | private String | verifyConnSQL |
Methods Summary |
---|
public void | configure(org.apache.avalon.framework.configuration.Configuration configuration)
try {
jdbcDriver = configuration.getChild("driver").getValue(null);
jdbcURL = configuration.getChild("dburl").getValue(null);
jdbcUsername = configuration.getChild("user").getValue(null);
jdbcPassword = configuration.getChild("password").getValue(null);
maxConn = configuration.getChild("max").getValueAsInteger(2);
//logfilename?
verifyConnSQL = configuration.getChild("keep-alive").getValue(null);
//Not support from Town: logfilename
//Not supporting from Excalibur: pool-controller, min, auto-commit, oradb, connection-class
if(jdbcDriver == null) {
throw new ConfigurationException("You need to specify a valid driver, e.g., <driver>my.class</driver>");
}
try {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Loading new driver: " + jdbcDriver);
}
// TODO: Figure out why this breaks when we change the Class.forName to
// a loadClass method call on the class loader.
// DO NOT MESS WITH THIS UNLESS YOU ARE WILLING TO TEST
// AND FIX THE PROBLEMS!
Class.forName(jdbcDriver, true, Thread.currentThread().getContextClassLoader());
// These variations do NOT work:
// getClass().getClassLoader().loadClass(jdbcDriver); -- DON'T USE -- BROKEN!!
// Thread.currentThread().getContextClassLoader().loadClass(jdbcDriver); -- DON'T USE -- BROKEN!!
} catch(ClassNotFoundException cnfe) {
StringBuffer exceptionBuffer =
new StringBuffer(128)
.append("'")
.append(jdbcDriver)
.append("' could not be found in classloader. Please specify a valid JDBC driver");
String exceptionMessage = exceptionBuffer.toString();
getLogger().error(exceptionMessage);
throw new ConfigurationException(exceptionMessage);
}
if(jdbcURL == null) {
throw new ConfigurationException("You need to specify a valid JDBC connection string, e.g., <dburl>jdbc:driver:database</dburl>");
}
if(maxConn < 0) {
throw new ConfigurationException("Maximum number of connections specified must be at least 1 (0 means no limit).");
}
if (getLogger().isDebugEnabled()) {
getLogger().debug("Starting connection pooler");
getLogger().debug("driver = " + jdbcDriver);
getLogger().debug("dburl = " + jdbcURL);
getLogger().debug("username = " + jdbcUsername);
//We don't show the password
getLogger().debug("max connections = " + maxConn);
}
pool = new ArrayList();
reaperActive = true;
reaper = new Thread(this);
reaper.setDaemon(true);
reaper.start();
} catch(ConfigurationException ce) {
//Let this pass through...
throw ce;
}
catch(Exception e) {
throw new ConfigurationException("Error configuring JdbcDataSource", e);
}
| private PoolConnEntry | createConn()Creates a new connection as per these properties, adds it to the pool, and logs the creation.
PoolConnEntry entry = null;
synchronized(pool) {
if(connCreationsInProgress > 0) {
//We are already creating one in another place
return null;
}
long now = System.currentTimeMillis();
if((now - connLastCreated) < (1000 * pool.size())) {
//We don't want to scale up too quickly...
if(DEEP_DEBUG) {
System.err.println("We don't want to scale up too quickly");
}
return null;
}
if((maxConn == 0) || (pool.size() < maxConn)) {
connCreationsInProgress++;
connLastCreated = now;
} else {
// We've already hit a limit... fail silently
if (getLogger().isDebugEnabled())
{
StringBuffer logBuffer =
new StringBuffer(128)
.append("Connection limit hit... ")
.append(pool.size())
.append(" in pool and ")
.append(connCreationsInProgress)
.append(" + on the way.");
getLogger().debug(logBuffer.toString());
}
return null;
}
try {
entry = new PoolConnEntry(this,
java.sql.DriverManager.getConnection(jdbcURL, jdbcUsername,
jdbcPassword),
++connectionCount);
if (getLogger().isDebugEnabled())
{
getLogger().debug("Opening connection " + entry);
}
entry.lock();
pool.add(entry);
return entry;
} catch(SQLException sqle) {
//Shouldn't ever happen, but it did, just return null.
// Exception from DriverManager.getConnection() - log it, and return null
StringWriter sout = new StringWriter();
PrintWriter pout = new PrintWriter(sout, true);
pout.println("Error creating connection: ");
sqle.printStackTrace(pout);
if (getLogger().isErrorEnabled()) {
getLogger().error(sout.toString());
}
return null;
} finally {
connCreationsInProgress--;
}
}
| protected void | debug(java.lang.String message)
getLogger().debug(message);
| public void | dispose()The dispose operation is called at the end of a components lifecycle.
Cleans up all JDBC connections.
// Stop the background monitoring thread
if(reaper != null) {
reaperActive = false;
//In case it's sleeping, help it quit faster
reaper.interrupt();
reaper = null;
}
// The various entries will finalize themselves once the reference
// is removed, so no need to do it here
| private void | finalizeEntry(PoolConnEntry entry)Closes a connection and removes it from the pool.
synchronized(pool) {
try {
entry.finalize();
} catch(Exception fe) {
}
pool.remove(entry);
}
| public java.sql.Connection | getConnection()Implements the ConnDefinition behavior when a connection is needed. Checks the pool of
connections to see if there is one available. If there is not and we are below the max
number of connections limit, it tries to create another connection. It retries this 10
times until a connection is available or can be created
//If the conn definition has a fatal connection problem, need to return that error
if(connErrorMessage != null) {
throw new SQLException(connErrorMessage);
}
//Look through our list of open connections right now, starting from beginning.
//If we find one, book it.
int count = total_served++;
if(DEEP_DEBUG) {
StringBuffer deepDebugBuffer =
new StringBuffer(128)
.append((new java.util.Date()).toString())
.append(" trying to get a connection (")
.append(count)
.append(")");
System.out.println(deepDebugBuffer.toString());
}
for(int attempts = 1; attempts <= 100; attempts++) {
synchronized(pool) {
for(int i = 0; i < pool.size(); i++) {
PoolConnEntry entry = (PoolConnEntry)pool.get(i);
//Set the appropriate flags to make this connection
//marked as in use
try {
if(entry.lock()) {
if(DEEP_DEBUG) {
StringBuffer deepDebugBuffer =
new StringBuffer(128)
.append((new java.util.Date()).toString())
.append(" return a connection (")
.append(count)
.append(")");
System.out.println(deepDebugBuffer.toString());
}
return entry;
}
} catch(SQLException se) {
//Somehow a closed connection appeared in our pool.
//Remove it immediately.
finalizeEntry(entry);
continue;
}
//we were unable to get a lock on this entry.. so continue through list
} //loop through existing connections
//If we have 0, create another
if(DEEP_DEBUG) {
System.out.println(pool.size());
}
try {
if(pool.size() == 0) {
//create a connection
PoolConnEntry entry = createConn();
if(entry != null) {
if(DEEP_DEBUG) {
StringBuffer deepDebugBuffer =
new StringBuffer(128)
.append((new java.util.Date()).toString())
.append(" returning new connection (")
.append(count)
.append(")");
System.out.println(deepDebugBuffer.toString());
}
return entry;
}
//looks like a connection was already created
} else {
//Since we didn't find one, and we have < max connections, then consider whether
// we create another
//if we've hit the 3rd attempt without getting a connection,
// let's create another to anticipate a slow down
if((attempts == 2) && (pool.size() < maxConn || maxConn == 0)) {
PoolConnEntry entry = createConn();
if(entry != null) {
if(DEEP_DEBUG) {
StringBuffer deepDebugBuffer =
new StringBuffer(32)
.append(" returning new connection (")
.append(count)
.append(")");
System.out.println(deepDebugBuffer.toString());
}
return entry;
} else {
attempts = 1;
}
}
}
} catch(SQLException sqle) {
//Ignore... error creating the connection
StringWriter sout = new StringWriter();
PrintWriter pout = new PrintWriter(sout, true);
pout.println("Error creating connection: ");
sqle.printStackTrace(pout);
if (getLogger().isErrorEnabled()) {
getLogger().error(sout.toString());
}
}
}
//otherwise sleep 50ms 10 times, then create a connection
try {
Thread.currentThread().sleep(50);
} catch(InterruptedException ie) {
}
}
// Give up... no connections available
throw new SQLException("Giving up... no connections available.");
| protected void | info(java.lang.String message)
getLogger().info(message);
| public void | killConnection(PoolConnEntry entry)Implements the ConnDefinition behavior when something bad has happened to a connection. If a
sql command was provided in the properties file, it will run this and attempt to determine
whether the connection is still valid. If it is, it recycles this connection back into the
pool. If it is not, it closes the connection.
if(entry != null) {
// if we were provided SQL to test the connection with, we will use
// this and possibly just release the connection after clearing warnings
if(verifyConnSQL != null) {
try {
// Test this connection
java.sql.Statement stmt = null;
try {
stmt = entry.createStatement();
stmt.execute(verifyConnSQL);
} finally {
try {
if (stmt != null) {
stmt.close();
}
} catch (SQLException sqle) {
// Failure to close ignored on test connection
}
}
// Passed test... recycle the entry
entry.unlock();
} catch(SQLException e1) {
// Failed test... close the entry
finalizeEntry(entry);
}
} else {
// No SQL was provided... we have to kill this entry to be sure
finalizeEntry(entry);
}
return;
} else {
if (getLogger().isWarnEnabled()) {
getLogger().warn("----> Could not find connection to kill!!!");
}
return;
}
| public void | releaseConnection(PoolConnEntry entry)Implements the ConnDefinition behavior when a connection is no longer needed. This resets
flags on the wrapper of the connection to allow others to use this connection.
//PoolConnEntry entry = findEntry(conn);
if(entry != null) {
entry.unlock();
return;
} else {
if (getLogger().isWarnEnabled()) {
getLogger().warn("----> Could not find the connection to free!!!");
}
return;
}
| public void | run()Background thread that checks if there are fewer connections open than minConn specifies,
and checks whether connections have been checked out for too long, killing them.
try {
while(reaperActive) {
synchronized(pool) {
for(int i = 0; i < pool.size(); i++) try {
PoolConnEntry entry = (PoolConnEntry)pool.get(i);
long age = System.currentTimeMillis() - entry.getLastActivity();
synchronized(entry) {
if((entry.getStatus() == PoolConnEntry.ACTIVE) &&
(age > ACTIVE_CONN_HARD_TIME_LIMIT)) {
StringBuffer logBuffer =
new StringBuffer(128)
.append(" ***** connection ")
.append(entry.getId())
.append(" is way too old: ")
.append(age)
.append(" > ")
.append(ACTIVE_CONN_HARD_TIME_LIMIT)
.append(" and will be closed.");
getLogger().info(logBuffer.toString());
// This connection is way too old...
// kill it no matter what
finalizeEntry(entry);
continue;
}
if((entry.getStatus() == PoolConnEntry.ACTIVE) &&
(age > ACTIVE_CONN_TIME_LIMIT)) {
StringBuffer logBuffer =
new StringBuffer(128)
.append(" ***** connection ")
.append(entry.getId())
.append(" is way too old: ")
.append(age)
.append(" > ")
.append(ACTIVE_CONN_TIME_LIMIT);
getLogger().info(logBuffer.toString());
// This connection is way too old...
// just log it for now.
continue;
}
if((entry.getStatus() == PoolConnEntry.AVAILABLE) &&
(age > CONN_IDLE_LIMIT)) {
//We've got a connection that's too old... kill it
finalizeEntry(entry);
continue;
}
}
}
catch (Throwable ex)
{
StringWriter sout = new StringWriter();
PrintWriter pout = new PrintWriter(sout, true);
pout.println("Reaper Error: ");
ex.printStackTrace(pout);
if (getLogger().isErrorEnabled()) {
getLogger().error(sout.toString());
}
}
}
try {
// Check for activity every 5 seconds
Thread.sleep(5000L);
} catch(InterruptedException ex) {
}
}
} finally {
Thread.currentThread().interrupted();
}
| protected void | warn(java.lang.String message)
getLogger().warn(message);
|
|