Skip to content

Commit

Permalink
Add Connection Validation and Pool configuration (#6233)
Browse files Browse the repository at this point in the history
* Add Connection Pool Validation (#6119)
(Cherrypick from 8a26d34)
* PoolingJndiDataSourceFactory shouldn't check for DataSource to be XA or not XA (#6187)
* Try to determine if a datasource already does pooling (#6199)

Only works for non-XA datasources currently, for XA datasources we need to still consider what is best to do as many XA drivers already implement pooling even if it's not configured.

(cherry picked from commit 281bf1e)
  • Loading branch information
tnleeuw authored Feb 5, 2024
1 parent 8c41786 commit c53a2da
Show file tree
Hide file tree
Showing 20 changed files with 284 additions and 207 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,15 @@
*/
package org.frankframework.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,12 @@
*/
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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,23 @@
*/
package org.frankframework.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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@
*/
package org.frankframework.jdbc.datasource;

import static org.frankframework.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
@@ -0,0 +1,40 @@
/*
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 org.frankframework.jndi;

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

public abstract class AbstractXADataSourceFactory extends PoolingJndiDataSourceFactory {

@Override
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, dataSourceName);
}
log.info("DataSource [{}] is not XA enabled and pooling not configured, used without augmentation", dataSourceName);
return (DataSource) dataSource;
}

protected abstract DataSource createXADataSource(XADataSource xaDataSource, String dataSourceName);
}
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,120 @@
/*
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 org.frankframework.jndi;

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

import javax.sql.CommonDataSource;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.DataSource;

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.frankframework.jdbc.datasource.OpenPoolingDataSource;
import org.frankframework.util.AppConstants;

import lombok.Getter;
import lombok.Setter;

/**
* 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);
}

@Override
protected DataSource augmentDatasource(CommonDataSource dataSource, String dataSourceName) {
if(maxPoolSize > 1) {
log.info("Creating connection pool for datasource [{}]", dataSourceName);
return createPool((DataSource)dataSource, dataSourceName);
}
log.info("Pooling not configured, using datasource [{}] without augmentation", dataSourceName);
return (DataSource) dataSource;
}

private static boolean isPooledDataSource(CommonDataSource dataSource) {
return dataSource instanceof ConnectionPoolDataSource
|| dataSource.getClass().getName().startsWith("org.apache.tomcat")
;
}

protected DataSource createPool(DataSource dataSource, String dataSourceName) {
if (isPooledDataSource(dataSource)) {
log.warn("DataSource [{}] already implements pooling. Will not be wrapped with DBCP2 pool. Frank!Framework connection pooling configuration is ignored, configure pooling properties in the JNDI Resource to avoid issues.", dataSourceName);
return 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
Loading

0 comments on commit c53a2da

Please sign in to comment.