This is an automated email from the ASF dual-hosted git repository. ggregory pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-dbcp.git
The following commit(s) were added to refs/heads/master by this push: new 8a579d3 [DBCP-547] Add a ConnectionFactory class name setting for BasicDataSource.createConnectionFactory() #33. 8a579d3 is described below commit 8a579d304595853012ccf8c2bc93022c383a35bb Author: Gary Gregory <gardgreg...@gmail.com> AuthorDate: Mon Jul 8 11:13:58 2019 -0400 [DBCP-547] Add a ConnectionFactory class name setting for BasicDataSource.createConnectionFactory() #33. - Update version from 2.6.1-SNAPSHOT to 2.7.0 since this commits adds new public APIs in BasicDataSource. - Provide an alternative implementation from the PR https://github.com/apache/commons-dbcp/pull/33 WRT to String value handling. The handling of empty string for the new APIs is made consistent with other String APIs instead of what is done in PR 33. - Formatted new class TesterConnectionFactory differently from the PR and sorted its methods. - Closes #33. --- pom.xml | 4 +- src/changes/changes.xml | 7 +- .../org/apache/commons/dbcp2/BasicDataSource.java | 110 ++++++++++++++++----- .../commons/dbcp2/BasicDataSourceFactory.java | 11 ++- .../apache/commons/dbcp2/TestBasicDataSource.java | 45 ++++++++- .../commons/dbcp2/TesterConnectionFactory.java | 84 ++++++++++++++++ 6 files changed, 229 insertions(+), 32 deletions(-) diff --git a/pom.xml b/pom.xml index 399774a..3d9b8f8 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,7 @@ </parent> <modelVersion>4.0.0</modelVersion> <artifactId>commons-dbcp2</artifactId> - <version>2.6.1-SNAPSHOT</version> + <version>2.7.0-SNAPSHOT</version> <name>Apache Commons DBCP</name> <inceptionYear>2001</inceptionYear> @@ -293,7 +293,7 @@ <commons.rc.version>RC1</commons.rc.version> <commons.module.name>org.apache.commons.dbcp2</commons.module.name> - <commons.release.version>2.6.1</commons.release.version> + <commons.release.version>2.7.0</commons.release.version> <commons.release.desc>for JDBC 4.2 on Java 8</commons.release.desc> <commons.release.2.version>2.4.0</commons.release.2.version> diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 6815b40..53e40a6 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -60,7 +60,7 @@ The <action> type attribute can be add,update,fix,remove. --> <body> - <release version="2.6.1" date="2019-MM-DD" description="This is a minor release, including bug fixes and enhancements."> + <release version="2.7.0" date="2019-MM-DD" description="This is a minor release, including bug fixes and enhancements."> <action dev="jleroux" type="add" issue="DBCP-539" due-to="Jacques Le Roux"> ManagedDataSource#close() should declare used exceptions. </action> @@ -71,7 +71,7 @@ The <action> type attribute can be add,update,fix,remove. Update tests from H2 1.4.198 to 1.4.199. </action> <action dev="ggregory" type="update" issue="DBCP-540" due-to="emopers"> - Close ObjectOutputStream before calling toByteArray on underlying ByteArrayOutputStream #28. + Close ObjectOutputStream before calling toByteArray() on underlying ByteArrayOutputStream #28. </action> <action dev="ggregory" type="update" issue="DBCP-541" due-to="Allon Murienik"> Upgrade to JUnit Jupiter #19. @@ -85,6 +85,9 @@ The <action> type attribute can be add,update,fix,remove. <action dev="ggregory" type="update" issue="DBCP-546" due-to="Sergey Chupov"> Avoid NPE when calling DriverAdapterCPDS.toString(). </action> + <action dev="ggregory" type="update" issue="DBCP-547" due-to="leechoongyon, Gary Gregory"> + Add a ConnectionFactory class name setting for BasicDataSource.createConnectionFactory() #33. + </action> </release> <release version="2.6.0" date="2019-02-14" description="This is a minor release, including bug fixes and enhancements."> <action dev="chtompki" type="add" issue="DBCP-534" due-to="Peter Wicks"> diff --git a/src/main/java/org/apache/commons/dbcp2/BasicDataSource.java b/src/main/java/org/apache/commons/dbcp2/BasicDataSource.java index 7c46359..3a3d065 100644 --- a/src/main/java/org/apache/commons/dbcp2/BasicDataSource.java +++ b/src/main/java/org/apache/commons/dbcp2/BasicDataSource.java @@ -109,7 +109,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean } protected static void validateConnectionFactory(final PoolableConnectionFactory connectionFactory) - throws Exception { + throws Exception { PoolableConnection conn = null; PooledObject<PoolableConnection> p = null; try { @@ -315,6 +315,11 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean private volatile int validationQueryTimeoutSeconds = -1; /** + * The fully qualified Java class name of a {@link ConnectionFactory} implementation. + */ + private String connectionFactoryClassName; + + /** * These SQL statements run once after a Connection is created. * <p> * This property can be used for example to run ALTER SESSION SET NLS_SORT=XCYECH in an Oracle Database only once @@ -364,7 +369,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean * The PrintWriter to which log messages should be directed. */ private volatile PrintWriter logWriter = new PrintWriter( - new OutputStreamWriter(System.out, StandardCharsets.UTF_8)); + new OutputStreamWriter(System.out, StandardCharsets.UTF_8)); private AbandonedConfig abandonedConfig; @@ -504,7 +509,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean } } catch (final Exception t) { final String message = "Cannot create JDBC driver of class '" - + (driverClassName != null ? driverClassName : "") + "' for connect URL '" + url + "'"; + + (driverClassName != null ? driverClassName : "") + "' for connect URL '" + url + "'"; logWriter.println(message); t.printStackTrace(logWriter); throw new SQLException(message, t); @@ -526,9 +531,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean log("DBCP DataSource configured without a 'password'"); } - final ConnectionFactory driverConnectionFactory = new DriverConnectionFactory(driverToUse, url, - connectionProperties); - return driverConnectionFactory; + return createConnectionFactory(driverToUse); } /** @@ -683,10 +686,10 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean * @return a non-null instance */ protected GenericObjectPool<PoolableConnection> createObjectPool(final PoolableConnectionFactory factory, - final GenericObjectPoolConfig<PoolableConnection> poolConfig, final AbandonedConfig abandonedConfig) { + final GenericObjectPoolConfig<PoolableConnection> poolConfig, final AbandonedConfig abandonedConfig) { GenericObjectPool<PoolableConnection> gop; - if (abandonedConfig != null && (abandonedConfig.getRemoveAbandonedOnBorrow() - || abandonedConfig.getRemoveAbandonedOnMaintenance())) { + if (abandonedConfig != null + && (abandonedConfig.getRemoveAbandonedOnBorrow() || abandonedConfig.getRemoveAbandonedOnMaintenance())) { gop = new GenericObjectPool<>(factory, poolConfig, abandonedConfig); } else { gop = new GenericObjectPool<>(factory, poolConfig); @@ -706,11 +709,11 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean * @return A new PoolableConnectionFactory configured with the current configuration of this BasicDataSource */ protected PoolableConnectionFactory createPoolableConnectionFactory(final ConnectionFactory driverConnectionFactory) - throws SQLException { + throws SQLException { PoolableConnectionFactory connectionFactory = null; try { connectionFactory = new PoolableConnectionFactory(driverConnectionFactory, - ObjectNameWrapper.unwrap(registeredJmxObjectName)); + ObjectNameWrapper.unwrap(registeredJmxObjectName)); connectionFactory.setValidationQuery(validationQuery); connectionFactory.setValidationQueryTimeout(validationQueryTimeoutSeconds); connectionFactory.setConnectionInitSql(connectionInitSqls); @@ -991,6 +994,20 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean } /** + * Returns the ConnectionFactoryClassName that has been configured for use by this pool. + * <p> + * Note: This getter only returns the last value set by a call to + * {@link #setConnectionFactoryClassName(String)}. + * </p> + * + * @return the ConnectionFactoryClassName that has been configured for use by this pool. + * @since 2.7.0 + */ + public String getConnectionFactoryClassName() { + return this.connectionFactoryClassName; + } + + /** * Returns the value of the flag that controls whether or not connections being returned to the pool will be checked * and configured with {@link Connection#setAutoCommit(boolean) Connection.setAutoCommit(true)} if the auto commit * setting is {@code false} when the connection is returned. It is <code>true</code> by default. @@ -1505,7 +1522,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean poolableConnection = connection.unwrap(PoolableConnection.class); if (poolableConnection == null) { throw new IllegalStateException( - "Cannot invalidate connection: Connection is not a poolable connection."); + "Cannot invalidate connection: Connection is not a poolable connection."); } } catch (final SQLException e) { throw new IllegalStateException("Cannot invalidate connection: Unwrapping poolable connection failed.", e); @@ -1551,6 +1568,16 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean } /** + * Delegates in a null-safe manner to {@link String#isEmpty()}. + * + * @param value the string to test, may be null. + * @return boolean false if value is null, otherwise {@link String#isEmpty()}. + */ + private boolean isEmpty(String value) { + return value == null ? true : value.trim().isEmpty(); + } + + /** * Returns true if we are pooling statements. * * @return true if prepared and callable statements are pooled @@ -1723,7 +1750,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean if (connectionInitSqls != null && connectionInitSqls.size() > 0) { ArrayList<String> newVal = null; for (final String s : connectionInitSqls) { - if (s != null && s.trim().length() > 0) { + if (!isEmpty(s)) { if (newVal == null) { newVal = new ArrayList<>(); } @@ -1801,10 +1828,10 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean * the default catalog */ public void setDefaultCatalog(final String defaultCatalog) { - if (defaultCatalog != null && defaultCatalog.trim().length() > 0) { - this.defaultCatalog = defaultCatalog; - } else { + if (isEmpty(defaultCatalog)) { this.defaultCatalog = null; + } else { + this.defaultCatalog = defaultCatalog; } } @@ -1851,10 +1878,10 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean * @since 2.5.0 */ public void setDefaultSchema(final String defaultSchema) { - if (defaultSchema != null && defaultSchema.trim().length() > 0) { - this.defaultSchema = defaultSchema; - } else { + if (isEmpty(defaultSchema)) { this.defaultSchema = null; + } else { + this.defaultSchema = defaultSchema; } } @@ -1902,7 +1929,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean if (disconnectionSqlCodes != null && disconnectionSqlCodes.size() > 0) { HashSet<String> newVal = null; for (final String s : disconnectionSqlCodes) { - if (s != null && s.trim().length() > 0) { + if (!isEmpty(s)) { if (newVal == null) { newVal = new HashSet<>(); } @@ -1961,10 +1988,25 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean * the class name of the JDBC driver */ public synchronized void setDriverClassName(final String driverClassName) { - if (driverClassName != null && driverClassName.trim().length() > 0) { + if (isEmpty(driverClassName)) { + this.driverClassName = null; + } else { this.driverClassName = driverClassName; + } + } + + /** + * Sets the ConnectionFactory class name. + * + * @param connectionFactoryClassName + * @since 2.7.0 + */ + + public void setConnectionFactoryClassName(final String connectionFactoryClassName) { + if (isEmpty(connectionFactoryClassName)) { + this.connectionFactoryClassName = null; } else { - this.driverClassName = null; + this.connectionFactoryClassName = connectionFactoryClassName; } } @@ -2481,10 +2523,10 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean * the new value for the validation query */ public void setValidationQuery(final String validationQuery) { - if (validationQuery != null && validationQuery.trim().length() > 0) { - this.validationQuery = validationQuery; - } else { + if (isEmpty(validationQuery)) { this.validationQuery = null; + } else { + this.validationQuery = validationQuery; } } @@ -2527,4 +2569,22 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean config.setJmxNameBase(base.toString()); config.setJmxNamePrefix(Constants.JMX_CONNECTION_POOL_PREFIX); } + + private ConnectionFactory createConnectionFactory(final Driver driver) throws SQLException { + if (connectionFactoryClassName != null) { + try { + Class<?> connectionFactoryFromCCL = Class.forName(connectionFactoryClassName); + return (ConnectionFactory) connectionFactoryFromCCL + .getConstructor(Driver.class, String.class, Properties.class) + .newInstance(driver, url, connectionProperties); + } catch (final Exception t) { + final String message = "Cannot load ConnectionFactory implementation '" + connectionFactoryClassName + "'"; + logWriter.println(message); + t.printStackTrace(logWriter); + throw new SQLException(message, t); + } + } + return new DriverConnectionFactory(driver, url, connectionProperties); + } + } diff --git a/src/main/java/org/apache/commons/dbcp2/BasicDataSourceFactory.java b/src/main/java/org/apache/commons/dbcp2/BasicDataSourceFactory.java index dfaab5e..e826cff 100644 --- a/src/main/java/org/apache/commons/dbcp2/BasicDataSourceFactory.java +++ b/src/main/java/org/apache/commons/dbcp2/BasicDataSourceFactory.java @@ -87,6 +87,7 @@ public class BasicDataSourceFactory implements ObjectFactory { private static final String PROP_VALIDATION_QUERY = "validationQuery"; private static final String PROP_VALIDATION_QUERY_TIMEOUT = "validationQueryTimeout"; private static final String PROP_JMX_NAME = "jmxName"; + private static final String PROP_CONNECTION_FACTORY_CLASS_NAME = "connectionFactoryClassName"; /** * The property name for connectionInitSqls. The associated value String must be of the form [query;]* @@ -141,7 +142,8 @@ public class BasicDataSourceFactory implements ObjectFactory { PROP_REMOVE_ABANDONED_TIMEOUT, PROP_LOG_ABANDONED, PROP_ABANDONED_USAGE_TRACKING, PROP_POOL_PREPARED_STATEMENTS, PROP_MAX_OPEN_PREPARED_STATEMENTS, PROP_CONNECTION_PROPERTIES, PROP_MAX_CONN_LIFETIME_MILLIS, PROP_LOG_EXPIRED_CONNECTIONS, PROP_ROLLBACK_ON_RETURN, PROP_ENABLE_AUTO_COMMIT_ON_RETURN, - PROP_DEFAULT_QUERY_TIMEOUT, PROP_FAST_FAIL_VALIDATION, PROP_DISCONNECTION_SQL_CODES, PROP_JMX_NAME }; + PROP_DEFAULT_QUERY_TIMEOUT, PROP_FAST_FAIL_VALIDATION, PROP_DISCONNECTION_SQL_CODES, PROP_JMX_NAME, + PROP_CONNECTION_FACTORY_CLASS_NAME }; /** * Obsolete properties from DBCP 1.x. with warning strings suggesting new properties. LinkedHashMap will guarantee @@ -548,6 +550,13 @@ public class BasicDataSourceFactory implements ObjectFactory { dataSource.setDisconnectionSqlCodes(parseList(value, ',')); } + value = properties.getProperty(PROP_CONNECTION_FACTORY_CLASS_NAME); + if (value != null) { + dataSource.setConnectionFactoryClassName(value); + } + + + // DBCP-215 // Trick to make sure that initialSize connections are created if (dataSource.getInitialSize() > 0) { diff --git a/src/test/java/org/apache/commons/dbcp2/TestBasicDataSource.java b/src/test/java/org/apache/commons/dbcp2/TestBasicDataSource.java index aee9a65..732bdf0 100644 --- a/src/test/java/org/apache/commons/dbcp2/TestBasicDataSource.java +++ b/src/test/java/org/apache/commons/dbcp2/TestBasicDataSource.java @@ -17,8 +17,13 @@ package org.apache.commons.dbcp2; -import static org.junit.jupiter.api.Assertions.*; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import java.io.IOException; import java.lang.management.ManagementFactory; @@ -38,8 +43,8 @@ import org.apache.commons.logging.LogFactory; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; /** @@ -875,6 +880,42 @@ public class TestBasicDataSource extends TestConnectionPool { Assertions.assertNotEquals(Boolean.valueOf(original), Boolean.valueOf(ds.getConnectionPool().getLogAbandoned())); } + + /** + * JIRA: DBCP-547 + * Verify that ConnectionFactory interface in BasicDataSource.createConnectionFactory(). + */ + @Test + public void testCreateConnectionFactory() throws Exception { + + /** not set ConnectionFactoryClassName */ + Properties properties = new Properties(); + properties.put("initialSize", "1"); + properties.put("driverClassName", "org.apache.commons.dbcp2.TesterDriver"); + properties.put("url", "jdbc:apache:commons:testdriver"); + properties.put("username", "foo"); + properties.put("password", "bar"); + BasicDataSource ds = BasicDataSourceFactory.createDataSource(properties); + Connection conn = ds.getConnection(); + assertNotNull(conn); + conn.close(); + ds.close(); + + /** set ConnectionFactoryClassName */ + properties = new Properties(); + properties.put("initialSize", "1"); + properties.put("driverClassName", "org.apache.commons.dbcp2.TesterDriver"); + properties.put("url", "jdbc:apache:commons:testdriver"); + properties.put("username", "foo"); + properties.put("password", "bar"); + properties.put("connectionFactoryClassName", "org.apache.commons.dbcp2.TesterConnectionFactory"); + ds = BasicDataSourceFactory.createDataSource(properties); + conn = ds.getConnection(); + assertNotNull(conn); + + conn.close(); + ds.close(); + } } /** diff --git a/src/test/java/org/apache/commons/dbcp2/TesterConnectionFactory.java b/src/test/java/org/apache/commons/dbcp2/TesterConnectionFactory.java new file mode 100644 index 0000000..7718e02 --- /dev/null +++ b/src/test/java/org/apache/commons/dbcp2/TesterConnectionFactory.java @@ -0,0 +1,84 @@ +/* + * 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; + +import java.sql.Connection; +import java.sql.Driver; +import java.sql.SQLException; +import java.util.Properties; + +/** + * Dummy {@link ConnectionFactory} for testing purpose. + */ +public class TesterConnectionFactory implements ConnectionFactory { + + private final String connectionString; + private final Driver driver; + private final Properties properties; + + /** + * Constructs a connection factory for a given Driver. + * + * @param driver The Driver. + * @param connectString The connection string. + * @param properties The connection properties. + */ + public TesterConnectionFactory(final Driver driver, final String connectString, final Properties properties) { + this.driver = driver; + this.connectionString = connectString; + this.properties = properties; + } + + @Override + public Connection createConnection() throws SQLException { + Connection conn = driver.connect(connectionString, properties); + doSomething(conn); + return conn; + } + + private void doSomething(Connection conn) { + // do something + } + + /** + * @return The connection String. + */ + public String getConnectionString() { + return connectionString; + } + + /** + * @return The Driver. + */ + public Driver getDriver() { + return driver; + } + + /** + * @return The Properties. + */ + public Properties getProperties() { + return properties; + } + + @Override + public String toString() { + return this.getClass().getName() + " [" + String.valueOf(driver) + ";" + String.valueOf(connectionString) + ";" + + String.valueOf(properties) + "]"; + } +}