All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.commons.dbcp2.cpdsadapter.DriverAdapterCPDS Maven / Gradle / Ivy

There is a newer version: 2.12.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.dbcp2.cpdsadapter;

import java.io.PrintWriter;
import java.io.Serializable;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.Hashtable;
import java.util.Properties;
import java.util.logging.Logger;

import javax.naming.Context;
import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.RefAddr;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.naming.StringRefAddr;
import javax.naming.spi.ObjectFactory;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;

import org.apache.commons.dbcp2.DelegatingPreparedStatement;
import org.apache.commons.dbcp2.PStmtKey;
import org.apache.commons.dbcp2.Utils;
import org.apache.commons.pool2.KeyedObjectPool;
import org.apache.commons.pool2.impl.BaseObjectPoolConfig;
import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;

/**
 * 

* An adapter for JDBC drivers that do not include an implementation of {@link javax.sql.ConnectionPoolDataSource}, but * still include a {@link java.sql.DriverManager} implementation. ConnectionPoolDataSources are not used * within general applications. They are used by DataSource implementations that pool * Connections, such as {@link org.apache.commons.dbcp2.datasources.SharedPoolDataSource}. A J2EE container * will normally provide some method of initializing the ConnectionPoolDataSource whose attributes are * presented as bean getters/setters and then deploying it via JNDI. It is then available as a source of physical * connections to the database, when the pooling DataSource needs to create a new physical connection. *

*

* Although normally used within a JNDI environment, the DriverAdapterCPDS can be instantiated and initialized as any * bean and then attached directly to a pooling DataSource. Jdbc2PoolDataSource can use the * ConnectionPoolDataSource with or without the use of JNDI. *

*

* The DriverAdapterCPDS also provides PreparedStatement pooling which is not generally available in jdbc2 * ConnectionPoolDataSource implementation, but is addressed within the jdbc3 specification. The * PreparedStatement pool in DriverAdapterCPDS has been in the dbcp package for some time, but it has not * undergone extensive testing in the configuration used here. It should be considered experimental and can be toggled * with the poolPreparedStatements attribute. *

*

* The package documentation contains an example using catalina and JNDI. The * datasources package documentation shows how to use * DriverAdapterCPDS as a source for Jdbc2PoolDataSource without the use of JNDI. *

* * @since 2.0 */ public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceable, Serializable, ObjectFactory { private static final String KEY_USER = "user"; private static final String KEY_PASSWORD = "password"; private static final long serialVersionUID = -4820523787212147844L; private static final String GET_CONNECTION_CALLED = "A PooledConnection was already requested from this source, " + "further initialization is not allowed."; static { // Attempt to prevent deadlocks - see DBCP - 272 DriverManager.getDrivers(); } /** Description */ private String description; /** Url name */ private String url; /** User name */ private String userName; /** User password */ private char[] userPassword; /** Driver class name */ private String driver; /** Login TimeOut in seconds */ private int loginTimeout; /** Log stream. NOT USED */ private transient PrintWriter logWriter; // PreparedStatement pool properties private boolean poolPreparedStatements; private int maxIdle = 10; private long timeBetweenEvictionRunsMillis = BaseObjectPoolConfig.DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS; private int numTestsPerEvictionRun = -1; private int minEvictableIdleTimeMillis = -1; private int maxPreparedStatements = -1; /** Whether or not getConnection has been called */ private volatile boolean getConnectionCalled; /** Connection properties passed to JDBC Driver */ private Properties connectionProperties; /** * Controls access to the underlying connection */ private boolean accessToUnderlyingConnectionAllowed; /** * Default no-arg constructor for Serialization */ public DriverAdapterCPDS() { } /** * Throws an IllegalStateException, if a PooledConnection has already been requested. */ private void assertInitializationAllowed() throws IllegalStateException { if (getConnectionCalled) { throw new IllegalStateException(GET_CONNECTION_CALLED); } } private boolean getBooleanContentString(RefAddr ra) { return Boolean.valueOf(getStringContent(ra)).booleanValue(); } /** * Gets the connection properties passed to the JDBC driver. * * @return the JDBC connection properties used when creating connections. */ public Properties getConnectionProperties() { return connectionProperties; } /** * Gets the value of description. This property is here for use by the code which will deploy this datasource. It is * not used internally. * * @return value of description, may be null. * @see #setDescription(String) */ public String getDescription() { return description; } /** * Gets the driver class name. * * @return value of driver. */ public String getDriver() { return driver; } private int getIntegerStringContent(final RefAddr ra) { return Integer.parseInt(getStringContent(ra)); } /** * Gets the maximum time in seconds that this data source can wait while attempting to connect to a database. NOT * USED. */ @Override public int getLoginTimeout() { return loginTimeout; } /** * Gets the log writer for this data source. NOT USED. */ @Override public PrintWriter getLogWriter() { return logWriter; } /** * Gets the maximum number of statements that can remain idle in the pool, without extra ones being released, or * negative for no limit. * * @return the value of maxIdle */ public int getMaxIdle() { return this.maxIdle; } /** * Gets the maximum number of prepared statements. * * @return maxPrepartedStatements value */ public int getMaxPreparedStatements() { return maxPreparedStatements; } /** * Gets the minimum amount of time a statement may sit idle in the pool before it is eligible for eviction by the * idle object evictor (if any). * * @see #setMinEvictableIdleTimeMillis * @see #setTimeBetweenEvictionRunsMillis * @return the minimum amount of time a statement may sit idle in the pool. */ public int getMinEvictableIdleTimeMillis() { return minEvictableIdleTimeMillis; } /** * Gets the number of statements to examine during each run of the idle object evictor thread (if any.) * * @see #setNumTestsPerEvictionRun * @see #setTimeBetweenEvictionRunsMillis * @return the number of statements to examine during each run of the idle object evictor thread (if any.) */ public int getNumTestsPerEvictionRun() { return numTestsPerEvictionRun; } /** * Implements {@link ObjectFactory} to create an instance of this class */ @Override public Object getObjectInstance(final Object refObj, final Name name, final Context context, final Hashtable env) throws Exception { // The spec says to return null if we can't create an instance // of the reference DriverAdapterCPDS cpds = null; if (refObj instanceof Reference) { final Reference ref = (Reference) refObj; if (ref.getClassName().equals(getClass().getName())) { RefAddr ra = ref.get("description"); if (isNotEmpty(ra)) { setDescription(getStringContent(ra)); } ra = ref.get("driver"); if (isNotEmpty(ra)) { setDriver(getStringContent(ra)); } ra = ref.get("url"); if (isNotEmpty(ra)) { setUrl(getStringContent(ra)); } ra = ref.get(KEY_USER); if (isNotEmpty(ra)) { setUser(getStringContent(ra)); } ra = ref.get(KEY_PASSWORD); if (isNotEmpty(ra)) { setPassword(getStringContent(ra)); } ra = ref.get("poolPreparedStatements"); if (isNotEmpty(ra)) { setPoolPreparedStatements(getBooleanContentString(ra)); } ra = ref.get("maxIdle"); if (isNotEmpty(ra)) { setMaxIdle(getIntegerStringContent(ra)); } ra = ref.get("timeBetweenEvictionRunsMillis"); if (isNotEmpty(ra)) { setTimeBetweenEvictionRunsMillis(getIntegerStringContent(ra)); } ra = ref.get("numTestsPerEvictionRun"); if (isNotEmpty(ra)) { setNumTestsPerEvictionRun(getIntegerStringContent(ra)); } ra = ref.get("minEvictableIdleTimeMillis"); if (isNotEmpty(ra)) { setMinEvictableIdleTimeMillis(getIntegerStringContent(ra)); } ra = ref.get("maxPreparedStatements"); if (isNotEmpty(ra)) { setMaxPreparedStatements(getIntegerStringContent(ra)); } ra = ref.get("accessToUnderlyingConnectionAllowed"); if (isNotEmpty(ra)) { setAccessToUnderlyingConnectionAllowed(getBooleanContentString(ra)); } cpds = this; } } return cpds; } @Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { throw new SQLFeatureNotSupportedException(); } /** * Gets the value of password for the default user. * * @return value of password. */ public String getPassword() { return Utils.toString(userPassword); } /** * Gets the value of password for the default user. * * @return value of password. * @since 2.4.0 */ public char[] getPasswordCharArray() { return userPassword; } /** * Attempts to establish a database connection using the default user and password. */ @Override public PooledConnection getPooledConnection() throws SQLException { return getPooledConnection(getUser(), getPassword()); } /** * Attempts to establish a database connection. * * @param pooledUserName * name to be used for the connection * @param pooledUserPassword * password to be used fur the connection */ @Override public PooledConnection getPooledConnection(final String pooledUserName, final String pooledUserPassword) throws SQLException { getConnectionCalled = true; PooledConnectionImpl pooledConnection = null; // Workaround for buggy WebLogic 5.1 classloader - ignore the exception upon first invocation. try { if (connectionProperties != null) { update(connectionProperties, KEY_USER, pooledUserName); update(connectionProperties, KEY_PASSWORD, pooledUserPassword); pooledConnection = new PooledConnectionImpl( DriverManager.getConnection(getUrl(), connectionProperties)); } else { pooledConnection = new PooledConnectionImpl( DriverManager.getConnection(getUrl(), pooledUserName, pooledUserPassword)); } pooledConnection.setAccessToUnderlyingConnectionAllowed(isAccessToUnderlyingConnectionAllowed()); } catch (final ClassCircularityError e) { if (connectionProperties != null) { pooledConnection = new PooledConnectionImpl( DriverManager.getConnection(getUrl(), connectionProperties)); } else { pooledConnection = new PooledConnectionImpl( DriverManager.getConnection(getUrl(), pooledUserName, pooledUserPassword)); } pooledConnection.setAccessToUnderlyingConnectionAllowed(isAccessToUnderlyingConnectionAllowed()); } KeyedObjectPool stmtPool = null; if (isPoolPreparedStatements()) { final GenericKeyedObjectPoolConfig config = new GenericKeyedObjectPoolConfig<>(); config.setMaxTotalPerKey(Integer.MAX_VALUE); config.setBlockWhenExhausted(false); config.setMaxWaitMillis(0); config.setMaxIdlePerKey(getMaxIdle()); if (getMaxPreparedStatements() <= 0) { // since there is no limit, create a prepared statement pool with an eviction thread; // evictor settings are the same as the connection pool settings. config.setTimeBetweenEvictionRunsMillis(getTimeBetweenEvictionRunsMillis()); config.setNumTestsPerEvictionRun(getNumTestsPerEvictionRun()); config.setMinEvictableIdleTimeMillis(getMinEvictableIdleTimeMillis()); } else { // since there is a limit, create a prepared statement pool without an eviction thread; // pool has LRU functionality so when the limit is reached, 15% of the pool is cleared. // see org.apache.commons.pool2.impl.GenericKeyedObjectPool.clearOldest method config.setMaxTotal(getMaxPreparedStatements()); config.setTimeBetweenEvictionRunsMillis(-1); config.setNumTestsPerEvictionRun(0); config.setMinEvictableIdleTimeMillis(0); } stmtPool = new GenericKeyedObjectPool<>(pooledConnection, config); pooledConnection.setStatementPool(stmtPool); } return pooledConnection; } /** * Implements {@link Referenceable}. */ @Override public Reference getReference() throws NamingException { // this class implements its own factory final String factory = getClass().getName(); final Reference ref = new Reference(getClass().getName(), factory, null); ref.add(new StringRefAddr("description", getDescription())); ref.add(new StringRefAddr("driver", getDriver())); ref.add(new StringRefAddr("loginTimeout", String.valueOf(getLoginTimeout()))); ref.add(new StringRefAddr(KEY_PASSWORD, getPassword())); ref.add(new StringRefAddr(KEY_USER, getUser())); ref.add(new StringRefAddr("url", getUrl())); ref.add(new StringRefAddr("poolPreparedStatements", String.valueOf(isPoolPreparedStatements()))); ref.add(new StringRefAddr("maxIdle", String.valueOf(getMaxIdle()))); ref.add(new StringRefAddr("timeBetweenEvictionRunsMillis", String.valueOf(getTimeBetweenEvictionRunsMillis()))); ref.add(new StringRefAddr("numTestsPerEvictionRun", String.valueOf(getNumTestsPerEvictionRun()))); ref.add(new StringRefAddr("minEvictableIdleTimeMillis", String.valueOf(getMinEvictableIdleTimeMillis()))); ref.add(new StringRefAddr("maxPreparedStatements", String.valueOf(getMaxPreparedStatements()))); return ref; } private String getStringContent(RefAddr ra) { return ra.getContent().toString(); } /** * Gets 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. * * @return the value of the evictor thread timer * @see #setTimeBetweenEvictionRunsMillis(long) */ public long getTimeBetweenEvictionRunsMillis() { return timeBetweenEvictionRunsMillis; } /** * Gets the value of url used to locate the database for this datasource. * * @return value of url. */ public String getUrl() { return url; } /** * Gets the value of default user (login or user name). * * @return value of user. */ public String getUser() { return userName; } /** * 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; } private boolean isNotEmpty(RefAddr ra) { return ra != null && ra.getContent() != null; } /** * Whether to toggle the pooling of PreparedStatements * * @return value of poolPreparedStatements. */ public boolean isPoolPreparedStatements() { return poolPreparedStatements; } /** * 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(final boolean allow) { this.accessToUnderlyingConnectionAllowed = allow; } /** * Sets the connection properties passed to the JDBC driver. *

* If props contains "user" and/or "password" properties, the corresponding instance properties are * set. If these properties are not present, they are filled in using {@link #getUser()}, {@link #getPassword()} * when {@link #getPooledConnection()} is called, or using the actual parameters to the method call when * {@link #getPooledConnection(String, String)} is called. Calls to {@link #setUser(String)} or * {@link #setPassword(String)} overwrite the values of these properties if connectionProperties is not * null. *

* * @param props * Connection properties to use when creating new connections. * @throws IllegalStateException * if {@link #getPooledConnection()} has been called */ public void setConnectionProperties(final Properties props) { assertInitializationAllowed(); connectionProperties = props; if (connectionProperties != null) { if (connectionProperties.containsKey(KEY_USER)) { setUser(connectionProperties.getProperty(KEY_USER)); } if (connectionProperties.containsKey(KEY_PASSWORD)) { setPassword(connectionProperties.getProperty(KEY_PASSWORD)); } } } /** * Sets the value of description. This property is here for use by the code which will deploy this datasource. It is * not used internally. * * @param v * Value to assign to description. */ public void setDescription(final String v) { this.description = v; } /** * Sets the driver class name. Setting the driver class name cause the driver to be registered with the * DriverManager. * * @param v * Value to assign to driver. * @throws IllegalStateException * if {@link #getPooledConnection()} has been called * @throws ClassNotFoundException * if the class cannot be located */ public void setDriver(final String v) throws ClassNotFoundException { assertInitializationAllowed(); this.driver = v; // make sure driver is registered Class.forName(v); } /** * Sets the maximum time in seconds that this data source will wait while attempting to connect to a database. NOT * USED. */ @Override public void setLoginTimeout(final int seconds) { loginTimeout = seconds; } /** * Sets the log writer for this data source. NOT USED. */ @Override public void setLogWriter(final PrintWriter out) { logWriter = out; } /** * Gets the maximum number of statements that can remain idle in the pool, without extra ones being released, or * negative for no limit. * * @param maxIdle * The maximum number of statements that can remain idle * @throws IllegalStateException * if {@link #getPooledConnection()} has been called */ public void setMaxIdle(final int maxIdle) { assertInitializationAllowed(); this.maxIdle = maxIdle; } /** * Sets the maximum number of prepared statements. * * @param maxPreparedStatements * the new maximum number of prepared statements */ public void setMaxPreparedStatements(final int maxPreparedStatements) { this.maxPreparedStatements = maxPreparedStatements; } /** * Sets the minimum amount of time a statement may sit idle in the pool before it is eligible for eviction by the * idle object evictor (if any). When non-positive, no objects will be evicted from the pool due to idle time alone. * * @param minEvictableIdleTimeMillis * minimum time to set (in ms) * @see #getMinEvictableIdleTimeMillis() * @see #setTimeBetweenEvictionRunsMillis(long) * @throws IllegalStateException * if {@link #getPooledConnection()} has been called */ public void setMinEvictableIdleTimeMillis(final int minEvictableIdleTimeMillis) { assertInitializationAllowed(); this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis; } /** * Sets the number of statements to examine during each run of the idle object evictor thread (if any). *

* When a negative value is supplied, ceil({*link #numIdle})/abs({*link #getNumTestsPerEvictionRun}) tests * will be run. I.e., when the value is -n, roughly one nth of the idle objects will be tested per * run. *

* * @param numTestsPerEvictionRun * number of statements to examine per run * @see #getNumTestsPerEvictionRun() * @see #setTimeBetweenEvictionRunsMillis(long) * @throws IllegalStateException * if {@link #getPooledConnection()} has been called */ public void setNumTestsPerEvictionRun(final int numTestsPerEvictionRun) { assertInitializationAllowed(); this.numTestsPerEvictionRun = numTestsPerEvictionRun; } /** * Sets the value of password for the default user. * * @param userPassword * Value to assign to password. * @throws IllegalStateException * if {@link #getPooledConnection()} has been called */ public void setPassword(final char[] userPassword) { assertInitializationAllowed(); this.userPassword = Utils.clone(userPassword); update(connectionProperties, KEY_PASSWORD, Utils.toString(this.userPassword)); } /** * Sets the value of password for the default user. * * @param userPassword * Value to assign to password. * @throws IllegalStateException * if {@link #getPooledConnection()} has been called */ public void setPassword(final String userPassword) { assertInitializationAllowed(); this.userPassword = Utils.toCharArray(userPassword); update(connectionProperties, KEY_PASSWORD, userPassword); } /** * Whether to toggle the pooling of PreparedStatements * * @param poolPreparedStatements * true to pool statements. * @throws IllegalStateException * if {@link #getPooledConnection()} has been called */ public void setPoolPreparedStatements(final boolean poolPreparedStatements) { assertInitializationAllowed(); this.poolPreparedStatements = poolPreparedStatements; } /** * Sets 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. * * @param timeBetweenEvictionRunsMillis * 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. * @see #getTimeBetweenEvictionRunsMillis() * @throws IllegalStateException * if {@link #getPooledConnection()} has been called */ public void setTimeBetweenEvictionRunsMillis(final long timeBetweenEvictionRunsMillis) { assertInitializationAllowed(); this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis; } /** * Sets the value of URL string used to locate the database for this datasource. * * @param v * Value to assign to url. * @throws IllegalStateException * if {@link #getPooledConnection()} has been called */ public void setUrl(final String v) { assertInitializationAllowed(); this.url = v; } /** * Sets the value of default user (login or user name). * * @param v * Value to assign to user. * @throws IllegalStateException * if {@link #getPooledConnection()} has been called */ public void setUser(final String v) { assertInitializationAllowed(); this.userName = v; update(connectionProperties, KEY_USER, v); } /** * Does not print the userName and userPassword field nor the 'user' or 'password' in the connectionProperties. * * @since 2.6.0 */ @Override public synchronized String toString() { final StringBuilder builder = new StringBuilder(super.toString()); builder.append("[description="); builder.append(description); builder.append(", url="); // TODO What if the connection string contains a 'user' or 'password' query parameter but that connection string is not in a legal URL format? builder.append(url); builder.append(", driver="); builder.append(driver); builder.append(", loginTimeout="); builder.append(loginTimeout); builder.append(", poolPreparedStatements="); builder.append(poolPreparedStatements); builder.append(", maxIdle="); builder.append(maxIdle); builder.append(", timeBetweenEvictionRunsMillis="); builder.append(timeBetweenEvictionRunsMillis); builder.append(", numTestsPerEvictionRun="); builder.append(numTestsPerEvictionRun); builder.append(", minEvictableIdleTimeMillis="); builder.append(minEvictableIdleTimeMillis); builder.append(", maxPreparedStatements="); builder.append(maxPreparedStatements); builder.append(", getConnectionCalled="); builder.append(getConnectionCalled); builder.append(", connectionProperties="); Properties tmpProps = connectionProperties; final String pwdKey = "password"; if (connectionProperties != null && connectionProperties.contains(pwdKey)) { tmpProps = (Properties) connectionProperties.clone(); tmpProps.remove(pwdKey); } builder.append(tmpProps); builder.append(", accessToUnderlyingConnectionAllowed="); builder.append(accessToUnderlyingConnectionAllowed); builder.append("]"); return builder.toString(); } private void update(final Properties properties, final String key, final String value) { if (properties != null && key != null) { if (value == null) { properties.remove(key); } else { properties.setProperty(key, value); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy