[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Donation if DataSource implementation
- To: mckoidb@xxxxxxxxx
- Subject: Donation if DataSource implementation
- From: Joel Rosi-Schwartz <Joel.Rosi-Schwartz@xxxxxxxxx>
- Date: Thu, 17 Jun 2004 16:10:59 +0100
- Cc: Tobias Downer <toby@xxxxxxxxx>
- Delivered-To: mailing list mckoidb@mckoi.com
- Mailing-List: contact mckoidb-help@mckoi.com; run by ezmlm
- Organization: Etish Limited
- User-Agent: KMail/1.5
Toby,
I have put together a DataSource implementation for Mckoidb. I have done basic
testing of it under Sun Application Server Platform Edition 8 and is seems to
work okay. I can not, however, warrant that this has been tested robustly.
Please feel free to use this as you please.
If you have any recommendations for improving the implementation, please let
me know.
Cheers,
Joel
/*
* Copyright 1999-2004 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.dbcp;
import java.io.PrintWriter;
import java.util.Properties;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.apache.commons.pool.impl.GenericKeyedObjectPool;
import org.apache.commons.pool.impl.GenericKeyedObjectPoolFactory;
import org.apache.commons.pool.impl.GenericObjectPool;
/**
* <p>Basic implementation of <code>javax.sql.DataSource</code> that is
* configured via JavaBeans properties. This is not the only way to
* combine the <em>commons-dbcp</em> and <em>commons-pool</em> packages,
* but provides a "one stop shopping" solution for basic requirements.</p>
*
* @author Glenn L. Nielsen
* @author Craig R. McClanahan
* @author Dirk Verbeeck
* @version $Revision: 1.37 $ $Date: 2004/06/09 18:21:23 $
*/
public class BasicDataSource implements DataSource {
// ------------------------------------------------------------- Properties
/**
* The default auto-commit state of connections created by this pool.
*/
protected boolean defaultAutoCommit = true;
public synchronized boolean getDefaultAutoCommit() {
return this.defaultAutoCommit;
}
public synchronized void setDefaultAutoCommit(boolean defaultAutoCommit) {
this.defaultAutoCommit = defaultAutoCommit;
this.restartNeeded = true;
}
/**
* The default read-only state of connections created by this pool.
*/
protected Boolean defaultReadOnly = null;
public synchronized boolean getDefaultReadOnly() {
if (this.defaultReadOnly != null) {
return this.defaultReadOnly.booleanValue();
}
return false;
}
public synchronized void setDefaultReadOnly(boolean defaultReadOnly) {
this.defaultReadOnly = defaultReadOnly ? Boolean.TRUE : Boolean.FALSE;
this.restartNeeded = true;
}
/**
* The default TransactionIsolation state of connections created by this pool.
*/
protected int defaultTransactionIsolation = PoolableConnectionFactory.UNKNOWN_TRANSACTIONISOLATION;
public synchronized int getDefaultTransactionIsolation() {
return this.defaultTransactionIsolation;
}
public synchronized void setDefaultTransactionIsolation(int defaultTransactionIsolation) {
this.defaultTransactionIsolation = defaultTransactionIsolation;
this.restartNeeded = true;
}
/**
* The default "catalog" of connections created by this pool.
*/
protected String defaultCatalog = null;
public synchronized String getDefaultCatalog() {
return this.defaultCatalog;
}
public synchronized void setDefaultCatalog(String defaultCatalog) {
if ((defaultCatalog != null) && (defaultCatalog.trim().length() > 0)) {
this.defaultCatalog = defaultCatalog;
}
else {
this.defaultCatalog = null;
}
this.restartNeeded = true;
}
/**
* The fully qualified Java class name of the JDBC driver to be used.
*/
protected String driverClassName = null;
public synchronized String getDriverClassName() {
return this.driverClassName;
}
public synchronized void setDriverClassName(String driverClassName) {
if ((driverClassName != null) && (driverClassName.trim().length() > 0)) {
this.driverClassName = driverClassName;
}
else {
this.driverClassName = null;
}
this.restartNeeded = true;
}
/**
* The maximum number of active connections that can be allocated from
* this pool at the same time, or zero for no limit.
*/
protected int maxActive = GenericObjectPool.DEFAULT_MAX_ACTIVE;
public synchronized int getMaxActive() {
return this.maxActive;
}
public synchronized void setMaxActive(int maxActive) {
this.maxActive = maxActive;
if (connectionPool != null) {
connectionPool.setMaxActive(maxActive);
}
}
/**
* The maximum number of active connections that can remain idle in the
* pool, without extra ones being released, or zero for no limit.
*/
protected int maxIdle = GenericObjectPool.DEFAULT_MAX_IDLE;;
public synchronized int getMaxIdle() {
return this.maxIdle;
}
public synchronized void setMaxIdle(int maxIdle) {
this.maxIdle = maxIdle;
if (connectionPool != null) {
connectionPool.setMaxIdle(maxIdle);
}
}
/**
* The minimum number of active connections that can remain idle in the
* pool, without extra ones being created, or 0 to create none.
*/
protected int minIdle = GenericObjectPool.DEFAULT_MIN_IDLE;;
public synchronized int getMinIdle() {
return this.minIdle;
}
public synchronized void setMinIdle(int minIdle) {
this.minIdle = minIdle;
if (connectionPool != null) {
connectionPool.setMinIdle(minIdle);
}
}
/**
* The initial number of connections that are created when the pool
* is started.
* @since 1.2
*/
protected int initialSize = 0;
public synchronized int getInitialSize() {
return this.initialSize;
}
public synchronized void setInitialSize(int initialSize) {
this.initialSize = initialSize;
this.restartNeeded = true;
}
/**
* The maximum number of milliseconds that the pool will wait (when there
* are no available connections) for a connection to be returned before
* throwing an exception, or -1 to wait indefinitely.
*/
protected long maxWait = GenericObjectPool.DEFAULT_MAX_WAIT;
public synchronized long getMaxWait() {
return this.maxWait;
}
public synchronized void setMaxWait(long maxWait) {
this.maxWait = maxWait;
if (connectionPool != null) {
connectionPool.setMaxWait(maxWait);
}
}
/**
* Prepared statement pooling for this pool.
*/
protected boolean poolPreparedStatements = false;
/**
* Returns true if we are pooling statements.
* @return boolean
*/
public synchronized boolean isPoolPreparedStatements() {
return this.poolPreparedStatements;
}
/**
* Sets whether to pool statements or not.
* @param poolPreparedStatements pooling on or off
*/
public synchronized void setPoolPreparedStatements(boolean poolingStatements) {
this.poolPreparedStatements = poolingStatements;
this.restartNeeded = true;
}
/**
* The maximum number of open statements that can be allocated from
* the statement pool at the same time, or zero for no limit. Since
* a connection usually only uses one or two statements at a time, this is
* mostly used to help detect resource leaks.
*/
protected int maxOpenPreparedStatements = GenericKeyedObjectPool.DEFAULT_MAX_TOTAL;
public synchronized int getMaxOpenPreparedStatements() {
return this.maxOpenPreparedStatements;
}
public synchronized void setMaxOpenPreparedStatements(int maxOpenStatements) {
this.maxOpenPreparedStatements = maxOpenStatements;
this.restartNeeded = true;
}
/**
* The indication of whether objects will be validated before being
* borrowed from the pool. If the object fails to validate, it will be
* dropped from the pool, and we will attempt to borrow another.
*/
protected boolean testOnBorrow = true;
public synchronized boolean getTestOnBorrow() {
return this.testOnBorrow;
}
public synchronized void setTestOnBorrow(boolean testOnBorrow) {
this.testOnBorrow = testOnBorrow;
if (connectionPool != null) {
connectionPool.setTestOnBorrow(testOnBorrow);
}
}
/**
* The indication of whether objects will be validated before being
* returned to the pool.
*/
protected boolean testOnReturn = false;
public synchronized boolean getTestOnReturn() {
return this.testOnReturn;
}
public synchronized void setTestOnReturn(boolean testOnReturn) {
this.testOnReturn = testOnReturn;
if (connectionPool != null) {
connectionPool.setTestOnReturn(testOnReturn);
}
}
/**
* The number of milliseconds to sleep between runs of the idle object
* evictor thread. When non-positive, no idle object evictor thread will
* be run.
*/
protected long timeBetweenEvictionRunsMillis =
GenericObjectPool.DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;
public synchronized long getTimeBetweenEvictionRunsMillis() {
return this.timeBetweenEvictionRunsMillis;
}
public synchronized void setTimeBetweenEvictionRunsMillis(long timeBetweenEvictionRunsMillis) {
this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
if (connectionPool != null) {
connectionPool.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
}
}
/**
* The number of objects to examine during each run of the idle object
* evictor thread (if any).
*/
protected int numTestsPerEvictionRun =
GenericObjectPool.DEFAULT_NUM_TESTS_PER_EVICTION_RUN;
public synchronized int getNumTestsPerEvictionRun() {
return this.numTestsPerEvictionRun;
}
public synchronized void setNumTestsPerEvictionRun(int numTestsPerEvictionRun) {
this.numTestsPerEvictionRun = numTestsPerEvictionRun;
if (connectionPool != null) {
connectionPool.setNumTestsPerEvictionRun(numTestsPerEvictionRun);
}
}
/**
* The minimum amount of time an object may sit idle in the pool before it
* is eligable for eviction by the idle object evictor (if any).
*/
protected long minEvictableIdleTimeMillis =
GenericObjectPool.DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS;
public synchronized long getMinEvictableIdleTimeMillis() {
return this.minEvictableIdleTimeMillis;
}
public synchronized void setMinEvictableIdleTimeMillis(long minEvictableIdleTimeMillis) {
this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
if (connectionPool != null) {
connectionPool.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
}
}
/**
* The indication of whether objects will be validated by the idle object
* evictor (if any). If an object fails to validate, it will be dropped
* from the pool.
*/
protected boolean testWhileIdle = false;
public synchronized boolean getTestWhileIdle() {
return this.testWhileIdle;
}
public synchronized void setTestWhileIdle(boolean testWhileIdle) {
this.testWhileIdle = testWhileIdle;
if (connectionPool != null) {
connectionPool.setTestWhileIdle(testWhileIdle);
}
}
/**
* [Read Only] The current number of active connections that have been
* allocated from this data source.
*/
public synchronized int getNumActive() {
if (connectionPool != null) {
return connectionPool.getNumActive();
} else {
return 0;
}
}
/**
* [Read Only] The current number of idle connections that are waiting
* to be allocated from this data source.
*/
public synchronized int getNumIdle() {
if (connectionPool != null) {
return connectionPool.getNumIdle();
} else {
return 0;
}
}
/**
* The connection password to be passed to our JDBC driver to establish
* a connection.
*/
protected String password = null;
public synchronized String getPassword() {
return this.password;
}
public synchronized void setPassword(String password) {
this.password = password;
this.restartNeeded = true;
}
/**
* The connection URL to be passed to our JDBC driver to establish
* a connection.
*/
protected String url = null;
public synchronized String getUrl() {
return this.url;
}
public synchronized void setUrl(String url) {
this.url = url;
this.restartNeeded = true;
}
/**
* The connection username to be passed to our JDBC driver to
* establish a connection.
*/
protected String username = null;
public synchronized String getUsername() {
return this.username;
}
public synchronized void setUsername(String username) {
this.username = username;
this.restartNeeded = true;
}
/**
* The SQL query that will be used to validate connections from this pool
* before returning them to the caller. If specified, this query
* <strong>MUST</strong> be an SQL SELECT statement that returns at least
* one row.
*/
protected String validationQuery = null;
public synchronized String getValidationQuery() {
return this.validationQuery;
}
public synchronized void setValidationQuery(String validationQuery) {
if ((validationQuery != null) && (validationQuery.trim().length() > 0)) {
this.validationQuery = validationQuery;
} else {
this.validationQuery = null;
}
this.restartNeeded = true;
}
/**
* Controls access to the underlying connection
*/
private boolean accessToUnderlyingConnectionAllowed = false;
/**
* Returns the value of the accessToUnderlyingConnectionAllowed property.
*
* @return true if access to the underlying is allowed, false otherwise.
*/
public synchronized boolean isAccessToUnderlyingConnectionAllowed() {
return this.accessToUnderlyingConnectionAllowed;
}
/**
* Sets the value of the accessToUnderlyingConnectionAllowed property.
* It controls if the PoolGuard allows access to the underlying connection.
* (Default: false)
*
* @param allow Access to the underlying connection is granted when true.
*/
public synchronized void setAccessToUnderlyingConnectionAllowed(boolean allow) {
this.accessToUnderlyingConnectionAllowed = allow;
this.restartNeeded = true;
}
// ----------------------------------------------------- Instance Variables
// TODO: review & make isRestartNeeded() public, restartNeeded protected
private boolean restartNeeded = false;
/**
* Returns whether or not a restart is needed.
* @return true if a restart is needed
*/
private synchronized boolean isRestartNeeded() {
return restartNeeded;
}
/**
* The object pool that internally manages our connections.
*/
protected GenericObjectPool connectionPool = null;
/**
* The connection properties that will be sent to our JDBC driver when
* establishing new connections. <strong>NOTE</strong> - The "user" and
* "password" properties will be passed explicitly, so they do not need
* to be included here.
*/
protected Properties connectionProperties = new Properties();
/**
* The data source we will use to manage connections. This object should
* be acquired <strong>ONLY</strong> by calls to the
* <code>createDataSource()</code> method.
*/
protected DataSource dataSource = null;
/**
* The PrintWriter to which log messages should be directed.
*/
protected PrintWriter logWriter = new PrintWriter(System.out);
// ----------------------------------------------------- DataSource Methods
/**
* Create (if necessary) and return a connection to the database.
*
* @exception SQLException if a database access error occurs
*/
public Connection getConnection() throws SQLException {
return createDataSource().getConnection();
}
/**
* Create (if necessary) and return a connection to the database.
*
* @param username Database user on whose behalf the Connection
* is being made
* @param password The database user's password
*
* @exception SQLException if a database access error occurs
*/
public Connection getConnection(String username, String password) throws SQLException {
return createDataSource().getConnection(username, password);
}
/**
* Return the login timeout (in seconds) for connecting to the database.
*
* @exception SQLException if a database access error occurs
*/
public int getLoginTimeout() throws SQLException {
return createDataSource().getLoginTimeout();
}
/**
* Return the log writer being used by this data source.
*
* @exception SQLException if a database access error occurs
*/
public PrintWriter getLogWriter() throws SQLException {
return createDataSource().getLogWriter();
}
/**
* Set the login timeout (in seconds) for connecting to the database.
*
* @param loginTimeout The new login timeout, or zero for no timeout
*
* @exception SQLException if a database access error occurs
*/
public void setLoginTimeout(int loginTimeout) throws SQLException {
createDataSource().setLoginTimeout(loginTimeout);
}
/**
* Set the log writer being used by this data source.
*
* @param logWriter The new log writer
*
* @exception SQLException if a database access error occurs
*/
public void setLogWriter(PrintWriter logWriter) throws SQLException {
createDataSource().setLogWriter(logWriter);
this.logWriter = logWriter;
}
private AbandonedConfig abandonedConfig;
/**
* Flag to remove abandoned connections if they exceed the
* removeAbandonedTimout.
*
* Set to true or false, default false.
* If set to true a connection is considered abandoned and eligible
* for removal if it has been idle longer than the removeAbandonedTimeout.
* Setting this to true can recover db connections from poorly written
* applications which fail to close a connection.
* @deprecated
*/
public boolean getRemoveAbandoned() {
if (abandonedConfig != null) {
return abandonedConfig.getRemoveAbandoned();
}
return false;
}
/**
* @deprecated
* @param removeAbandoned
*/
public void setRemoveAbandoned(boolean removeAbandoned) {
if (abandonedConfig == null) {
abandonedConfig = new AbandonedConfig();
}
abandonedConfig.setRemoveAbandoned(removeAbandoned);
this.restartNeeded = true;
}
/**
* Timeout in seconds before an abandoned connection can be removed.
*
* Defaults to 300 seconds.
* @deprecated
*/
public int getRemoveAbandonedTimeout() {
if (abandonedConfig != null) {
return abandonedConfig.getRemoveAbandonedTimeout();
}
return 300;
}
/**
* @deprecated
* @param removeAbandonedTimeout
*/
public void setRemoveAbandonedTimeout(int removeAbandonedTimeout) {
if (abandonedConfig == null) {
abandonedConfig = new AbandonedConfig();
}
abandonedConfig.setRemoveAbandonedTimeout(removeAbandonedTimeout);
this.restartNeeded = true;
}
/**
* Flag to log stack traces for application code which abandoned
* a Statement or Connection.
*
* Defaults to false.
*
* Logging of abandoned Statements and Connections adds overhead
* for every Connection open or new Statement because a stack
* trace has to be generated.
* @deprecated
*/
public boolean getLogAbandoned() {
if (abandonedConfig != null) {
return abandonedConfig.getLogAbandoned();
}
return false;
}
/**
* @deprecated
* @param logAbandoned
*/
public void setLogAbandoned(boolean logAbandoned) {
if (abandonedConfig == null) {
abandonedConfig = new AbandonedConfig();
}
abandonedConfig.setLogAbandoned(logAbandoned);
this.restartNeeded = true;
}
// --------------------------------------------------------- Public Methods
/**
* Add a custom connection property to the set that will be passed to our
* JDBC driver. This <strong>MUST</strong> be called before the first
* connection is retrieved (along with all the other configuration
* property setters).
*
* @param name Name of the custom connection property
* @param value Value of the custom connection property
*/
public void addConnectionProperty(String name, String value) {
connectionProperties.put(name, value);
this.restartNeeded = true;
}
public void removeConnectionProperty(String name) {
connectionProperties.remove(name);
this.restartNeeded = true;
}
/**
* Close and release all connections that are currently stored in the
* connection pool associated with our data source.
*
* @exception SQLException if a database error occurs
*/
public synchronized void close() throws SQLException {
GenericObjectPool oldpool = connectionPool;
connectionPool = null;
dataSource = null;
try {
if (oldpool != null) {
oldpool.close();
}
} catch(SQLException e) {
throw e;
} catch(RuntimeException e) {
throw e;
} catch(Exception e) {
throw new SQLNestedException("Cannot close connection pool", e);
}
}
// ------------------------------------------------------ Protected Methods
/**
* <p>Create (if necessary) and return the internal data source we are
* using to manage our connections.</p>
*
* <p><strong>IMPLEMENTATION NOTE</strong> - It is tempting to use the
* "double checked locking" idiom in an attempt to avoid synchronizing
* on every single call to this method. However, this idiom fails to
* work correctly in the face of some optimizations that are legal for
* a JVM to perform.</p>
*
* @exception SQLException if the object pool cannot be created.
*/
protected synchronized DataSource createDataSource()
throws SQLException {
// Return the pool if we have already created it
if (dataSource != null) {
return (dataSource);
}
// Load the JDBC driver class
if (driverClassName != null) {
try {
Class.forName(driverClassName);
} catch (Throwable t) {
String message = "Cannot load JDBC driver class '" +
driverClassName + "'";
logWriter.println(message);
t.printStackTrace(logWriter);
throw new SQLNestedException(message, t);
}
}
// Create a JDBC driver instance
Driver driver = null;
try {
driver = DriverManager.getDriver(url);
} catch (Throwable t) {
String message = "Cannot create JDBC driver of class '" +
(driverClassName != null ? driverClassName : "") +
"' for connect URL '" + url + "'";
logWriter.println(message);
t.printStackTrace(logWriter);
throw new SQLNestedException(message, t);
}
// Can't test without a validationQuery
if (validationQuery == null) {
setTestOnBorrow(false);
setTestOnReturn(false);
setTestWhileIdle(false);
}
// Create an object pool to contain our active connections
if ((abandonedConfig != null) && (abandonedConfig.getRemoveAbandoned() == true)) {
connectionPool = new AbandonedObjectPool(null,abandonedConfig);
}
else {
connectionPool = new GenericObjectPool();
}
connectionPool.setMaxActive(maxActive);
connectionPool.setMaxIdle(maxIdle);
connectionPool.setMinIdle(minIdle);
connectionPool.setMaxWait(maxWait);
connectionPool.setTestOnBorrow(testOnBorrow);
connectionPool.setTestOnReturn(testOnReturn);
connectionPool.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
connectionPool.setNumTestsPerEvictionRun(numTestsPerEvictionRun);
connectionPool.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
connectionPool.setTestWhileIdle(testWhileIdle);
// Set up statement pool, if desired
GenericKeyedObjectPoolFactory statementPoolFactory = null;
if (isPoolPreparedStatements()) {
statementPoolFactory = new GenericKeyedObjectPoolFactory(null,
-1, // unlimited maxActive (per key)
GenericKeyedObjectPool.WHEN_EXHAUSTED_FAIL,
0, // maxWait
1, // maxIdle (per key)
maxOpenPreparedStatements);
}
// Set up the driver connection factory we will use
if (username != null) {
connectionProperties.put("user", username);
} else {
log("DBCP DataSource configured without a 'username'");
}
if (password != null) {
connectionProperties.put("password", password);
} else {
log("DBCP DataSource configured without a 'password'");
}
DriverConnectionFactory driverConnectionFactory =
new DriverConnectionFactory(driver, url, connectionProperties);
// Set up the poolable connection factory we will use
PoolableConnectionFactory connectionFactory = null;
try {
connectionFactory =
new PoolableConnectionFactory(driverConnectionFactory,
connectionPool,
statementPoolFactory,
validationQuery,
defaultReadOnly,
defaultAutoCommit,
defaultTransactionIsolation,
defaultCatalog,
abandonedConfig);
if (connectionFactory == null) {
throw new SQLException("Cannot create PoolableConnectionFactory");
}
validateConnectionFactory(connectionFactory);
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new SQLNestedException("Cannot create PoolableConnectionFactory (" + e.getMessage() + ")", e);
}
// Create and return the pooling data source to manage the connections
dataSource = new PoolingDataSource(connectionPool);
((PoolingDataSource) dataSource).setAccessToUnderlyingConnectionAllowed(isAccessToUnderlyingConnectionAllowed());
dataSource.setLogWriter(logWriter);
try {
for (int i = 0 ; i < initialSize ; i++) {
connectionPool.addObject();
}
} catch (Exception e) {
throw new SQLNestedException("Error preloading the connection pool", e);
}
return dataSource;
}
private static void validateConnectionFactory(PoolableConnectionFactory connectionFactory) throws Exception {
Connection conn = null;
try {
conn = (Connection) connectionFactory.makeObject();
connectionFactory.activateObject(conn);
connectionFactory.validateConnection(conn);
connectionFactory.passivateObject(conn);
}
finally {
connectionFactory.destroyObject(conn);
}
}
private void restart() {
try {
close();
} catch (SQLException e) {
log("Could not restart DataSource, cause: " + e.getMessage());
}
}
private void log(String message) {
if (logWriter != null) {
logWriter.println(message);
}
}
}