serge 01/12/05 14:10:14
Added: src/java/org/apache/james/util/mordred JdbcDataSource.java
PoolConnEntry.java
Log:
Excalibur and Town create Mordred, a reliable database connection pooler in an
Avalon architecture.
Revision Changes Path
1.1
jakarta-james/src/java/org/apache/james/util/mordred/JdbcDataSource.java
Index: JdbcDataSource.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.util.mordred;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Vector;
import org.apache.avalon.framework.activity.Disposable;
import org.apache.avalon.framework.configuration.Configurable;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.logger.AbstractLoggable;
//Notice how cornerstone is dependent on Excalibur
import org.apache.avalon.excalibur.datasource.DataSourceComponent;
/**
* <p>This is a <b>reliable</b> DataSource implementation, based on the pooling
* logic written for <a href="http://www.whichever.com/">Town</a> and the
* configuration found in Avalon's excalibur code.
* </p>
* <p>This uses the normal <code>java.sql.Connection</code> object and
* <code>java.sql.DriverManager</code>. The Configuration is like this:
*
* <pre>
* <jdbc>
* <pool-controller min="<i>5</i>" max="<i>10</i>"
connection-class="<i>my.overrided.ConnectionClass</i>">
* <keep-alive>select 1</keep-alive>
* </pool-controller>
* <driver><i>com.database.jdbc.JdbcDriver</i></driver>
* <dburl><i>jdbc:driver://host/mydb</i></dburl>
* <user><i>username</i></user>
* <password><i>password</i></password>
* </jdbc>
* </pre>
*
* @author <a href="mailto:[EMAIL PROTECTED]">Serge Knystautas</a>
* @version CVS $Revision: 1.1 $ $Date: 2001/12/05 22:10:14 $
* @since 4.0
*/
public class JdbcDataSource
extends AbstractLoggable implements Configurable, Runnable, Disposable,
DataSourceComponent
{
/**
* Configure and set up DB connection. Here we set the connection
* information needed to create the Connection objects.
*
* @param conf The Configuration object needed to describe the
* connection.
*
* @throws ConfigurationException
*/
public void configure( final Configuration configuration )
throws ConfigurationException
{
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 {
getLogger().debug("Loading new driver: " + jdbcDriver);
Class.forName(jdbcDriver, true,
Thread.currentThread().getContextClassLoader());
} catch (ClassNotFoundException cnfe) {
throw new ConfigurationException("'" + jdbcDriver + "' could not be
found in classloader. Please specify a valid JDBC driver");
}
if (jdbcURL == null) {
throw new ConfigurationException("You need to specify a valid JDBC
connection string, e.g., <dburl>jdbc:driver:database</dburl>");
}
if (maxConn < 1) {
throw new ConfigurationException("Maximum number of connections
specified must be at least 1.");
}
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 Vector();
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 static int total_served = 0;
// The limit that an active connection can be running
public final static long ACTIVE_CONN_TIME_LIMIT = 60000; // (one minute)
// How long before you kill off a connection due to inactivity
public final static long CONN_IDLE_LIMIT = 600000; // (10 minutes)
// Thread that checks for dead/aged connections and removes them from pool
private Thread reaper;
// Flag to indicate whether reaper thread should run
private boolean reaperActive = false;
// Driver class
private String jdbcDriver;
// Server to connect to database (this really is the jdbc URL)
private String jdbcURL;
// Username to login to database
private String jdbcUsername;
// Password to login to database
private String jdbcPassword;
// Maximum number of connections to have open at any point
private int maxConn = -1;
// collection of connection objects
private Vector pool;
// connection number for like of this broker
private int connectionCount;
// This is a temporary variable used to track how many active threads
// are in createConnection(). This is to prevent to many connections
// from being opened at once.
private int connCreationsInProgress = 0;
// the last time a connection was created...
private long connLastCreated = 0;
// a SQL command to execute to see if the connection is still ok
private String verifyConnSQL;
// The error message is the conn pooler cannot serve connections for whatever
reason
private String connErrorMessage = null;
/**
* Creates a new connection as per these properties, adds it to the pool,
* and logs the creation.
*
* @return PoolConnEntry the new connection wrapped as an entry
*
* @throws SQLException
*/
private PoolConnEntry createConn() throws SQLException {
PoolConnEntry entry = null;
synchronized (this) {
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...
return null;
}
if (maxConn == -1 || pool.size() < maxConn) {
connCreationsInProgress++;
connLastCreated = now;
} else {
// We've already hit a limit... fail silently
getLogger().debug("Connection limit hit... " + pool.size() + " in
pool and " + connCreationsInProgress + " + on the way.");
return null;
}
}
try {
entry = new PoolConnEntry(this,
java.sql.DriverManager.getConnection(jdbcURL, jdbcUsername,
jdbcPassword),
++connectionCount);
getLogger().debug("Opening connection " + entry);
entry.lock();
pool.addElement(entry);
return entry;
} catch (SQLException sqle) {
//Shouldn't ever happen, but it did, just return null.
return null;
} finally {
synchronized (this) {
connCreationsInProgress--;
}
}
}
/**
* 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.
*
* @param java.sql.Connection
*/
public void releaseConnection(PoolConnEntry entry) {
//PoolConnEntry entry = findEntry(conn);
if (entry != null) {
entry.unlock();
return;
} else {
getLogger().warn("----> Could not find the connection to free!!!");
return;
}
}
/**
* 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.
*
* @param java.sql.Connection the connection that had problems
*
* @deprecated - No longer used in the new approach.
*/
public void killConnection(PoolConnEntry entry) {
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 = entry.createStatement();
stmt.execute(verifyConnSQL);
stmt.close();
// 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 {
getLogger().warn("----> Could not find connection to kill!!!");
new Throwable().printStackTrace();
return;
}
}
/**
* Closes a connection and removes it from the pool.
* @param PoolConnEntry entry
*/
private synchronized void finalizeEntry(PoolConnEntry entry) {
pool.removeElement(entry);
}
/**
* 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
*
* @return java.sql.Connection
*/
public Connection getConnection() throws SQLException {
//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++;
//System.out.println(new java.util.Date() + " trying to get a connection ("
+ count + ")");
for (int attempts = 1; attempts <= 100; attempts++) {
synchronized (pool) {
for (int i = 0; i < pool.size(); i++) {
PoolConnEntry entry = (PoolConnEntry)pool.elementAt(i);
//Set the appropriate flags to make this connection
//marked as in use
try {
if (entry.lock()) {
//System.out.println(new java.util.Date() + " return a
connection (" + count + ")");
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
try {
if (pool.size() == 0) {
//create a connection
PoolConnEntry entry = createConn();
if (entry != null) {
//System.out.println(new java.util.Date() + " returning new
connection (" + count + ")");
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 == 20 && (pool.size() < maxConn || maxConn == -1)) {
PoolConnEntry entry = createConn();
if (entry != null) {
//System.out.println(" returning new connection (" +
count + "(");
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);
getLogger().error(sout.toString());
}
//otherwise sleep 20ms 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.");
}
/**
* 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.
*/
public void run() {
while (reaperActive) {
for (int i = 0; i < pool.size(); i++) {
PoolConnEntry entry = (PoolConnEntry) pool.elementAt(i);
long age = System.currentTimeMillis()
- entry.getLastActivity();
synchronized (entry) {
if (entry.getStatus() == PoolConnEntry.ACTIVE &&
age > ACTIVE_CONN_TIME_LIMIT) {
getLogger().info(" ***** connection " + entry.getId() + " is
way too old: "
+ age + " > " + ACTIVE_CONN_TIME_LIMIT);
// This connection is way too old...
// kill it no matter what
finalizeEntry(entry);
continue;
}
if (entry.getStatus() == PoolConnEntry.AVAILABLE &&
age > CONN_IDLE_LIMIT) {
//We've got a connection that's too old... kill it
finalizeEntry(entry);
continue;
}
}
}
try {
// Check for activity every 5 seconds
Thread.sleep(5000L);
} catch (InterruptedException ex) {}
}
}
/**
* Close all connections. The connection pooler will recreate these connections
* if something starts requesting them again.
*
* @deprecated This was left over code from Town... but not exposed in Avalon.
*/
public synchronized void killAllConnections() {
//Just remove the references to all the connections... this will cause them
to get
// finalized before very long. (not an instant shutdown, but that's ok).
pool.clear();
}
/**
* Need to clean up all connections
*/
public void dispose() {
// 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
}
/*
* This is a real hack, but oh well for now
*/
protected void warn(String message) { getLogger().warn(message); }
protected void info(String message) { getLogger().info(message); }
protected void debug(String message) { getLogger().debug(message); }
}
1.1
jakarta-james/src/java/org/apache/james/util/mordred/PoolConnEntry.java
Index: PoolConnEntry.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.util.mordred;
import java.io.*;
import java.sql.*;
import java.util.*;
/**
* Insert the type's description here.
* Creation date: (8/24/99 11:41:10 AM)
* @author: Serge Knystautas <[EMAIL PROTECTED]>
*/
public class PoolConnEntry implements java.sql.Connection{
// States for connections (in use, being tested, or active)
public final static int AVAILABLE = 0;
public final static int ACTIVE = 1;
private JdbcDataSource container;
private Connection connection;
private int status;
private long lockTime;
private long createDate;
private long lastActivity;
private int id;
private java.lang.Throwable trace;
/**
* Insert the method's description here.
* Creation date: (8/24/99 11:43:45 AM)
* @param conn java.sql.Connection
*/
public PoolConnEntry(JdbcDataSource container, Connection conn, int id) {
this.container = container;
this.connection = conn;
status = AVAILABLE;
createDate = System.currentTimeMillis();
lastActivity = System.currentTimeMillis();
this.id = id;
}
/**
* Locks an entry for anybody else using it
*/
public synchronized boolean lock() throws SQLException {
if (status != PoolConnEntry.AVAILABLE) {
return false;
}
if (connection.isClosed()) {
throw new SQLException("Connection has been closed.");
}
status = PoolConnEntry.ACTIVE;
lockTime = System.currentTimeMillis();
lastActivity = lockTime;
trace = new Throwable();
clearWarnings();
return true;
}
/**
* Resets flags on an entry for reuse in the pool
*/
public synchronized void unlock() {
lastActivity = System.currentTimeMillis();
trace = null;
status = AVAILABLE;
}
/**
* Simple method to log any warnings on an entry (connection), and
* then clear them.
* @throws java.sql.SQLException
*/
public void clearWarnings() {
try {
SQLWarning currSQLWarning = connection.getWarnings();
while (currSQLWarning != null) {
container.debug("Warnings on connection " + id + " " +
currSQLWarning);
currSQLWarning = currSQLWarning.getNextWarning();
}
connection.clearWarnings();
} catch (SQLException sqle) {
container.debug("Error while clearing exceptions on " + id);
// It will probably get killed by itself before too long if this failed
}
}
/**
* Insert the method's description here.
* Creation date: (8/24/99 11:43:19 AM)
* @return long
*/
public long getCreateDate() {
return createDate;
}
/**
* Insert the method's description here.
* Creation date: (8/24/99 12:09:01 PM)
* @return int
*/
public int getId() {
return id;
}
/**
* Insert the method's description here.
* Creation date: (8/24/99 11:43:19 AM)
* @return long
*/
public long getLastActivity() {
return lastActivity;
}
/**
* Insert the method's description here.
* Creation date: (8/24/99 11:43:19 AM)
* @return long
*/
public long getLockTime() {
return lockTime;
}
/**
* Insert the method's description here.
* Creation date: (8/24/99 11:43:19 AM)
* @return int
*/
public int getStatus() {
return status;
}
/**
* Insert the method's description here.
* Creation date: (8/24/99 2:33:38 PM)
* @return java.lang.Throwable
*/
public java.lang.Throwable getTrace() {
return trace;
}
/**
* Need to clean up the connection
*/
protected void finalize() {
container.debug("Closing connection " + id);
try {
connection.close();
} catch (SQLException ex) {
container.warn("Cannot close connection " + id + " on finalize");
}
// Dump the stack trace of whoever created this connection
if (getTrace() != null) {
StringWriter sout = new StringWriter();
trace.printStackTrace(new PrintWriter(sout, true));
container.info(sout.toString());
}
}
public String getString() {
return getId() + ": " + connection.toString();
}
/*
* New approach now actually has this implement a connection, as a wrapper.
* All calls will be passed to underlying connection.
* Except when closed is called, which will instead cause the releaseConnection
on
* the parent to be executed.
*
* These are the methods from java.sql.Connection
*/
public void close() throws SQLException {
clearWarnings();
container.releaseConnection(this);
}
public boolean isClosed() throws SQLException {
return connection.isClosed();
}
public final Statement createStatement() throws SQLException {
return connection.createStatement();
}
public final PreparedStatement prepareStatement(final String sql) throws
SQLException {
return connection.prepareStatement(sql);
}
public final CallableStatement prepareCall(final String sql) throws SQLException
{
return connection.prepareCall(sql);
}
public final String nativeSQL(final String sql) throws SQLException {
return connection.nativeSQL( sql );
}
public final void setAutoCommit(final boolean autoCommit) throws SQLException {
connection.setAutoCommit( autoCommit );
}
public final boolean getAutoCommit() throws SQLException {
return connection.getAutoCommit();
}
public final void commit() throws SQLException {
connection.commit();
}
public final void rollback() throws SQLException {
connection.rollback();
}
public final DatabaseMetaData getMetaData() throws SQLException {
return connection.getMetaData();
}
public final void setReadOnly( final boolean readOnly ) throws SQLException {
connection.setReadOnly( readOnly );
}
public final boolean isReadOnly() throws SQLException {
return connection.isReadOnly();
}
public final void setCatalog( final String catalog ) throws SQLException {
connection.setCatalog( catalog );
}
public final String getCatalog() throws SQLException {
return connection.getCatalog();
}
public final void setTransactionIsolation( final int level ) throws SQLException
{
connection.setTransactionIsolation(level);
}
public final int getTransactionIsolation() throws SQLException {
return connection.getTransactionIsolation();
}
public final SQLWarning getWarnings() throws SQLException {
return connection.getWarnings();
}
public final Statement createStatement( final int resultSetType,
final int resultSetConcurrency )
throws SQLException {
return connection.createStatement(resultSetType, resultSetConcurrency);
}
public final PreparedStatement prepareStatement( final String sql,
final int resultSetType,
final int resultSetConcurrency )
throws SQLException {
return connection.prepareStatement( sql, resultSetType,
resultSetConcurrency);
}
public final CallableStatement prepareCall( final String sql,
final int resultSetType,
final int resultSetConcurrency )
throws SQLException {
return connection.prepareCall( sql, resultSetType, resultSetConcurrency );
}
public final Map getTypeMap() throws SQLException {
return connection.getTypeMap();
}
public final void setTypeMap( final Map map ) throws SQLException {
connection.setTypeMap( map );
}
/*
@JDBC3_START@
*/
/*
public final void setHoldability(int holdability)
throws SQLException
{
throw new SQLException("This is not a Jdbc 3.0 Compliant Connection");
}
public final int getHoldability()
throws SQLException
{
throw new SQLException("This is not a Jdbc 3.0 Compliant Connection");
}
public final java.sql.Savepoint setSavepoint()
throws SQLException
{
throw new SQLException("This is not a Jdbc 3.0 Compliant Connection");
}
public final java.sql.Savepoint setSavepoint(String savepoint)
throws SQLException
{
throw new SQLException("This is not a Jdbc 3.0 Compliant Connection");
}
public final void rollback(java.sql.Savepoint savepoint)
throws SQLException
{
throw new SQLException("This is not a Jdbc 3.0 Compliant Connection");
}
public final void releaseSavepoint(java.sql.Savepoint savepoint)
throws SQLException
{
throw new SQLException("This is not a Jdbc 3.0 Compliant Connection");
}
public final Statement createStatement(int resulSetType,
int resultSetConcurrency,
int resultSetHoldability)
throws SQLException
{
throw new SQLException("This is not a Jdbc 3.0 Compliant Connection");
}
public final PreparedStatement prepareStatement(String sql,
int resulSetType,
int resultSetConcurrency,
int resultSetHoldability)
throws SQLException
{
throw new SQLException("This is not a Jdbc 3.0 Compliant Connection");
}
public final CallableStatement prepareCall(String sql,
int resulSetType,
int resultSetConcurrency,
int resultSetHoldability)
throws SQLException
{
throw new SQLException("This is not a Jdbc 3.0 Compliant Connection");
}
public final PreparedStatement prepareStatement(String sql,
int autoGeneratedKeys)
throws SQLException
{
throw new SQLException("This is not a Jdbc 3.0 Compliant Connection");
}
public final PreparedStatement prepareStatement(String sql,
int[] columnIndexes)
throws SQLException
{
throw new SQLException("This is not a Jdbc 3.0 Compliant Connection");
}
public final PreparedStatement prepareStatement(String sql,
String[] columnNames)
throws SQLException
{
throw new SQLException("This is not a Jdbc 3.0 Compliant Connection");
}
*/
/*
@JDBC3_END@
*/
}
--
To unsubscribe, e-mail: <mailto:[EMAIL PROTECTED]>
For additional commands, e-mail: <mailto:[EMAIL PROTECTED]>