Skip to content

Commit

Permalink
Add Connection Pool Validation (#6119)
Browse files Browse the repository at this point in the history
  • Loading branch information
tnleeuw authored Jan 29, 2024
1 parent 6644cd6 commit 8a26d34
Show file tree
Hide file tree
Showing 18 changed files with 246 additions and 205 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,15 @@
*/
package nl.nn.adapterframework.jdbc.datasource;

import javax.annotation.Nonnull;

import org.apache.commons.pool2.impl.GenericObjectPool;

public class GenericObjectPoolUtil {

private static final String CLOSE = "], ";

static void addPoolMetadata(GenericObjectPool pool, StringBuilder info) {
if (pool == null || info == null) {
return;
}
static void addPoolMetadata(@Nonnull GenericObjectPool<?> pool, @Nonnull StringBuilder info) {
info.append("DBCP2 Pool Info: ");
info.append("maxIdle [").append(pool.getMaxIdle()).append(CLOSE);
info.append("minIdle [").append(pool.getMinIdle()).append(CLOSE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@

import org.apache.commons.dbcp2.managed.ManagedDataSource;
import org.apache.commons.dbcp2.managed.TransactionRegistry;
import org.apache.commons.pool2.ObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPool;

/**
Expand All @@ -29,14 +28,17 @@
*/
public class OpenManagedDataSource<C extends Connection> extends ManagedDataSource<C> {

public OpenManagedDataSource(ObjectPool pool, TransactionRegistry transactionRegistry) {
public OpenManagedDataSource(final GenericObjectPool<C> pool, TransactionRegistry transactionRegistry) {
super(pool, transactionRegistry);
}

public void addPoolMetadata(StringBuilder info) {
ObjectPool<C> objectPool = super.getPool();
if (objectPool instanceof GenericObjectPool) {
GenericObjectPoolUtil.addPoolMetadata((GenericObjectPool<C>) objectPool, info);
}
@Override
protected GenericObjectPool<C> getPool() {
return (GenericObjectPool<C>) super.getPool();
}

@Override
public String toString() {
return "ManagedDataSource, DBCP2 Pool Info: " + super.getPool();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,28 @@
*/
package nl.nn.adapterframework.jdbc.datasource;

import java.sql.Connection;

import org.apache.commons.dbcp2.PoolingDataSource;
import org.apache.commons.pool2.ObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPool;

/**
* Extension of {@link PoolingDataSource} that exposes an extra method to fetch pool statistics.
*
* @param <C>
*/
public class OpenPoolingDataSource<C> extends PoolingDataSource {
public OpenPoolingDataSource(final ObjectPool<C> pool) {
public class OpenPoolingDataSource<C extends Connection> extends PoolingDataSource<C> {
public OpenPoolingDataSource(final GenericObjectPool<C> pool) {
super(pool);
}

public void addPoolMetadata(StringBuilder info) {
ObjectPool<C> objectPool = super.getPool();
if (objectPool instanceof GenericObjectPool) {
GenericObjectPoolUtil.addPoolMetadata((GenericObjectPool<C>) objectPool, info);
}
@Override
protected GenericObjectPool<C> getPool() {
return (GenericObjectPool<C>) super.getPool();
}

@Override
public String toString() {
return "PoolingDataSource, DBCP2 Pool Info: " + super.getPool();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@
*/
package nl.nn.adapterframework.jdbc.datasource;

import static nl.nn.adapterframework.jdbc.datasource.GenericObjectPoolUtil.addPoolMetadata;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

import javax.annotation.Nonnull;
import javax.sql.DataSource;

import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
Expand Down Expand Up @@ -113,30 +116,28 @@ public String getInfo() {
info.append("driver version [").append(metadata.get("driver-version")).append(CLOSE);
}

if (getTargetDataSource() instanceof OpenManagedDataSource) {
OpenManagedDataSource targetDataSource = (OpenManagedDataSource) getTargetDataSource();
targetDataSource.addPoolMetadata(info);
} else if (getTargetDataSource() instanceof org.apache.commons.dbcp2.PoolingDataSource) {
OpenPoolingDataSource dataSource = (OpenPoolingDataSource) getTargetDataSource();
dataSource.addPoolMetadata(info);
} else if (getTargetDataSource() instanceof PoolingDataSource) { // BTM instance
addBTMDatasourceInfo(info);
// TODO: Clean up this code more.
DataSource targetDataSource = getTargetDataSource();
if (targetDataSource instanceof OpenManagedDataSource) {
OpenManagedDataSource<?> dataSource = (OpenManagedDataSource<?>) targetDataSource;
addPoolMetadata(dataSource.getPool(), info);
} else if (targetDataSource instanceof OpenPoolingDataSource) {
OpenPoolingDataSource<?> dataSource = (OpenPoolingDataSource<?>) targetDataSource;
addPoolMetadata(dataSource.getPool(), info);
} else if (targetDataSource instanceof PoolingDataSource) { // BTM instance
PoolingDataSource dataSource = (PoolingDataSource) targetDataSource;
addBTMDatasourceInfo(dataSource, info);
}

info.append(" datasource [").append(obtainTargetDataSource().getClass().getName()).append("]");
return info.toString();
}

private void addBTMDatasourceInfo(StringBuilder info) {
PoolingDataSource dataSource = (PoolingDataSource) getTargetDataSource();
private void addBTMDatasourceInfo(@Nonnull PoolingDataSource dataSource, StringBuilder info) {
info.append("BTM Pool Info: ");
if (dataSource == null) {
return;
}
info.append("maxPoolSize [").append(dataSource.getMaxPoolSize()).append(CLOSE);
info.append("minPoolSize [").append(dataSource.getMinPoolSize()).append(CLOSE);
info.append("totalPoolSize [").append(dataSource.getTotalPoolSize()).append(CLOSE);
info.append("inPoolSize [").append(dataSource.getInPoolSize()).append(CLOSE);
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2021-2023 WeAreFrank!
Copyright 2021-2024 WeAreFrank!
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
Copyright 2024 WeAreFrank!
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 nl.nn.adapterframework.jndi;

import java.sql.Connection;
import java.time.Duration;

import javax.sql.CommonDataSource;
import javax.sql.DataSource;
import javax.sql.XADataSource;

import org.apache.commons.dbcp2.ConnectionFactory;
import org.apache.commons.dbcp2.DataSourceConnectionFactory;
import org.apache.commons.dbcp2.PoolableConnection;
import org.apache.commons.dbcp2.PoolableConnectionFactory;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.pool2.impl.GenericObjectPool;

import lombok.Getter;
import lombok.Setter;
import nl.nn.adapterframework.jdbc.datasource.OpenPoolingDataSource;
import nl.nn.adapterframework.util.AppConstants;

/**
* Factory through which (TX-enabled) Pooling DataSources can be retrieved.
*
* Already created DataSources are stored in a ConcurrentHashMap.
* Every DataSource can be augmented before it is added.
*/
public class PoolingJndiDataSourceFactory extends JndiDataSourceFactory {

public static final String DEFAULT_DATASOURCE_NAME_PROPERTY = "jdbc.datasource.default";
public static final String GLOBAL_DEFAULT_DATASOURCE_NAME = AppConstants.getInstance().getProperty(DEFAULT_DATASOURCE_NAME_PROPERTY);
@Getter @Setter protected int minPoolSize = 0;
@Getter @Setter protected int maxPoolSize = 20;
@Getter @Setter protected int maxIdle = 2;
@Getter @Setter protected int maxLifeTime = 0;
@Getter @Setter protected int connectionCheckInterval = 300;
@Getter @Setter protected String testQuery = null;

public PoolingJndiDataSourceFactory() {
super();
AppConstants appConstants = AppConstants.getInstance();
minPoolSize = appConstants.getInt("transactionmanager.jdbc.connection.minPoolSize", minPoolSize);
maxPoolSize = appConstants.getInt("transactionmanager.jdbc.connection.maxPoolSize", maxPoolSize);
maxIdle = appConstants.getInt("transactionmanager.jdbc.connection.maxIdle", maxIdle);
maxLifeTime = appConstants.getInt("transactionmanager.jdbc.connection.maxLifeTime", maxLifeTime);
connectionCheckInterval = appConstants.getInt("transactionmanager.jdbc.connection.checkInterval", connectionCheckInterval);
testQuery = appConstants.getString("transactionmanager.jdbc.connection.testQuery", testQuery);
}

protected DataSource augmentDatasource(CommonDataSource dataSource, String dataSourceName) {
if (dataSource instanceof XADataSource) {
log.info("DataSource [{}] is XA enabled, registering with a Transaction Manager", dataSourceName);
return createXADataSource((XADataSource) dataSource, dataSourceName);
}

if(maxPoolSize > 1) {
log.info("DataSource [{}] is not XA enabled, creating connection pool for the datasource", dataSourceName);
return createPool((DataSource)dataSource);
}
log.info("DataSource [{}] is not XA enabled and pooling not configured, used without augmentation", dataSourceName);
return (DataSource) dataSource;
}

protected DataSource createXADataSource(XADataSource xaDataSource, String dataSourceName) {
throw new UnsupportedOperationException("non-XA DataSourceFactory [" + this.getClass().getName() + "] cannot create XA-DataSources");
}

protected DataSource createPool(DataSource dataSource) {
ConnectionFactory cf = new DataSourceConnectionFactory(dataSource);
PoolableConnectionFactory poolableConnectionFactory = new PoolableConnectionFactory(cf, null);

GenericObjectPool<PoolableConnection> connectionPool = createConnectionPool(poolableConnectionFactory);
OpenPoolingDataSource<PoolableConnection> ds = new OpenPoolingDataSource<>(connectionPool);
log.info("registered PoolingDataSource [{}]", ds);
return ds;
}

protected GenericObjectPool<PoolableConnection> createConnectionPool(PoolableConnectionFactory poolableConnectionFactory) {
poolableConnectionFactory.setAutoCommitOnReturn(false);
poolableConnectionFactory.setDefaultTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
if (maxLifeTime > 0) {
poolableConnectionFactory.setMaxConn(Duration.ofSeconds(maxLifeTime));
}
poolableConnectionFactory.setRollbackOnReturn(true);
if (StringUtils.isNotBlank(testQuery)) {
poolableConnectionFactory.setValidationQuery(testQuery);
poolableConnectionFactory.setValidationQueryTimeout(Duration.ofSeconds(5));
}
poolableConnectionFactory.setFastFailValidation(true);
GenericObjectPool<PoolableConnection> connectionPool = new GenericObjectPool<>(poolableConnectionFactory);
connectionPool.setMinIdle(minPoolSize);
connectionPool.setMaxTotal(maxPoolSize);
connectionPool.setMaxIdle(maxIdle);
connectionPool.setTestOnBorrow(true);
connectionPool.setTestWhileIdle(true);
if (connectionCheckInterval > 0) {
connectionPool.setDurationBetweenEvictionRuns(Duration.ofSeconds(connectionCheckInterval));
}
connectionPool.setBlockWhenExhausted(true);
poolableConnectionFactory.setPool(connectionPool);
return connectionPool;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public BtmConnectionFactoryFactory() {

@Override
protected ConnectionFactory augment(ConnectionFactory connectionFactory, String connectionFactoryName) {
if (connectionFactory instanceof XAConnectionFactory) {
if (connectionFactory instanceof XAConnectionFactory && getMaxPoolSize() > 1) {
PoolingConnectionFactory result = new PoolingConnectionFactory();
result.setUniqueName(connectionFactoryName);
result.setMinPoolSize(getMinPoolSize());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2021-2023 WeAreFrank!
Copyright 2021-2024 WeAreFrank!
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand All @@ -15,81 +15,36 @@
*/
package nl.nn.adapterframework.jta.btm;

import java.sql.Connection;
import java.time.Duration;

import javax.sql.CommonDataSource;
import javax.sql.DataSource;
import javax.sql.XADataSource;

import org.apache.commons.dbcp2.ConnectionFactory;
import org.apache.commons.dbcp2.DataSourceConnectionFactory;
import org.apache.commons.dbcp2.PoolableConnection;
import org.apache.commons.dbcp2.PoolableConnectionFactory;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.jdbc.datasource.DelegatingDataSource;

import bitronix.tm.resource.jdbc.PoolingDataSource;
import lombok.Getter;
import lombok.Setter;
import nl.nn.adapterframework.jdbc.datasource.OpenPoolingDataSource;
import nl.nn.adapterframework.jndi.JndiDataSourceFactory;
import nl.nn.adapterframework.jndi.PoolingJndiDataSourceFactory;
import nl.nn.adapterframework.util.AppConstants;

public class BtmDataSourceFactory extends JndiDataSourceFactory implements DisposableBean {
public class BtmDataSourceFactory extends PoolingJndiDataSourceFactory implements DisposableBean {

private @Getter @Setter int minPoolSize = 0;
private @Getter @Setter int maxPoolSize = 20;
private @Getter @Setter int maxIdleTime = 60;
private @Getter @Setter int maxLifeTime = 0;
private @Getter @Setter String testQuery = null;

public BtmDataSourceFactory() {
// For backwards compatibility, apply these configuration constants if they're found.
AppConstants appConstants = AppConstants.getInstance();
minPoolSize = appConstants.getInt("transactionmanager.btm.jdbc.connection.minPoolSize", minPoolSize);
maxPoolSize = appConstants.getInt("transactionmanager.btm.jdbc.connection.maxPoolSize", maxPoolSize);
maxIdle = appConstants.getInt("transactionmanager.btm.jdbc.connection.maxIdle", maxIdle);
maxIdleTime = appConstants.getInt("transactionmanager.btm.jdbc.connection.maxIdleTime", maxIdleTime);
maxLifeTime = appConstants.getInt("transactionmanager.btm.jdbc.connection.maxLifeTime", maxLifeTime);
testQuery = appConstants.getString("transactionmanager.btm.jdbc.connection.testQuery", testQuery);
}

@Override
protected DataSource augmentDatasource(CommonDataSource dataSource, String dataSourceName) {
if (dataSource instanceof XADataSource) {
return createXAPool((XADataSource) dataSource, dataSourceName);
}

log.info("DataSource [{}] is not XA enabled, unable to register with an Transaction Manager", dataSourceName);
if(maxPoolSize > 1) {
return createPool((DataSource)dataSource);
}
return (DataSource) dataSource;
}

private DataSource createPool(DataSource dataSource) {
ConnectionFactory cf = new DataSourceConnectionFactory(dataSource);
PoolableConnectionFactory poolableConnectionFactory = new PoolableConnectionFactory(cf, null);

poolableConnectionFactory.setAutoCommitOnReturn(false);
poolableConnectionFactory.setDefaultTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
if (maxLifeTime > 0) {
poolableConnectionFactory.setMaxConn(Duration.ofSeconds(maxLifeTime));
}
poolableConnectionFactory.setRollbackOnReturn(true);
GenericObjectPool<PoolableConnection> connectionPool = new GenericObjectPool<>(poolableConnectionFactory);
connectionPool.setMinIdle(minPoolSize);
connectionPool.setMaxTotal(maxPoolSize);
connectionPool.setBlockWhenExhausted(true);
poolableConnectionFactory.setPool(connectionPool);

OpenPoolingDataSource<PoolableConnection> ds = new OpenPoolingDataSource<>(connectionPool);
log.info("registered PoolingDataSource [{}]", ds);
return ds;
}

private DataSource createXAPool(XADataSource dataSource, String dataSourceName) {
protected DataSource createXADataSource(XADataSource xaDataSource, String dataSourceName) {
PoolingDataSource result = new PoolingDataSource();
result.setUniqueName(dataSourceName);
result.setMinPoolSize(minPoolSize);
Expand All @@ -103,7 +58,7 @@ private DataSource createXAPool(XADataSource dataSource, String dataSourceName)
result.setEnableJdbc4ConnectionTest(true); //Assume everything uses JDBC4. BTM will test if isValid exists, to avoid unnecessary 'future' calls.

result.setAllowLocalTransactions(true);
result.setXaDataSource(dataSource);
result.setXaDataSource(xaDataSource);
result.init();

log.info("registered BTM DataSource [{}]", result);
Expand Down
Loading

0 comments on commit 8a26d34

Please sign in to comment.