relax pool dependency
This commit is contained in:
parent
844b288a18
commit
ac788cb95b
61 changed files with 12647 additions and 69 deletions
|
@ -1,3 +1,3 @@
|
||||||
group = org.xbib
|
group = org.xbib
|
||||||
name = database
|
name = database
|
||||||
version = 2.2.0
|
version = 2.3.0
|
||||||
|
|
|
@ -3,7 +3,6 @@ import org.xbib.jdbc.query.Flavor;
|
||||||
|
|
||||||
module org.xbib.jdbc.oracle {
|
module org.xbib.jdbc.oracle {
|
||||||
requires org.xbib.jdbc.query;
|
requires org.xbib.jdbc.query;
|
||||||
requires org.xbib.jdbc.connection.pool;
|
|
||||||
requires com.oracle.database.jdbc;
|
requires com.oracle.database.jdbc;
|
||||||
requires java.sql;
|
requires java.sql;
|
||||||
uses Flavor;
|
uses Flavor;
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package org.xbib.jdbc.oracle;
|
package org.xbib.jdbc.oracle;
|
||||||
|
|
||||||
import oracle.jdbc.OraclePreparedStatement;
|
import oracle.jdbc.OraclePreparedStatement;
|
||||||
import org.xbib.jdbc.connection.pool.ProxyPreparedStatement;
|
|
||||||
import org.xbib.jdbc.query.Flavor;
|
import org.xbib.jdbc.query.Flavor;
|
||||||
|
|
||||||
import java.sql.PreparedStatement;
|
import java.sql.PreparedStatement;
|
||||||
|
@ -45,7 +44,13 @@ public class Oracle implements Flavor {
|
||||||
return "binary_float";
|
return "binary_float";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void setFloat(PreparedStatement preparedStatement, int i, Float floatValue) throws SQLException {
|
||||||
|
if (preparedStatement instanceof OraclePreparedStatement) {
|
||||||
|
((OraclePreparedStatement) preparedStatement).setBinaryFloat(i, floatValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*@Override
|
||||||
public void setFloat(PreparedStatement preparedStatement, int i, Float floatValue) throws SQLException {
|
public void setFloat(PreparedStatement preparedStatement, int i, Float floatValue) throws SQLException {
|
||||||
if (preparedStatement instanceof ProxyPreparedStatement) {
|
if (preparedStatement instanceof ProxyPreparedStatement) {
|
||||||
ProxyPreparedStatement proxyPreparedStatement = (ProxyPreparedStatement) preparedStatement;
|
ProxyPreparedStatement proxyPreparedStatement = (ProxyPreparedStatement) preparedStatement;
|
||||||
|
@ -53,14 +58,20 @@ public class Oracle implements Flavor {
|
||||||
} else {
|
} else {
|
||||||
((OraclePreparedStatement) preparedStatement).setBinaryFloat(i, floatValue);
|
((OraclePreparedStatement) preparedStatement).setBinaryFloat(i, floatValue);
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String typeDouble() {
|
public String typeDouble() {
|
||||||
return "binary_double";
|
return "binary_double";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void setDouble(PreparedStatement preparedStatement, int i, Double doubleValue) throws SQLException {
|
||||||
|
if (preparedStatement instanceof OraclePreparedStatement) {
|
||||||
|
((OraclePreparedStatement) preparedStatement).setBinaryDouble(i, doubleValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*@Override
|
||||||
public void setDouble(PreparedStatement preparedStatement, int i, Double doubleValue) throws SQLException {
|
public void setDouble(PreparedStatement preparedStatement, int i, Double doubleValue) throws SQLException {
|
||||||
if (preparedStatement instanceof ProxyPreparedStatement) {
|
if (preparedStatement instanceof ProxyPreparedStatement) {
|
||||||
ProxyPreparedStatement proxyPreparedStatement = (ProxyPreparedStatement) preparedStatement;
|
ProxyPreparedStatement proxyPreparedStatement = (ProxyPreparedStatement) preparedStatement;
|
||||||
|
@ -68,7 +79,7 @@ public class Oracle implements Flavor {
|
||||||
} else {
|
} else {
|
||||||
((OraclePreparedStatement) preparedStatement).setBinaryDouble(i, doubleValue);
|
((OraclePreparedStatement) preparedStatement).setBinaryDouble(i, doubleValue);
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String typeBigDecimal(int size, int precision) {
|
public String typeBigDecimal(int size, int precision) {
|
||||||
|
|
13
jdbc-pool/src/main/java/module-info.java
Normal file
13
jdbc-pool/src/main/java/module-info.java
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
module org.xbib.jdbc.pool {
|
||||||
|
uses org.xbib.jdbc.pool.api.XbibDataSourceProvider;
|
||||||
|
requires java.security.jgss;
|
||||||
|
requires java.sql;
|
||||||
|
requires java.transaction.xa;
|
||||||
|
exports org.xbib.jdbc.pool.api;
|
||||||
|
exports org.xbib.jdbc.pool.api.cache;
|
||||||
|
exports org.xbib.jdbc.pool.api.configuration;
|
||||||
|
exports org.xbib.jdbc.pool.api.exceptionsorter;
|
||||||
|
exports org.xbib.jdbc.pool.api.security;
|
||||||
|
exports org.xbib.jdbc.pool.api.transaction;
|
||||||
|
exports org.xbib.jdbc.pool;
|
||||||
|
}
|
|
@ -0,0 +1,312 @@
|
||||||
|
package org.xbib.jdbc.pool;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.DriverManager;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Properties;
|
||||||
|
import javax.sql.XAConnection;
|
||||||
|
import org.xbib.jdbc.pool.api.XbibDataSourceListener;
|
||||||
|
import org.xbib.jdbc.pool.api.configuration.XbibConnectionFactoryConfiguration;
|
||||||
|
import org.xbib.jdbc.pool.api.security.XbibSecurityProvider;
|
||||||
|
import org.xbib.jdbc.pool.api.transaction.TransactionIntegration.ResourceRecoveryFactory;
|
||||||
|
import org.xbib.jdbc.pool.util.PropertyInjector;
|
||||||
|
import org.xbib.jdbc.pool.util.XAConnectionAdaptor;
|
||||||
|
import static org.xbib.jdbc.pool.api.configuration.XbibConnectionFactoryConfiguration.TransactionIsolation.UNDEFINED;
|
||||||
|
import static org.xbib.jdbc.pool.util.ListenerHelper.fireOnWarning;
|
||||||
|
|
||||||
|
public final class ConnectionFactory implements ResourceRecoveryFactory {
|
||||||
|
|
||||||
|
private static final String URL_PROPERTY_NAME = "url";
|
||||||
|
|
||||||
|
private static final Properties EMPTY_PROPERTIES = new Properties();
|
||||||
|
|
||||||
|
private final XbibConnectionFactoryConfiguration configuration;
|
||||||
|
private final XbibDataSourceListener[] listeners;
|
||||||
|
private final Properties jdbcProperties = new Properties(); // backup of jdbcProperties for DRIVER mode
|
||||||
|
private final Mode factoryMode;
|
||||||
|
|
||||||
|
// these are the sources for connections, that will be used depending on the mode
|
||||||
|
private java.sql.Driver driver;
|
||||||
|
private javax.sql.DataSource dataSource;
|
||||||
|
private javax.sql.XADataSource xaDataSource;
|
||||||
|
private javax.sql.XADataSource xaRecoveryDataSource;
|
||||||
|
|
||||||
|
private PropertyInjector injector;
|
||||||
|
private Integer defaultIsolationLevel;
|
||||||
|
|
||||||
|
public ConnectionFactory(XbibConnectionFactoryConfiguration configuration, XbibDataSourceListener... listeners) {
|
||||||
|
this.configuration = configuration;
|
||||||
|
this.listeners = listeners;
|
||||||
|
|
||||||
|
factoryMode = Mode.fromClass(configuration.connectionProviderClass());
|
||||||
|
switch (factoryMode) {
|
||||||
|
case XA_DATASOURCE:
|
||||||
|
injector = new PropertyInjector(configuration.connectionProviderClass());
|
||||||
|
Properties xaProperties = configuration.xaProperties().isEmpty() ? configuration.jdbcProperties() : configuration.xaProperties();
|
||||||
|
xaDataSource = newXADataSource(xaProperties);
|
||||||
|
xaRecoveryDataSource = newXADataSource(xaProperties);
|
||||||
|
break;
|
||||||
|
case DATASOURCE:
|
||||||
|
injector = new PropertyInjector(configuration.connectionProviderClass());
|
||||||
|
dataSource = newDataSource(configuration.jdbcProperties());
|
||||||
|
break;
|
||||||
|
case DRIVER:
|
||||||
|
driver = newDriver();
|
||||||
|
jdbcProperties.putAll(configuration.jdbcProperties());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int defaultJdbcIsolationLevel() {
|
||||||
|
return defaultIsolationLevel == null ? UNDEFINED.level() : defaultIsolationLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("StringConcatenation")
|
||||||
|
private javax.sql.XADataSource newXADataSource(Properties properties) {
|
||||||
|
javax.sql.XADataSource newDataSource;
|
||||||
|
try {
|
||||||
|
newDataSource = configuration.connectionProviderClass().asSubclass(javax.sql.XADataSource.class).getDeclaredConstructor().newInstance();
|
||||||
|
} catch (IllegalAccessException | InstantiationException | InvocationTargetException |
|
||||||
|
NoSuchMethodException e) {
|
||||||
|
throw new RuntimeException("Unable to instantiate javax.sql.XADataSource", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configuration.jdbcUrl() != null && !configuration.jdbcUrl().isEmpty()) {
|
||||||
|
injectUrlProperty(newDataSource, URL_PROPERTY_NAME, configuration.jdbcUrl());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
newDataSource.setLoginTimeout((int) configuration.loginTimeout().getSeconds());
|
||||||
|
} catch (SQLException e) {
|
||||||
|
fireOnWarning(listeners, "Unable to set login timeout: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
injectJdbcProperties(newDataSource, properties);
|
||||||
|
return newDataSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("StringConcatenation")
|
||||||
|
private javax.sql.DataSource newDataSource(Properties properties) {
|
||||||
|
javax.sql.DataSource newDataSource;
|
||||||
|
try {
|
||||||
|
newDataSource = configuration.connectionProviderClass().asSubclass(javax.sql.DataSource.class).getDeclaredConstructor().newInstance();
|
||||||
|
} catch (IllegalAccessException | InstantiationException | InvocationTargetException |
|
||||||
|
NoSuchMethodException e) {
|
||||||
|
throw new RuntimeException("Unable to instantiate javax.sql.DataSource", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configuration.jdbcUrl() != null && !configuration.jdbcUrl().isEmpty()) {
|
||||||
|
injectUrlProperty(newDataSource, URL_PROPERTY_NAME, configuration.jdbcUrl());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
newDataSource.setLoginTimeout((int) configuration.loginTimeout().getSeconds());
|
||||||
|
} catch (SQLException e) {
|
||||||
|
fireOnWarning(listeners, "Unable to set login timeout: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
injectJdbcProperties(newDataSource, properties);
|
||||||
|
return newDataSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("StringConcatenation")
|
||||||
|
private java.sql.Driver newDriver() {
|
||||||
|
DriverManager.setLoginTimeout((int) configuration.loginTimeout().getSeconds());
|
||||||
|
|
||||||
|
if (configuration.connectionProviderClass() == null) {
|
||||||
|
try {
|
||||||
|
return driver = DriverManager.getDriver(configuration.jdbcUrl());
|
||||||
|
} catch (SQLException sql) {
|
||||||
|
throw new RuntimeException("Unable to get java.sql.Driver from DriverManager", sql);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
driver = configuration.connectionProviderClass().asSubclass(java.sql.Driver.class).getDeclaredConstructor().newInstance();
|
||||||
|
if (!driver.acceptsURL(configuration.jdbcUrl())) {
|
||||||
|
fireOnWarning(listeners, "Driver does not support the provided URL: " + configuration.jdbcUrl());
|
||||||
|
}
|
||||||
|
return driver;
|
||||||
|
} catch (IllegalAccessException | InstantiationException | InvocationTargetException |
|
||||||
|
NoSuchMethodException e) {
|
||||||
|
throw new RuntimeException("Unable to instantiate java.sql.Driver", e);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new RuntimeException("Unable to verify that the java.sql.Driver supports the provided URL", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("StringConcatenation")
|
||||||
|
private void injectUrlProperty(Object target, String propertyName, String propertyValue) {
|
||||||
|
try {
|
||||||
|
injector.inject(target, propertyName, propertyValue);
|
||||||
|
} catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException |
|
||||||
|
NoSuchMethodException e) {
|
||||||
|
// AG-134 - Some drivers have setURL() instead of setUrl(), so we retry with upper case.
|
||||||
|
// AG-228 - Not all (XA)DataSource's require an url to be set
|
||||||
|
if (propertyName.chars().anyMatch(Character::isLowerCase)) {
|
||||||
|
injectUrlProperty(target, propertyName.toUpperCase(Locale.ROOT), propertyValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"ObjectAllocationInLoop", "StringConcatenation"})
|
||||||
|
private void injectJdbcProperties(Object target, Properties properties) {
|
||||||
|
boolean ignoring = false;
|
||||||
|
for (String propertyName : properties.stringPropertyNames()) {
|
||||||
|
try {
|
||||||
|
injector.inject(target, propertyName, properties.getProperty(propertyName));
|
||||||
|
} catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException |
|
||||||
|
NoSuchMethodException e) {
|
||||||
|
fireOnWarning(listeners, "Ignoring property '" + propertyName + "': " + e.getMessage());
|
||||||
|
ignoring = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ignoring) {
|
||||||
|
fireOnWarning(listeners, "Available properties " + Arrays.toString(injector.availableProperties().toArray()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
private Properties jdbcProperties() {
|
||||||
|
Properties properties = new Properties();
|
||||||
|
properties.putAll(jdbcProperties);
|
||||||
|
properties.putAll(securityProperties(configuration.principal(), configuration.credentials()));
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Properties recoveryProperties() {
|
||||||
|
Properties properties = new Properties();
|
||||||
|
if (hasRecoveryCredentials()) {
|
||||||
|
properties.putAll(securityProperties(configuration.recoveryPrincipal(), configuration.recoveryCredentials()));
|
||||||
|
} else {
|
||||||
|
// use the main credentials when recovery credentials are not provided
|
||||||
|
properties.putAll(securityProperties(configuration.principal(), configuration.credentials()));
|
||||||
|
}
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Properties securityProperties(Principal principal, Iterable<Object> credentials) {
|
||||||
|
Properties properties = new Properties();
|
||||||
|
properties.putAll(securityProperties(principal));
|
||||||
|
for (Object credential : credentials) {
|
||||||
|
properties.putAll(securityProperties(credential));
|
||||||
|
}
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Properties securityProperties(Object securityObject) {
|
||||||
|
if (securityObject == null) {
|
||||||
|
return EMPTY_PROPERTIES;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (XbibSecurityProvider provider : configuration.securityProviders()) {
|
||||||
|
Properties properties = provider.getSecurityProperties(securityObject);
|
||||||
|
if (properties != null) {
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fireOnWarning(listeners, "Unknown security object of type: " + securityObject.getClass().getName());
|
||||||
|
return EMPTY_PROPERTIES;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
public XAConnection createConnection() throws SQLException {
|
||||||
|
switch (factoryMode) {
|
||||||
|
case DRIVER:
|
||||||
|
return new XAConnectionAdaptor(connectionSetup(driver.connect(configuration.jdbcUrl(), jdbcProperties())));
|
||||||
|
case DATASOURCE:
|
||||||
|
injectJdbcProperties(dataSource, securityProperties(configuration.principal(), configuration.credentials()));
|
||||||
|
return new XAConnectionAdaptor(connectionSetup(dataSource.getConnection()));
|
||||||
|
case XA_DATASOURCE:
|
||||||
|
injectJdbcProperties(xaDataSource, securityProperties(configuration.principal(), configuration.credentials()));
|
||||||
|
return xaConnectionSetup(xaDataSource.getXAConnection());
|
||||||
|
default:
|
||||||
|
throw new SQLException("Unknown connection factory mode");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("MagicConstant")
|
||||||
|
private Connection connectionSetup(Connection connection) throws SQLException {
|
||||||
|
if (connection == null) {
|
||||||
|
// AG-90: Driver can return null if the URL is not supported (see java.sql.Driver#connect() documentation)
|
||||||
|
throw new SQLException("Driver does not support the provided URL: " + configuration.jdbcUrl());
|
||||||
|
}
|
||||||
|
|
||||||
|
connection.setAutoCommit(configuration.autoCommit());
|
||||||
|
if (configuration.jdbcTransactionIsolation().isDefined()) {
|
||||||
|
connection.setTransactionIsolation(configuration.jdbcTransactionIsolation().level());
|
||||||
|
} else if (defaultIsolationLevel == null) {
|
||||||
|
defaultIsolationLevel = connection.getTransactionIsolation();
|
||||||
|
}
|
||||||
|
if (configuration.initialSql() != null && !configuration.initialSql().isEmpty()) {
|
||||||
|
try (Statement statement = connection.createStatement()) {
|
||||||
|
statement.execute(configuration.initialSql());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
private XAConnection xaConnectionSetup(XAConnection xaConnection) throws SQLException {
|
||||||
|
if (xaConnection.getXAResource() == null) {
|
||||||
|
// Make sure that XAConnections are not processed as non-XA connections by the pool
|
||||||
|
xaConnection.close();
|
||||||
|
throw new SQLException("null XAResource from XADataSource");
|
||||||
|
}
|
||||||
|
try (Connection connection = xaConnection.getConnection()) {
|
||||||
|
connectionSetup(connection);
|
||||||
|
}
|
||||||
|
return xaConnection;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
public boolean hasRecoveryCredentials() {
|
||||||
|
return configuration.recoveryPrincipal() != null || (configuration.recoveryCredentials() != null && !configuration.recoveryCredentials().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRecoverable() {
|
||||||
|
if (factoryMode == Mode.XA_DATASOURCE) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
fireOnWarning(listeners, "Recovery connections are only available for XADataSource");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public XAConnection getRecoveryConnection() throws SQLException {
|
||||||
|
if (isRecoverable()) {
|
||||||
|
injectJdbcProperties(xaRecoveryDataSource, recoveryProperties());
|
||||||
|
return xaRecoveryDataSource.getXAConnection();
|
||||||
|
}
|
||||||
|
// Fallback for wrong implemented TransactionIntegration
|
||||||
|
throw new SQLException("Recovery connections are only available for XADataSource");
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
private enum Mode {
|
||||||
|
|
||||||
|
DRIVER, DATASOURCE, XA_DATASOURCE;
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
static Mode fromClass(Class<?> providerClass) {
|
||||||
|
if (providerClass == null) {
|
||||||
|
return DRIVER;
|
||||||
|
} else if (javax.sql.XADataSource.class.isAssignableFrom(providerClass)) {
|
||||||
|
return XA_DATASOURCE;
|
||||||
|
} else if (javax.sql.DataSource.class.isAssignableFrom(providerClass)) {
|
||||||
|
return DATASOURCE;
|
||||||
|
} else if (java.sql.Driver.class.isAssignableFrom(providerClass)) {
|
||||||
|
return DRIVER;
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Unable to create ConnectionFactory from providerClass " + providerClass.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,401 @@
|
||||||
|
package org.xbib.jdbc.pool;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.SQLWarning;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
||||||
|
import javax.sql.XAConnection;
|
||||||
|
import javax.transaction.xa.XAResource;
|
||||||
|
import org.xbib.jdbc.pool.api.cache.Acquirable;
|
||||||
|
import org.xbib.jdbc.pool.api.configuration.XbibConnectionFactoryConfiguration;
|
||||||
|
import org.xbib.jdbc.pool.api.configuration.XbibConnectionPoolConfiguration;
|
||||||
|
import org.xbib.jdbc.pool.api.transaction.TransactionAware;
|
||||||
|
import org.xbib.jdbc.pool.util.AutoCloseableElement;
|
||||||
|
import org.xbib.jdbc.pool.util.UncheckedArrayList;
|
||||||
|
import org.xbib.jdbc.pool.wrapper.ConnectionWrapper;
|
||||||
|
import org.xbib.jdbc.pool.wrapper.XAConnectionWrapper;
|
||||||
|
import static java.lang.System.nanoTime;
|
||||||
|
import static java.lang.Thread.currentThread;
|
||||||
|
import static java.util.Arrays.copyOfRange;
|
||||||
|
import static java.util.EnumSet.noneOf;
|
||||||
|
import static java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater;
|
||||||
|
import static org.xbib.jdbc.pool.ConnectionHandler.DirtyAttribute.AUTOCOMMIT;
|
||||||
|
import static org.xbib.jdbc.pool.ConnectionHandler.DirtyAttribute.TRANSACTION_ISOLATION;
|
||||||
|
import static org.xbib.jdbc.pool.util.ListenerHelper.fireOnWarning;
|
||||||
|
|
||||||
|
public final class ConnectionHandler implements TransactionAware, Acquirable {
|
||||||
|
|
||||||
|
private static final AtomicReferenceFieldUpdater<ConnectionHandler, State> stateUpdater = newUpdater(ConnectionHandler.class, State.class, "state");
|
||||||
|
|
||||||
|
private static final SQLCallable<Boolean> NO_ACTIVE_TRANSACTION = () -> false;
|
||||||
|
|
||||||
|
private final XAConnection xaConnection;
|
||||||
|
|
||||||
|
// Single Connection reference from xaConnection.getConnection()
|
||||||
|
private final Connection connection;
|
||||||
|
|
||||||
|
// Single XAResource reference from xaConnection.getXAResource(). Can be null for no XA datasources.
|
||||||
|
private final XAResource xaResource;
|
||||||
|
|
||||||
|
private final Pool connectionPool;
|
||||||
|
|
||||||
|
// attributes that need to be reset when the connection is returned
|
||||||
|
private final Set<DirtyAttribute> dirtyAttributes = noneOf(DirtyAttribute.class);
|
||||||
|
|
||||||
|
// collection of wrappers created while enlisted in the current transaction
|
||||||
|
private final AutoCloseableElement enlistedOpenWrappers = AutoCloseableElement.newHead();
|
||||||
|
|
||||||
|
// Can use annotation to get (in theory) a little better performance
|
||||||
|
// @Contended
|
||||||
|
private volatile State state = State.NEW;
|
||||||
|
|
||||||
|
// for leak detection (only valid for CHECKED_OUT connections)
|
||||||
|
private Thread holdingThread;
|
||||||
|
|
||||||
|
// Enhanced leak report
|
||||||
|
@SuppressWarnings("VolatileArrayField")
|
||||||
|
private volatile StackTraceElement[] acquisitionStackTrace;
|
||||||
|
private StackTraceElement[] lastOperationStackTrace;
|
||||||
|
private List<String> connectionOperations;
|
||||||
|
|
||||||
|
// for expiration (CHECKED_IN connections) and leak detection (CHECKED_OUT connections)
|
||||||
|
private long lastAccess;
|
||||||
|
|
||||||
|
// flag to indicate that this the connection is enlisted to a transaction
|
||||||
|
private boolean enlisted;
|
||||||
|
|
||||||
|
// reference to the task that flushes this connection when it gets over it's maxLifetime
|
||||||
|
private Future<?> maxLifetimeTask;
|
||||||
|
|
||||||
|
// Callback set by the transaction integration layer to prevent deferred enlistment
|
||||||
|
// If the connection is not associated with a transaction and an operation occurs within the bounds of a transaction, an SQLException is thrown
|
||||||
|
// If there is no transaction integration this should just return false
|
||||||
|
private SQLCallable<Boolean> transactionActiveCheck = NO_ACTIVE_TRANSACTION;
|
||||||
|
|
||||||
|
public ConnectionHandler(XAConnection xa, Pool pool) throws SQLException {
|
||||||
|
xaConnection = xa;
|
||||||
|
connection = xaConnection.getConnection();
|
||||||
|
xaResource = xaConnection.getXAResource();
|
||||||
|
|
||||||
|
connectionPool = pool;
|
||||||
|
touch();
|
||||||
|
}
|
||||||
|
|
||||||
|
public XAConnectionWrapper xaConnectionWrapper() {
|
||||||
|
return new XAConnectionWrapper(this, xaConnection, connectionPool.getConfiguration().connectionFactoryConfiguration().trackJdbcResources());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConnectionWrapper connectionWrapper() {
|
||||||
|
return new ConnectionWrapper(this, connectionPool.getConfiguration().connectionFactoryConfiguration().trackJdbcResources(), enlisted ? enlistedOpenWrappers : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConnectionWrapper detachedWrapper() {
|
||||||
|
return new ConnectionWrapper(this, connectionPool.getConfiguration().connectionFactoryConfiguration().trackJdbcResources(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("StringConcatenation")
|
||||||
|
public void onConnectionWrapperClose(ConnectionWrapper wrapper, ConnectionWrapper.JdbcResourcesLeakReport leakReport) throws SQLException {
|
||||||
|
if (leakReport.hasLeak()) {
|
||||||
|
fireOnWarning(connectionPool.getListeners(), "JDBC resources leaked: " + leakReport.resultSetCount() + " ResultSet(s) and " + leakReport.statementCount() + " Statement(s)");
|
||||||
|
}
|
||||||
|
if (!enlisted && !wrapper.isDetached()) {
|
||||||
|
transactionEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Connection rawConnection() {
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public XAResource getXaResource() {
|
||||||
|
return xaResource;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("MagicConstant")
|
||||||
|
public void resetConnection() throws SQLException {
|
||||||
|
transactionActiveCheck = NO_ACTIVE_TRANSACTION;
|
||||||
|
|
||||||
|
if (!dirtyAttributes.isEmpty()) {
|
||||||
|
XbibConnectionFactoryConfiguration connectionFactoryConfiguration = connectionPool.getConfiguration().connectionFactoryConfiguration();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (dirtyAttributes.contains(AUTOCOMMIT)) {
|
||||||
|
connection.setAutoCommit(connectionFactoryConfiguration.autoCommit());
|
||||||
|
}
|
||||||
|
if (dirtyAttributes.contains(TRANSACTION_ISOLATION)) {
|
||||||
|
XbibConnectionFactoryConfiguration.IsolationLevel isolation = connectionFactoryConfiguration.jdbcTransactionIsolation();
|
||||||
|
connection.setTransactionIsolation(isolation.isDefined() ? isolation.level() : connectionPool.defaultJdbcIsolationLevel());
|
||||||
|
}
|
||||||
|
// other attributes do not have default values in connectionFactoryConfiguration
|
||||||
|
} catch (SQLException se) {
|
||||||
|
setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
} finally {
|
||||||
|
dirtyAttributes.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
SQLWarning warning = connection.getWarnings();
|
||||||
|
if (warning != null) {
|
||||||
|
XbibConnectionPoolConfiguration.ExceptionSorter exceptionSorter = connectionPool.getConfiguration().exceptionSorter();
|
||||||
|
while (warning != null) {
|
||||||
|
if (exceptionSorter != null && exceptionSorter.isFatal(warning)) {
|
||||||
|
setState(State.FLUSH);
|
||||||
|
}
|
||||||
|
warning = warning.getNextWarning();
|
||||||
|
}
|
||||||
|
connection.clearWarnings();
|
||||||
|
}
|
||||||
|
} catch (SQLException se) {
|
||||||
|
setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
// keep errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void closeConnection() throws SQLException {
|
||||||
|
if (maxLifetimeTask != null && !maxLifetimeTask.isDone()) {
|
||||||
|
maxLifetimeTask.cancel(false);
|
||||||
|
}
|
||||||
|
maxLifetimeTask = null;
|
||||||
|
try {
|
||||||
|
State observedState = stateUpdater.get(this);
|
||||||
|
if (observedState != State.FLUSH) {
|
||||||
|
throw new SQLException("Closing connection in incorrect state " + observedState);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
xaConnection.close();
|
||||||
|
} finally {
|
||||||
|
stateUpdater.set(this, State.DESTROYED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean acquire() {
|
||||||
|
return setState(State.CHECKED_IN, State.CHECKED_OUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAcquirable() {
|
||||||
|
State observedState = stateUpdater.get(this);
|
||||||
|
return observedState != State.FLUSH && observedState != State.DESTROYED;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean setState(State expected, State newState) {
|
||||||
|
if (expected == State.DESTROYED) {
|
||||||
|
throw new IllegalArgumentException("Trying to move out of state DESTROYED");
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (newState) {
|
||||||
|
case NEW:
|
||||||
|
throw new IllegalArgumentException("Trying to set invalid state NEW");
|
||||||
|
case CHECKED_IN:
|
||||||
|
case CHECKED_OUT:
|
||||||
|
case VALIDATION:
|
||||||
|
case FLUSH:
|
||||||
|
case DESTROYED:
|
||||||
|
return stateUpdater.compareAndSet(this, expected, newState);
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Trying to set invalid state " + newState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setState(State newState) {
|
||||||
|
// Maybe could use lazySet here, but there doesn't seem to be any performance advantage
|
||||||
|
stateUpdater.set(this, newState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isActive() {
|
||||||
|
return stateUpdater.get(this) == State.CHECKED_OUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void touch() {
|
||||||
|
lastAccess = nanoTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLeak(Duration timeout) {
|
||||||
|
return isActive() && !enlisted && isIdle(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isIdle(Duration timeout) {
|
||||||
|
return nanoTime() - lastAccess > timeout.toNanos();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxLifetimeTask(Future<?> maxLifetimeTask) {
|
||||||
|
this.maxLifetimeTask = maxLifetimeTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isValid() {
|
||||||
|
return connectionPool.getConfiguration().connectionValidator().isValid(detachedWrapper());
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Leak detection //
|
||||||
|
|
||||||
|
public Thread getHoldingThread() {
|
||||||
|
return holdingThread;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHoldingThread(Thread holdingThread) {
|
||||||
|
this.holdingThread = holdingThread;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Enhanced leak report //
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abbreviated list of all operation on the connection, for enhanced leak report
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("VariableNotUsedInsideIf")
|
||||||
|
public void traceConnectionOperation(String operation) {
|
||||||
|
if (acquisitionStackTrace != null) {
|
||||||
|
connectionOperations.add(operation);
|
||||||
|
lastOperationStackTrace = currentThread().getStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abbreviated list of all operation on the connection, for enhanced leak report
|
||||||
|
*/
|
||||||
|
public List<String> getConnectionOperations() {
|
||||||
|
return connectionOperations;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stack trace of the first acquisition for this connection
|
||||||
|
*/
|
||||||
|
public StackTraceElement[] getAcquisitionStackTrace() {
|
||||||
|
return acquisitionStackTrace == null ? null : copyOfRange(acquisitionStackTrace, 4, acquisitionStackTrace.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores a stack trace for leak report. Setting a value != null also enables tracing of operations on the connection
|
||||||
|
*/
|
||||||
|
public void setAcquisitionStackTrace(StackTraceElement[] stackTrace) {
|
||||||
|
lastOperationStackTrace = null;
|
||||||
|
if (connectionOperations == null) {
|
||||||
|
connectionOperations = new UncheckedArrayList<>(String.class);
|
||||||
|
}
|
||||||
|
connectionOperations.clear();
|
||||||
|
acquisitionStackTrace = stackTrace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stack trace for the last operation on this connection
|
||||||
|
*/
|
||||||
|
public StackTraceElement[] getLastOperationStackTrace() {
|
||||||
|
return lastOperationStackTrace == null ? null : copyOfRange(lastOperationStackTrace, 3, lastOperationStackTrace.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDirtyAttribute(DirtyAttribute attribute) {
|
||||||
|
dirtyAttributes.add(attribute);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnlisted() {
|
||||||
|
return enlisted;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- TransactionAware //
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Connection getConnection() {
|
||||||
|
return detachedWrapper();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void transactionStart() throws SQLException {
|
||||||
|
try {
|
||||||
|
if (!enlisted && connection.getAutoCommit()) {
|
||||||
|
connection.setAutoCommit(false);
|
||||||
|
setDirtyAttribute(AUTOCOMMIT);
|
||||||
|
}
|
||||||
|
enlisted = true;
|
||||||
|
} catch (SQLException se) {
|
||||||
|
setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void transactionBeforeCompletion(boolean successful) {
|
||||||
|
if (enlistedOpenWrappers.closeAllAutocloseableElements() != 0) {
|
||||||
|
if (successful) {
|
||||||
|
fireOnWarning(connectionPool.getListeners(), "Closing open connection(s) prior to commit");
|
||||||
|
} else {
|
||||||
|
// AG-168 - Close without warning as Synchronization.beforeCompletion is only invoked on success. See issue for more details.
|
||||||
|
// fireOnWarning( connectionPool.getListeners(), "Closing open connection prior to rollback" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void transactionCommit() throws SQLException {
|
||||||
|
verifyEnlistment();
|
||||||
|
try {
|
||||||
|
connection.commit();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void transactionRollback() throws SQLException {
|
||||||
|
verifyEnlistment();
|
||||||
|
try {
|
||||||
|
connection.rollback();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void transactionEnd() throws SQLException {
|
||||||
|
if (enlistedOpenWrappers.closeAllAutocloseableElements() != 0) {
|
||||||
|
// should never happen, but it's here as a safeguard to prevent double returns in all cases.
|
||||||
|
fireOnWarning(connectionPool.getListeners(), "Closing open connection(s) on after completion");
|
||||||
|
}
|
||||||
|
enlisted = false;
|
||||||
|
connectionPool.returnConnectionHandler(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void transactionCheckCallback(SQLCallable<Boolean> transactionCheck) {
|
||||||
|
transactionActiveCheck = transactionCheck;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void verifyEnlistment() throws SQLException {
|
||||||
|
if (!enlisted && transactionActiveCheck.call()) {
|
||||||
|
throw new SQLException("Deferred enlistment not supported");
|
||||||
|
}
|
||||||
|
if (enlisted && !transactionActiveCheck.call()) {
|
||||||
|
throw new SQLException("Enlisted connection used without active transaction");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFlushOnly() {
|
||||||
|
// Assumed currentState == State.CHECKED_OUT (or eventually in FLUSH already)
|
||||||
|
setState(State.FLUSH);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFlushOnly(SQLException se) {
|
||||||
|
// Assumed currentState == State.CHECKED_OUT (or eventually in FLUSH already)
|
||||||
|
XbibConnectionPoolConfiguration.ExceptionSorter exceptionSorter = connectionPool.getConfiguration().exceptionSorter();
|
||||||
|
if (exceptionSorter != null && exceptionSorter.isFatal(se)) {
|
||||||
|
setState(State.FLUSH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum State {
|
||||||
|
NEW, CHECKED_IN, CHECKED_OUT, VALIDATION, FLUSH, DESTROYED
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum DirtyAttribute {
|
||||||
|
AUTOCOMMIT, TRANSACTION_ISOLATION, NETWORK_TIMEOUT, SCHEMA, CATALOG
|
||||||
|
}
|
||||||
|
}
|
823
jdbc-pool/src/main/java/org/xbib/jdbc/pool/ConnectionPool.java
Normal file
823
jdbc-pool/src/main/java/org/xbib/jdbc/pool/ConnectionPool.java
Normal file
|
@ -0,0 +1,823 @@
|
||||||
|
package org.xbib.jdbc.pool;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.CancellationException;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.atomic.LongAccumulator;
|
||||||
|
import java.util.concurrent.atomic.LongAdder;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import javax.sql.XAConnection;
|
||||||
|
import org.xbib.jdbc.pool.MetricsRepository.EmptyMetricsRepository;
|
||||||
|
import org.xbib.jdbc.pool.api.XbibDataSource;
|
||||||
|
import org.xbib.jdbc.pool.api.XbibDataSourceListener;
|
||||||
|
import org.xbib.jdbc.pool.api.XbibPoolInterceptor;
|
||||||
|
import org.xbib.jdbc.pool.api.cache.ConnectionCache;
|
||||||
|
import org.xbib.jdbc.pool.api.configuration.XbibConnectionPoolConfiguration;
|
||||||
|
import org.xbib.jdbc.pool.api.transaction.TransactionIntegration;
|
||||||
|
import org.xbib.jdbc.pool.util.PriorityScheduledExecutor;
|
||||||
|
import org.xbib.jdbc.pool.util.StampedCopyOnWriteArrayList;
|
||||||
|
import org.xbib.jdbc.pool.util.XbibSynchronizer;
|
||||||
|
import static java.lang.Integer.toHexString;
|
||||||
|
import static java.lang.System.identityHashCode;
|
||||||
|
import static java.lang.System.nanoTime;
|
||||||
|
import static java.lang.Thread.currentThread;
|
||||||
|
import static java.util.Collections.unmodifiableList;
|
||||||
|
import static java.util.concurrent.TimeUnit.NANOSECONDS;
|
||||||
|
import static java.util.stream.Collectors.joining;
|
||||||
|
import static java.util.stream.Collectors.toList;
|
||||||
|
import static org.xbib.jdbc.pool.ConnectionHandler.State.CHECKED_IN;
|
||||||
|
import static org.xbib.jdbc.pool.ConnectionHandler.State.CHECKED_OUT;
|
||||||
|
import static org.xbib.jdbc.pool.ConnectionHandler.State.FLUSH;
|
||||||
|
import static org.xbib.jdbc.pool.ConnectionHandler.State.VALIDATION;
|
||||||
|
import static org.xbib.jdbc.pool.api.XbibDataSource.FlushMode.GRACEFUL;
|
||||||
|
import static org.xbib.jdbc.pool.api.XbibDataSource.FlushMode.LEAK;
|
||||||
|
import static org.xbib.jdbc.pool.api.configuration.XbibConnectionPoolConfiguration.MultipleAcquisitionAction.OFF;
|
||||||
|
import static org.xbib.jdbc.pool.util.InterceptorHelper.fireOnConnectionAcquiredInterceptor;
|
||||||
|
import static org.xbib.jdbc.pool.util.InterceptorHelper.fireOnConnectionCreateInterceptor;
|
||||||
|
import static org.xbib.jdbc.pool.util.InterceptorHelper.fireOnConnectionDestroyInterceptor;
|
||||||
|
import static org.xbib.jdbc.pool.util.InterceptorHelper.fireOnConnectionReturnInterceptor;
|
||||||
|
import static org.xbib.jdbc.pool.util.ListenerHelper.fireBeforeConnectionAcquire;
|
||||||
|
import static org.xbib.jdbc.pool.util.ListenerHelper.fireBeforeConnectionCreation;
|
||||||
|
import static org.xbib.jdbc.pool.util.ListenerHelper.fireBeforeConnectionDestroy;
|
||||||
|
import static org.xbib.jdbc.pool.util.ListenerHelper.fireBeforeConnectionFlush;
|
||||||
|
import static org.xbib.jdbc.pool.util.ListenerHelper.fireBeforeConnectionLeak;
|
||||||
|
import static org.xbib.jdbc.pool.util.ListenerHelper.fireBeforeConnectionReap;
|
||||||
|
import static org.xbib.jdbc.pool.util.ListenerHelper.fireBeforeConnectionReturn;
|
||||||
|
import static org.xbib.jdbc.pool.util.ListenerHelper.fireBeforeConnectionValidation;
|
||||||
|
import static org.xbib.jdbc.pool.util.ListenerHelper.fireOnConnectionAcquired;
|
||||||
|
import static org.xbib.jdbc.pool.util.ListenerHelper.fireOnConnectionCreation;
|
||||||
|
import static org.xbib.jdbc.pool.util.ListenerHelper.fireOnConnectionDestroy;
|
||||||
|
import static org.xbib.jdbc.pool.util.ListenerHelper.fireOnConnectionFlush;
|
||||||
|
import static org.xbib.jdbc.pool.util.ListenerHelper.fireOnConnectionInvalid;
|
||||||
|
import static org.xbib.jdbc.pool.util.ListenerHelper.fireOnConnectionLeak;
|
||||||
|
import static org.xbib.jdbc.pool.util.ListenerHelper.fireOnConnectionPooled;
|
||||||
|
import static org.xbib.jdbc.pool.util.ListenerHelper.fireOnConnectionReap;
|
||||||
|
import static org.xbib.jdbc.pool.util.ListenerHelper.fireOnConnectionReturn;
|
||||||
|
import static org.xbib.jdbc.pool.util.ListenerHelper.fireOnConnectionValid;
|
||||||
|
import static org.xbib.jdbc.pool.util.ListenerHelper.fireOnInfo;
|
||||||
|
import static org.xbib.jdbc.pool.util.ListenerHelper.fireOnWarning;
|
||||||
|
|
||||||
|
public final class ConnectionPool implements Pool {
|
||||||
|
|
||||||
|
private static final AtomicInteger HOUSEKEEP_COUNT = new AtomicInteger();
|
||||||
|
|
||||||
|
private final XbibConnectionPoolConfiguration configuration;
|
||||||
|
private final XbibDataSourceListener[] listeners;
|
||||||
|
|
||||||
|
private final StampedCopyOnWriteArrayList<ConnectionHandler> allConnections;
|
||||||
|
|
||||||
|
private final XbibSynchronizer synchronizer;
|
||||||
|
private final ConnectionFactory connectionFactory;
|
||||||
|
private final PriorityScheduledExecutor housekeepingExecutor;
|
||||||
|
private final TransactionIntegration transactionIntegration;
|
||||||
|
|
||||||
|
private final boolean borrowValidationEnabled;
|
||||||
|
private final boolean idleValidationEnabled;
|
||||||
|
private final boolean leakEnabled;
|
||||||
|
private final boolean validationEnabled;
|
||||||
|
private final boolean reapEnabled;
|
||||||
|
|
||||||
|
private final LongAccumulator maxUsed = new LongAccumulator(Math::max, Long.MIN_VALUE);
|
||||||
|
private final LongAdder activeCount = new LongAdder();
|
||||||
|
private final ConnectionCache localCache;
|
||||||
|
private MetricsRepository metricsRepository;
|
||||||
|
private List<XbibPoolInterceptor> interceptors;
|
||||||
|
|
||||||
|
public ConnectionPool(XbibConnectionPoolConfiguration configuration, XbibDataSourceListener... listeners) {
|
||||||
|
this.configuration = configuration;
|
||||||
|
this.listeners = listeners;
|
||||||
|
|
||||||
|
allConnections = new StampedCopyOnWriteArrayList<>(ConnectionHandler.class);
|
||||||
|
localCache = configuration.connectionCache();
|
||||||
|
|
||||||
|
synchronizer = new XbibSynchronizer();
|
||||||
|
connectionFactory = new ConnectionFactory(configuration.connectionFactoryConfiguration(), listeners);
|
||||||
|
housekeepingExecutor = new PriorityScheduledExecutor(1, "xbib-" + HOUSEKEEP_COUNT.incrementAndGet(), listeners);
|
||||||
|
transactionIntegration = configuration.transactionIntegration();
|
||||||
|
|
||||||
|
borrowValidationEnabled = configuration.validateOnBorrow();
|
||||||
|
idleValidationEnabled = !configuration.validateOnBorrow() && !configuration.idleValidationTimeout().isZero();
|
||||||
|
leakEnabled = !configuration.leakTimeout().isZero();
|
||||||
|
validationEnabled = !configuration.validationTimeout().isZero();
|
||||||
|
reapEnabled = !configuration.reapTimeout().isZero();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void init() {
|
||||||
|
if (configuration.acquisitionTimeout().compareTo(configuration.connectionFactoryConfiguration().loginTimeout()) < 0) {
|
||||||
|
fireOnWarning(listeners, "Login timeout should be smaller than acquisition timeout");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (leakEnabled) {
|
||||||
|
housekeepingExecutor.schedule(new LeakTask(), configuration.leakTimeout().toNanos(), NANOSECONDS);
|
||||||
|
}
|
||||||
|
if (validationEnabled) {
|
||||||
|
housekeepingExecutor.schedule(new ValidationTask(), configuration.validationTimeout().toNanos(), NANOSECONDS);
|
||||||
|
}
|
||||||
|
if (reapEnabled) {
|
||||||
|
housekeepingExecutor.schedule(new ReapTask(), configuration.reapTimeout().toNanos(), NANOSECONDS);
|
||||||
|
}
|
||||||
|
transactionIntegration.addResourceRecoveryFactory(connectionFactory.hasRecoveryCredentials() ? connectionFactory : this);
|
||||||
|
|
||||||
|
// fill to the initial size
|
||||||
|
if (configuration.initialSize() < configuration.minSize()) {
|
||||||
|
fireOnInfo(listeners, "Initial size smaller than min. Connections will be created when necessary");
|
||||||
|
} else if (configuration.initialSize() > configuration.maxSize()) {
|
||||||
|
fireOnInfo(listeners, "Initial size bigger than max. Connections will be destroyed as soon as they return to the pool");
|
||||||
|
}
|
||||||
|
for (int n = configuration.initialSize(); n > 0; n--) {
|
||||||
|
housekeepingExecutor.executeNow(new CreateConnectionTask().initial());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public XbibConnectionPoolConfiguration getConfiguration() {
|
||||||
|
return configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int defaultJdbcIsolationLevel() {
|
||||||
|
return connectionFactory.defaultJdbcIsolationLevel();
|
||||||
|
}
|
||||||
|
|
||||||
|
public XbibDataSourceListener[] getListeners() {
|
||||||
|
return listeners;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<XbibPoolInterceptor> getPoolInterceptors() {
|
||||||
|
return unmodifiableList(interceptors);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPoolInterceptors(Collection<? extends XbibPoolInterceptor> list) {
|
||||||
|
if (list.stream().anyMatch(i -> i.getPriority() < 0)) {
|
||||||
|
throw new IllegalArgumentException("Negative priority values on XbibPoolInterceptor are reserved.");
|
||||||
|
}
|
||||||
|
if (list.isEmpty() && (interceptors == null || interceptors.isEmpty())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
interceptors = list.stream().sorted(XbibPoolInterceptor.DEFAULT_COMPARATOR).collect(toList());
|
||||||
|
|
||||||
|
Function<XbibPoolInterceptor, String> interceptorName = i -> i.getClass().getName() + "@" + toHexString(identityHashCode(i)) + " (priority " + i.getPriority() + ")";
|
||||||
|
fireOnInfo(listeners, "Pool interceptors: " + interceptors.stream().map(interceptorName).collect(joining(" >>> ", "[", "]")));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void flushPool(XbibDataSource.FlushMode mode) {
|
||||||
|
if (mode == LEAK && !leakEnabled) {
|
||||||
|
fireOnWarning(listeners, "Flushing leak connections with no specified leak timout.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
housekeepingExecutor.execute(new FlushTask(mode));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
transactionIntegration.removeResourceRecoveryFactory(connectionFactory.hasRecoveryCredentials() ? connectionFactory : this);
|
||||||
|
|
||||||
|
for (Runnable task : housekeepingExecutor.shutdownNow()) {
|
||||||
|
if (task instanceof DestroyConnectionTask) {
|
||||||
|
task.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ConnectionHandler handler : allConnections) {
|
||||||
|
handler.setState(FLUSH);
|
||||||
|
new DestroyConnectionTask(handler).run();
|
||||||
|
}
|
||||||
|
allConnections.clear();
|
||||||
|
activeCount.reset();
|
||||||
|
|
||||||
|
synchronizer.release(synchronizer.getQueueLength());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRecoverable() {
|
||||||
|
return connectionFactory.isRecoverable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public XAConnection getRecoveryConnection() throws SQLException {
|
||||||
|
long stamp = beforeAcquire();
|
||||||
|
checkMultipleAcquisition();
|
||||||
|
ConnectionHandler checkedOutHandler = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
do {
|
||||||
|
checkedOutHandler = (ConnectionHandler) localCache.get();
|
||||||
|
if (checkedOutHandler == null) {
|
||||||
|
checkedOutHandler = handlerFromSharedCache();
|
||||||
|
}
|
||||||
|
} while ((borrowValidationEnabled && !borrowValidation(checkedOutHandler))
|
||||||
|
|| (idleValidationEnabled && !idleValidation(checkedOutHandler)));
|
||||||
|
|
||||||
|
activeCount.increment();
|
||||||
|
fireOnConnectionAcquiredInterceptor(interceptors, checkedOutHandler);
|
||||||
|
afterAcquire(stamp, checkedOutHandler, false);
|
||||||
|
return checkedOutHandler.xaConnectionWrapper();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
if (checkedOutHandler != null) {
|
||||||
|
checkedOutHandler.setState(CHECKED_OUT, CHECKED_IN);
|
||||||
|
}
|
||||||
|
throw t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private long beforeAcquire() throws SQLException {
|
||||||
|
fireBeforeConnectionAcquire(listeners);
|
||||||
|
if (housekeepingExecutor.isShutdown()) {
|
||||||
|
throw new SQLException("This pool is closed and does not handle any more connections!");
|
||||||
|
}
|
||||||
|
return metricsRepository.beforeConnectionAcquire();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkMultipleAcquisition() throws SQLException {
|
||||||
|
if (configuration.multipleAcquisition() != OFF) {
|
||||||
|
for (ConnectionHandler handler : allConnections) {
|
||||||
|
if (handler.getHoldingThread() == currentThread()) {
|
||||||
|
switch (configuration.multipleAcquisition()) {
|
||||||
|
case STRICT:
|
||||||
|
throw new SQLException("Acquisition of multiple connections by the same Thread.");
|
||||||
|
case WARN:
|
||||||
|
fireOnWarning(listeners, "Acquisition of multiple connections by the same Thread. This can lead to pool exhaustion and eventually a deadlock!");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Connection getConnection() throws SQLException {
|
||||||
|
long stamp = beforeAcquire();
|
||||||
|
|
||||||
|
ConnectionHandler checkedOutHandler = handlerFromTransaction();
|
||||||
|
if (checkedOutHandler != null) {
|
||||||
|
// AG-140 - If associate throws here is fine, it's assumed the synchronization that returns the connection has been registered
|
||||||
|
transactionIntegration.associate(checkedOutHandler, checkedOutHandler.getXaResource());
|
||||||
|
afterAcquire(stamp, checkedOutHandler, true);
|
||||||
|
return checkedOutHandler.connectionWrapper();
|
||||||
|
}
|
||||||
|
checkMultipleAcquisition();
|
||||||
|
|
||||||
|
try {
|
||||||
|
do {
|
||||||
|
checkedOutHandler = (ConnectionHandler) localCache.get();
|
||||||
|
if (checkedOutHandler == null) {
|
||||||
|
checkedOutHandler = handlerFromSharedCache();
|
||||||
|
}
|
||||||
|
} while ((borrowValidationEnabled && !borrowValidation(checkedOutHandler))
|
||||||
|
|| (idleValidationEnabled && !idleValidation(checkedOutHandler)));
|
||||||
|
transactionIntegration.associate(checkedOutHandler, checkedOutHandler.getXaResource());
|
||||||
|
|
||||||
|
activeCount.increment();
|
||||||
|
fireOnConnectionAcquiredInterceptor(interceptors, checkedOutHandler);
|
||||||
|
afterAcquire(stamp, checkedOutHandler, true);
|
||||||
|
return checkedOutHandler.connectionWrapper();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
if (checkedOutHandler != null) {
|
||||||
|
// AG-140 - Return the connection to the pool to prevent leak
|
||||||
|
checkedOutHandler.setState(CHECKED_OUT, CHECKED_IN);
|
||||||
|
}
|
||||||
|
throw t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConnectionHandler handlerFromTransaction() throws SQLException {
|
||||||
|
return (ConnectionHandler) transactionIntegration.getTransactionAware();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConnectionHandler handlerFromSharedCache() throws SQLException {
|
||||||
|
long remaining = configuration.acquisitionTimeout().toNanos();
|
||||||
|
remaining = remaining > 0 ? remaining : Long.MAX_VALUE;
|
||||||
|
Future<ConnectionHandler> task = null;
|
||||||
|
try {
|
||||||
|
for (; ; ) {
|
||||||
|
// If min-size increases, create a connection right away
|
||||||
|
if (allConnections.size() < configuration.minSize()) {
|
||||||
|
task = housekeepingExecutor.executeNow(new CreateConnectionTask());
|
||||||
|
}
|
||||||
|
// Try to find an available connection in the pool
|
||||||
|
for (ConnectionHandler handler : allConnections) {
|
||||||
|
if (handler.acquire()) {
|
||||||
|
return handler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If no connections are available and there is room, create one
|
||||||
|
if (task == null && allConnections.size() < configuration.maxSize()) {
|
||||||
|
task = housekeepingExecutor.executeNow(new CreateConnectionTask());
|
||||||
|
}
|
||||||
|
long start = nanoTime();
|
||||||
|
if (task == null) {
|
||||||
|
// Pool full, will have to wait for a connection to be returned
|
||||||
|
if (!synchronizer.tryAcquireNanos(synchronizer.getStamp(), remaining)) {
|
||||||
|
throw new SQLException("Sorry, acquisition timeout!");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Wait for the new connection instead of the synchronizer to propagate any exception on connection establishment
|
||||||
|
ConnectionHandler handler = task.get(remaining, NANOSECONDS);
|
||||||
|
if (handler != null && handler.acquire()) {
|
||||||
|
return handler;
|
||||||
|
}
|
||||||
|
task = null;
|
||||||
|
}
|
||||||
|
remaining -= nanoTime() - start;
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
currentThread().interrupt();
|
||||||
|
throw new SQLException("Interrupted while acquiring");
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
throw unwrapExecutionException(e);
|
||||||
|
} catch (RejectedExecutionException | CancellationException e) {
|
||||||
|
throw new SQLException("Can't create new connection as the pool is shutting down", e);
|
||||||
|
} catch (TimeoutException e) {
|
||||||
|
task.cancel(true);
|
||||||
|
// AG-201: Last effort. Connections may have returned to the pool while waiting.
|
||||||
|
for (ConnectionHandler handler : allConnections) {
|
||||||
|
if (handler.acquire()) {
|
||||||
|
return handler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new SQLException("Acquisition timeout while waiting for new connection", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private SQLException unwrapExecutionException(ExecutionException ee) {
|
||||||
|
try {
|
||||||
|
throw ee.getCause();
|
||||||
|
} catch (RuntimeException | Error re) {
|
||||||
|
throw re;
|
||||||
|
} catch (SQLException se) {
|
||||||
|
return se;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
return new SQLException("Exception while creating new connection", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean idleValidation(ConnectionHandler handler) {
|
||||||
|
if (!handler.isIdle(configuration.idleValidationTimeout())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return borrowValidation(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean borrowValidation(ConnectionHandler handler) {
|
||||||
|
if (handler.setState(CHECKED_OUT, VALIDATION)) {
|
||||||
|
return performValidation(handler, CHECKED_OUT);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean performValidation(ConnectionHandler handler, ConnectionHandler.State targetState) {
|
||||||
|
fireBeforeConnectionValidation(listeners, handler);
|
||||||
|
if (handler.isValid() && handler.setState(VALIDATION, targetState)) {
|
||||||
|
fireOnConnectionValid(listeners, handler);
|
||||||
|
synchronizer.releaseConditional();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
removeFromPool(handler);
|
||||||
|
metricsRepository.afterConnectionInvalid();
|
||||||
|
fireOnConnectionInvalid(listeners, handler);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void afterAcquire(long metricsStamp, ConnectionHandler checkedOutHandler, boolean verifyEnlistment) throws SQLException {
|
||||||
|
metricsRepository.afterConnectionAcquire(metricsStamp);
|
||||||
|
fireOnConnectionAcquired(listeners, checkedOutHandler);
|
||||||
|
|
||||||
|
if (verifyEnlistment && !checkedOutHandler.isEnlisted()) {
|
||||||
|
switch (configuration.transactionRequirement()) {
|
||||||
|
case STRICT:
|
||||||
|
returnConnectionHandler(checkedOutHandler);
|
||||||
|
throw new SQLException("Connection acquired without transaction.");
|
||||||
|
case WARN:
|
||||||
|
fireOnWarning(listeners, new SQLException("Connection acquired without transaction."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (leakEnabled || reapEnabled) {
|
||||||
|
checkedOutHandler.touch();
|
||||||
|
}
|
||||||
|
if (leakEnabled || configuration.multipleAcquisition() != OFF) {
|
||||||
|
if (checkedOutHandler.getHoldingThread() != null && checkedOutHandler.getHoldingThread() != currentThread()) {
|
||||||
|
Throwable warn = new Throwable("Shared connection between threads '" + checkedOutHandler.getHoldingThread().getName() + "' and '" + currentThread().getName() + "'");
|
||||||
|
warn.setStackTrace(checkedOutHandler.getHoldingThread().getStackTrace());
|
||||||
|
fireOnWarning(listeners, warn);
|
||||||
|
}
|
||||||
|
checkedOutHandler.setHoldingThread(currentThread());
|
||||||
|
if (configuration.enhancedLeakReport()) {
|
||||||
|
checkedOutHandler.setAcquisitionStackTrace(currentThread().getStackTrace());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
public void returnConnectionHandler(ConnectionHandler handler) throws SQLException {
|
||||||
|
fireBeforeConnectionReturn(listeners, handler);
|
||||||
|
if (leakEnabled) {
|
||||||
|
handler.setHoldingThread(null);
|
||||||
|
if (configuration.enhancedLeakReport()) {
|
||||||
|
handler.setAcquisitionStackTrace(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (idleValidationEnabled || reapEnabled) {
|
||||||
|
handler.touch();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (!transactionIntegration.disassociate(handler)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
activeCount.decrement();
|
||||||
|
|
||||||
|
// resize on change of max-size, or flush on close
|
||||||
|
int currentSize = allConnections.size();
|
||||||
|
if ((currentSize > configuration.maxSize() && currentSize > configuration.minSize()) || configuration.flushOnClose()) {
|
||||||
|
handler.setState(FLUSH);
|
||||||
|
removeFromPool(handler);
|
||||||
|
metricsRepository.afterConnectionReap();
|
||||||
|
fireOnConnectionReap(listeners, handler);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
handler.resetConnection();
|
||||||
|
} catch (SQLException sqlException) {
|
||||||
|
fireOnWarning(listeners, sqlException);
|
||||||
|
}
|
||||||
|
localCache.put(handler);
|
||||||
|
fireOnConnectionReturnInterceptor(interceptors, handler);
|
||||||
|
|
||||||
|
if (handler.setState(CHECKED_OUT, CHECKED_IN)) {
|
||||||
|
// here the handler is already up for grabs
|
||||||
|
synchronizer.releaseConditional();
|
||||||
|
metricsRepository.afterConnectionReturn();
|
||||||
|
fireOnConnectionReturn(listeners, handler);
|
||||||
|
} else {
|
||||||
|
// handler not in CHECKED_OUT implies FLUSH
|
||||||
|
removeFromPool(handler);
|
||||||
|
metricsRepository.afterConnectionFlush();
|
||||||
|
fireOnConnectionFlush(listeners, handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeFromPool(ConnectionHandler handler) {
|
||||||
|
allConnections.remove(handler);
|
||||||
|
synchronizer.releaseConditional();
|
||||||
|
housekeepingExecutor.execute(new FillTask());
|
||||||
|
housekeepingExecutor.execute(new DestroyConnectionTask(handler));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Exposed statistics //
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMetricsEnabled(boolean metricsEnabled) {
|
||||||
|
metricsRepository = metricsEnabled ? new DefaultMetricsRepository(this) : new EmptyMetricsRepository();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MetricsRepository getMetrics() {
|
||||||
|
return metricsRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long activeCount() {
|
||||||
|
return activeCount.sum();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long availableCount() {
|
||||||
|
return allConnections.size() - activeCount.sum();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long maxUsedCount() {
|
||||||
|
return maxUsed.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetMaxUsedCount() {
|
||||||
|
maxUsed.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long awaitingCount() {
|
||||||
|
return synchronizer.getQueueLength();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- health check //
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isHealthy(boolean newConnection) throws SQLException {
|
||||||
|
ConnectionHandler healthHandler;
|
||||||
|
Future<ConnectionHandler> task = null;
|
||||||
|
if (newConnection) {
|
||||||
|
try {
|
||||||
|
do {
|
||||||
|
task = housekeepingExecutor.executeNow(new CreateConnectionTask().initial());
|
||||||
|
healthHandler = task.get(configuration.acquisitionTimeout().isZero() ? Long.MAX_VALUE : configuration.acquisitionTimeout().toNanos(), NANOSECONDS);
|
||||||
|
} while (!healthHandler.setState(CHECKED_IN, VALIDATION));
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
currentThread().interrupt();
|
||||||
|
throw new SQLException("Interrupted while acquiring");
|
||||||
|
} catch (ExecutionException ee) {
|
||||||
|
throw unwrapExecutionException(ee);
|
||||||
|
} catch (RejectedExecutionException | CancellationException e) {
|
||||||
|
throw new SQLException("Can't create new connection as the pool is shutting down", e);
|
||||||
|
} catch (TimeoutException e) {
|
||||||
|
task.cancel(true);
|
||||||
|
throw new SQLException("Acquisition timeout on health check");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
healthHandler = handlerFromSharedCache();
|
||||||
|
healthHandler.setState(CHECKED_OUT, VALIDATION);
|
||||||
|
}
|
||||||
|
return performValidation(healthHandler, CHECKED_IN);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- create //
|
||||||
|
|
||||||
|
private final class CreateConnectionTask implements Callable<ConnectionHandler> {
|
||||||
|
|
||||||
|
private boolean initial;
|
||||||
|
|
||||||
|
// initial connections do not take configuration.maxSize into account
|
||||||
|
private CreateConnectionTask initial() {
|
||||||
|
initial = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConnectionHandler call() throws SQLException {
|
||||||
|
if (!initial && allConnections.size() >= configuration.maxSize()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
fireBeforeConnectionCreation(listeners);
|
||||||
|
long metricsStamp = metricsRepository.beforeConnectionCreation();
|
||||||
|
|
||||||
|
try {
|
||||||
|
ConnectionHandler handler = new ConnectionHandler(connectionFactory.createConnection(), ConnectionPool.this);
|
||||||
|
metricsRepository.afterConnectionCreation(metricsStamp);
|
||||||
|
|
||||||
|
if (!configuration.maxLifetime().isZero()) {
|
||||||
|
handler.setMaxLifetimeTask(housekeepingExecutor.schedule(new FlushTask(GRACEFUL, handler), configuration.maxLifetime().toNanos(), NANOSECONDS));
|
||||||
|
}
|
||||||
|
|
||||||
|
fireOnConnectionCreation(listeners, handler);
|
||||||
|
fireOnConnectionCreateInterceptor(interceptors, handler);
|
||||||
|
|
||||||
|
handler.setState(CHECKED_IN);
|
||||||
|
allConnections.add(handler);
|
||||||
|
|
||||||
|
maxUsed.accumulate(allConnections.size());
|
||||||
|
fireOnConnectionPooled(listeners, handler);
|
||||||
|
|
||||||
|
return handler;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
fireOnWarning(listeners, e);
|
||||||
|
throw e;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
fireOnWarning(listeners, "Failed to create connection due to " + t.getClass().getSimpleName());
|
||||||
|
throw t;
|
||||||
|
} finally {
|
||||||
|
// not strictly needed, but not harmful either
|
||||||
|
synchronizer.releaseConditional();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- flush //
|
||||||
|
|
||||||
|
private final class FlushTask implements Runnable {
|
||||||
|
|
||||||
|
private final XbibDataSource.FlushMode mode;
|
||||||
|
private final ConnectionHandler handler;
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
FlushTask(XbibDataSource.FlushMode mode) {
|
||||||
|
this.mode = mode;
|
||||||
|
this.handler = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"WeakerAccess", "SameParameterValue"})
|
||||||
|
FlushTask(XbibDataSource.FlushMode mode, ConnectionHandler handler) {
|
||||||
|
this.mode = mode;
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
for (ConnectionHandler ch : handler != null ? Collections.singleton(handler) : allConnections) {
|
||||||
|
fireBeforeConnectionFlush(listeners, ch);
|
||||||
|
flush(mode, ch);
|
||||||
|
}
|
||||||
|
afterFlush(mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void flush(XbibDataSource.FlushMode mode, ConnectionHandler handler) {
|
||||||
|
switch (mode) {
|
||||||
|
case ALL:
|
||||||
|
handler.setState(FLUSH);
|
||||||
|
flushHandler(handler);
|
||||||
|
break;
|
||||||
|
case GRACEFUL:
|
||||||
|
if (handler.setState(CHECKED_IN, FLUSH)) {
|
||||||
|
flushHandler(handler);
|
||||||
|
} else if (!handler.setState(CHECKED_OUT, FLUSH) && handler.isAcquirable()) {
|
||||||
|
// concurrency caused both transitions fail but handler is still acquirable. re-schedule this task.
|
||||||
|
housekeepingExecutor.execute(this);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case LEAK:
|
||||||
|
if (handler.isLeak(configuration.leakTimeout()) && handler.setState(CHECKED_OUT, FLUSH)) {
|
||||||
|
flushHandler(handler);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case IDLE:
|
||||||
|
if (allConnections.size() > configuration.minSize() && handler.setState(CHECKED_IN, FLUSH)) {
|
||||||
|
flushHandler(handler);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case INVALID:
|
||||||
|
fireBeforeConnectionValidation(listeners, handler);
|
||||||
|
if (handler.setState(CHECKED_IN, VALIDATION)) {
|
||||||
|
if (handler.isValid() && handler.setState(VALIDATION, CHECKED_IN)) {
|
||||||
|
fireOnConnectionValid(listeners, handler);
|
||||||
|
} else {
|
||||||
|
handler.setState(VALIDATION, FLUSH);
|
||||||
|
fireOnConnectionInvalid(listeners, handler);
|
||||||
|
flushHandler(handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void flushHandler(ConnectionHandler handler) {
|
||||||
|
allConnections.remove(handler);
|
||||||
|
synchronizer.releaseConditional();
|
||||||
|
metricsRepository.afterConnectionFlush();
|
||||||
|
fireOnConnectionFlush(listeners, handler);
|
||||||
|
housekeepingExecutor.execute(new DestroyConnectionTask(handler));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void afterFlush(XbibDataSource.FlushMode mode) {
|
||||||
|
switch (mode) {
|
||||||
|
case ALL:
|
||||||
|
case GRACEFUL:
|
||||||
|
case INVALID:
|
||||||
|
case LEAK:
|
||||||
|
case FILL:
|
||||||
|
// refill to minSize
|
||||||
|
housekeepingExecutor.execute(new FillTask());
|
||||||
|
break;
|
||||||
|
case IDLE:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fireOnWarning(listeners, "Unsupported Flush mode " + mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- fill task //
|
||||||
|
|
||||||
|
private final class FillTask implements Runnable {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
for (int n = configuration.minSize() - allConnections.size(); n > 0; n--) {
|
||||||
|
housekeepingExecutor.executeNow(new CreateConnectionTask());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- leak detection //
|
||||||
|
|
||||||
|
private final class LeakTask implements Runnable {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
housekeepingExecutor.schedule(this, configuration.leakTimeout().toNanos(), NANOSECONDS);
|
||||||
|
|
||||||
|
for (ConnectionHandler handler : allConnections) {
|
||||||
|
housekeepingExecutor.execute(new LeakConnectionTask(handler));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LeakConnectionTask implements Runnable {
|
||||||
|
|
||||||
|
private final ConnectionHandler handler;
|
||||||
|
|
||||||
|
LeakConnectionTask(ConnectionHandler handler) {
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
fireBeforeConnectionLeak(listeners, handler);
|
||||||
|
if (handler.isLeak(configuration.leakTimeout())) {
|
||||||
|
metricsRepository.afterLeakDetection();
|
||||||
|
fireOnConnectionLeak(listeners, handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- validation //
|
||||||
|
|
||||||
|
private final class ValidationTask implements Runnable {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
housekeepingExecutor.schedule(this, configuration.validationTimeout().toNanos(), NANOSECONDS);
|
||||||
|
|
||||||
|
for (ConnectionHandler handler : allConnections) {
|
||||||
|
housekeepingExecutor.execute(new ValidateConnectionTask(handler));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ValidateConnectionTask implements Runnable {
|
||||||
|
|
||||||
|
private final ConnectionHandler handler;
|
||||||
|
|
||||||
|
ValidateConnectionTask(ConnectionHandler handler) {
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (handler.setState(CHECKED_IN, VALIDATION)) {
|
||||||
|
performValidation(handler, CHECKED_IN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- reap //
|
||||||
|
|
||||||
|
private final class ReapTask implements Runnable {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
housekeepingExecutor.schedule(this, configuration.reapTimeout().toNanos(), NANOSECONDS);
|
||||||
|
|
||||||
|
// reset the thead local cache
|
||||||
|
localCache.reset();
|
||||||
|
|
||||||
|
for (ConnectionHandler handler : allConnections) {
|
||||||
|
housekeepingExecutor.execute(new ReapConnectionTask(handler));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ReapConnectionTask implements Runnable {
|
||||||
|
|
||||||
|
private final ConnectionHandler handler;
|
||||||
|
|
||||||
|
ReapConnectionTask(ConnectionHandler handler) {
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
fireBeforeConnectionReap(listeners, handler);
|
||||||
|
if (allConnections.size() > configuration.minSize() && handler.setState(CHECKED_IN, FLUSH)) {
|
||||||
|
if (handler.isIdle(configuration.reapTimeout())) {
|
||||||
|
removeFromPool(handler);
|
||||||
|
metricsRepository.afterConnectionReap();
|
||||||
|
fireOnConnectionReap(listeners, handler);
|
||||||
|
} else {
|
||||||
|
handler.setState(CHECKED_IN);
|
||||||
|
// for debug, something like: fireOnWarning( listeners, "Connection " + handler.getConnection() + " used recently. Do not reap!" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- destroy //
|
||||||
|
|
||||||
|
private final class DestroyConnectionTask implements Runnable {
|
||||||
|
|
||||||
|
private final ConnectionHandler handler;
|
||||||
|
|
||||||
|
DestroyConnectionTask(ConnectionHandler handler) {
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
fireBeforeConnectionDestroy(listeners, handler);
|
||||||
|
try {
|
||||||
|
fireOnConnectionDestroyInterceptor(interceptors, handler);
|
||||||
|
handler.closeConnection();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
fireOnWarning(listeners, e);
|
||||||
|
}
|
||||||
|
metricsRepository.afterConnectionDestroy();
|
||||||
|
fireOnConnectionDestroy(listeners, handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
115
jdbc-pool/src/main/java/org/xbib/jdbc/pool/DataSource.java
Normal file
115
jdbc-pool/src/main/java/org/xbib/jdbc/pool/DataSource.java
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
package org.xbib.jdbc.pool;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.SQLFeatureNotSupportedException;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import org.xbib.jdbc.pool.api.XbibDataSource;
|
||||||
|
import org.xbib.jdbc.pool.api.XbibDataSourceListener;
|
||||||
|
import org.xbib.jdbc.pool.api.XbibDataSourceMetrics;
|
||||||
|
import org.xbib.jdbc.pool.api.XbibPoolInterceptor;
|
||||||
|
import org.xbib.jdbc.pool.api.configuration.XbibDataSourceConfiguration;
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
|
||||||
|
public final class DataSource implements XbibDataSource {
|
||||||
|
|
||||||
|
private final XbibDataSourceConfiguration configuration;
|
||||||
|
private final Pool connectionPool;
|
||||||
|
|
||||||
|
public DataSource(XbibDataSourceConfiguration dataSourceConfiguration, XbibDataSourceListener... listeners) {
|
||||||
|
configuration = dataSourceConfiguration;
|
||||||
|
if (configuration.dataSourceImplementation() == XbibDataSourceConfiguration.DataSourceImplementation.XBIB_POOLLESS) {
|
||||||
|
connectionPool = new Poolless(dataSourceConfiguration.connectionPoolConfiguration(), listeners);
|
||||||
|
} else {
|
||||||
|
connectionPool = new ConnectionPool(dataSourceConfiguration.connectionPoolConfiguration(), listeners);
|
||||||
|
}
|
||||||
|
|
||||||
|
dataSourceConfiguration.registerMetricsEnabledListener(connectionPool);
|
||||||
|
connectionPool.onMetricsEnabled(dataSourceConfiguration.metricsEnabled());
|
||||||
|
connectionPool.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<XbibPoolInterceptor> getPoolInterceptors() {
|
||||||
|
return connectionPool.getPoolInterceptors();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPoolInterceptors(Collection<? extends XbibPoolInterceptor> interceptors) {
|
||||||
|
connectionPool.setPoolInterceptors(interceptors == null ? emptyList() : interceptors);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public XbibDataSourceConfiguration getConfiguration() {
|
||||||
|
return configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public XbibDataSourceMetrics getMetrics() {
|
||||||
|
return connectionPool.getMetrics();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flush(FlushMode mode) {
|
||||||
|
connectionPool.flushPool(mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isHealthy(boolean newConnection) throws SQLException {
|
||||||
|
return connectionPool.isHealthy(newConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
connectionPool.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Connection getConnection() throws SQLException {
|
||||||
|
return connectionPool.getConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Connection getConnection(String username, String password) throws SQLException {
|
||||||
|
throw new SQLException("username and password combination invalid on a pooled data source!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T unwrap(Class<T> target) throws SQLException {
|
||||||
|
return target.cast(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isWrapperFor(Class<?> target) throws SQLException {
|
||||||
|
return target.isInstance(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PrintWriter getLogWriter() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLogWriter(PrintWriter out) throws SQLException {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLoginTimeout() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLoginTimeout(int seconds) throws SQLException {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
|
||||||
|
throw new SQLFeatureNotSupportedException("Not Supported");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package org.xbib.jdbc.pool;
|
||||||
|
|
||||||
|
import org.xbib.jdbc.pool.api.XbibDataSource;
|
||||||
|
import org.xbib.jdbc.pool.api.XbibDataSourceListener;
|
||||||
|
import org.xbib.jdbc.pool.api.XbibDataSourceProvider;
|
||||||
|
import org.xbib.jdbc.pool.api.configuration.XbibDataSourceConfiguration;
|
||||||
|
import static org.xbib.jdbc.pool.api.configuration.XbibDataSourceConfiguration.DataSourceImplementation.XBIB;
|
||||||
|
import static org.xbib.jdbc.pool.api.configuration.XbibDataSourceConfiguration.DataSourceImplementation.XBIB_POOLLESS;
|
||||||
|
|
||||||
|
public final class DataSourceProvider implements XbibDataSourceProvider {
|
||||||
|
|
||||||
|
public DataSourceProvider() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public XbibDataSource getDataSource(XbibDataSourceConfiguration config, XbibDataSourceListener... listeners) {
|
||||||
|
return config.dataSourceImplementation() == XBIB || config.dataSourceImplementation() == XBIB_POOLLESS ? new DataSource(config, listeners) : null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,222 @@
|
||||||
|
package org.xbib.jdbc.pool;
|
||||||
|
|
||||||
|
import java.text.MessageFormat;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.concurrent.atomic.LongAccumulator;
|
||||||
|
import java.util.concurrent.atomic.LongAdder;
|
||||||
|
import static java.lang.System.nanoTime;
|
||||||
|
import static java.time.Duration.ZERO;
|
||||||
|
import static java.time.Duration.ofNanos;
|
||||||
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
|
||||||
|
public final class DefaultMetricsRepository implements MetricsRepository {
|
||||||
|
|
||||||
|
private static final String FORMAT_1 = "Connections: {0} created | {1} invalid | {2} reap | {3} flush | {4} destroyed";
|
||||||
|
private static final String FORMAT_2 = "Pool: {0} available | {1} active | {2} max | {3} acquired | {4} returned";
|
||||||
|
private static final String FORMAT_3 = "Created duration: {0,number,000.000}ms average | {1}ms max | {2}ms total";
|
||||||
|
private static final String FORMAT_4 = "Acquire duration: {0,number,000.000}ms average | {1}ms max | {2}ms total";
|
||||||
|
private static final String FORMAT_5 = "Threads awaiting: {0}";
|
||||||
|
|
||||||
|
private final Pool connectionPool;
|
||||||
|
private final LongAdder creationCount = new LongAdder();
|
||||||
|
private final LongAdder creationTotalTime = new LongAdder();
|
||||||
|
private final LongAdder acquireCount = new LongAdder();
|
||||||
|
private final LongAdder returnCount = new LongAdder();
|
||||||
|
private final LongAdder acquireTotalTime = new LongAdder();
|
||||||
|
private final LongAdder leakDetectionCount = new LongAdder();
|
||||||
|
private final LongAdder invalidCount = new LongAdder();
|
||||||
|
private final LongAdder flushCount = new LongAdder();
|
||||||
|
private final LongAdder reapCount = new LongAdder();
|
||||||
|
private final LongAdder destroyCount = new LongAdder();
|
||||||
|
private final LongAccumulator maxCreatedDuration = new LongAccumulator(Long::max, 0);
|
||||||
|
private final LongAccumulator maxAcquireDuration = new LongAccumulator(Long::max, 0);
|
||||||
|
|
||||||
|
public DefaultMetricsRepository(Pool pool) {
|
||||||
|
connectionPool = pool;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long beforeConnectionCreation() {
|
||||||
|
return nanoTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterConnectionCreation(long timestamp) {
|
||||||
|
long duration = nanoTime() - timestamp;
|
||||||
|
creationCount.increment();
|
||||||
|
creationTotalTime.add(duration);
|
||||||
|
maxCreatedDuration.accumulate(duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long beforeConnectionAcquire() {
|
||||||
|
return nanoTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterConnectionAcquire(long timestamp) {
|
||||||
|
long duration = nanoTime() - timestamp;
|
||||||
|
acquireCount.increment();
|
||||||
|
acquireTotalTime.add(duration);
|
||||||
|
maxAcquireDuration.accumulate(duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterConnectionReturn() {
|
||||||
|
returnCount.increment();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterLeakDetection() {
|
||||||
|
leakDetectionCount.increment();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterConnectionInvalid() {
|
||||||
|
invalidCount.increment();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterConnectionFlush() {
|
||||||
|
flushCount.increment();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterConnectionReap() {
|
||||||
|
reapCount.increment();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterConnectionDestroy() {
|
||||||
|
destroyCount.increment();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long creationCount() {
|
||||||
|
return creationCount.longValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Duration creationTimeAverage() {
|
||||||
|
if (creationCount.longValue() == 0) {
|
||||||
|
return ZERO;
|
||||||
|
}
|
||||||
|
return ofNanos(creationTotalTime.longValue() / creationCount.longValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Duration creationTimeMax() {
|
||||||
|
return ofNanos(maxCreatedDuration.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Duration creationTimeTotal() {
|
||||||
|
return ofNanos(creationTotalTime.longValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long acquireCount() {
|
||||||
|
return acquireCount.longValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long leakDetectionCount() {
|
||||||
|
return leakDetectionCount.longValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long invalidCount() {
|
||||||
|
return invalidCount.longValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long flushCount() {
|
||||||
|
return flushCount.longValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long reapCount() {
|
||||||
|
return reapCount.longValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long destroyCount() {
|
||||||
|
return destroyCount.longValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long activeCount() {
|
||||||
|
return connectionPool.activeCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long maxUsedCount() {
|
||||||
|
return connectionPool.maxUsedCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long availableCount() {
|
||||||
|
return connectionPool.availableCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Duration blockingTimeAverage() {
|
||||||
|
if (acquireCount.longValue() == 0) {
|
||||||
|
return ZERO;
|
||||||
|
}
|
||||||
|
return ofNanos(acquireTotalTime.longValue() / acquireCount.longValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Duration blockingTimeMax() {
|
||||||
|
return ofNanos(maxAcquireDuration.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Duration blockingTimeTotal() {
|
||||||
|
return ofNanos(acquireTotalTime.longValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long awaitingCount() {
|
||||||
|
return connectionPool.awaitingCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
creationCount.reset();
|
||||||
|
creationTotalTime.reset();
|
||||||
|
acquireCount.reset();
|
||||||
|
acquireTotalTime.reset();
|
||||||
|
leakDetectionCount.reset();
|
||||||
|
invalidCount.reset();
|
||||||
|
|
||||||
|
maxCreatedDuration.reset();
|
||||||
|
maxAcquireDuration.reset();
|
||||||
|
connectionPool.resetMaxUsedCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
double avgCreationMs = (double) creationTimeAverage().toNanos() / MILLISECONDS.toNanos(1);
|
||||||
|
double avgBlockingMs = (double) blockingTimeAverage().toNanos() / MILLISECONDS.toNanos(1);
|
||||||
|
|
||||||
|
String nl = System.lineSeparator();
|
||||||
|
|
||||||
|
StringBuffer buffer = new StringBuffer(500);
|
||||||
|
buffer.append(nl).append("===").append(nl);
|
||||||
|
new MessageFormat(FORMAT_1, Locale.ROOT).format(new Object[]{creationCount, invalidCount, reapCount, flushCount, destroyCount}, buffer, null).append(nl);
|
||||||
|
new MessageFormat(FORMAT_2, Locale.ROOT).format(new Object[]{availableCount(), activeCount(), maxUsedCount(), acquireCount, returnCount}, buffer, null).append(nl);
|
||||||
|
new MessageFormat(FORMAT_3, Locale.ROOT).format(new Object[]{avgCreationMs, creationTimeMax().toMillis(), creationTimeTotal().toMillis()}, buffer, null).append(nl);
|
||||||
|
new MessageFormat(FORMAT_4, Locale.ROOT).format(new Object[]{avgBlockingMs, blockingTimeMax().toMillis(), blockingTimeTotal().toMillis()}, buffer, null).append(nl);
|
||||||
|
new MessageFormat(FORMAT_5, Locale.ROOT).format(new Object[]{awaitingCount()}, buffer, null).append(nl);
|
||||||
|
return buffer.append("===").toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
package org.xbib.jdbc.pool;
|
||||||
|
|
||||||
|
import org.xbib.jdbc.pool.api.XbibDataSourceMetrics;
|
||||||
|
|
||||||
|
public interface MetricsRepository extends XbibDataSourceMetrics {
|
||||||
|
|
||||||
|
default long beforeConnectionCreation() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
default void afterConnectionCreation(long timestamp) {
|
||||||
|
}
|
||||||
|
|
||||||
|
default long beforeConnectionAcquire() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
default void afterConnectionAcquire(long timestamp) {
|
||||||
|
}
|
||||||
|
|
||||||
|
default void afterConnectionReturn() {
|
||||||
|
}
|
||||||
|
|
||||||
|
default void afterLeakDetection() {
|
||||||
|
}
|
||||||
|
|
||||||
|
default void afterConnectionInvalid() {
|
||||||
|
}
|
||||||
|
|
||||||
|
default void afterConnectionFlush() {
|
||||||
|
}
|
||||||
|
|
||||||
|
default void afterConnectionReap() {
|
||||||
|
}
|
||||||
|
|
||||||
|
default void afterConnectionDestroy() {
|
||||||
|
}
|
||||||
|
|
||||||
|
final class EmptyMetricsRepository implements MetricsRepository {
|
||||||
|
|
||||||
|
public EmptyMetricsRepository() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Metrics Disabled";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
51
jdbc-pool/src/main/java/org/xbib/jdbc/pool/Pool.java
Normal file
51
jdbc-pool/src/main/java/org/xbib/jdbc/pool/Pool.java
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package org.xbib.jdbc.pool;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import org.xbib.jdbc.pool.api.XbibDataSource.FlushMode;
|
||||||
|
import org.xbib.jdbc.pool.api.XbibDataSourceListener;
|
||||||
|
import org.xbib.jdbc.pool.api.XbibDataSourceMetrics;
|
||||||
|
import org.xbib.jdbc.pool.api.XbibPoolInterceptor;
|
||||||
|
import org.xbib.jdbc.pool.api.configuration.XbibConnectionPoolConfiguration;
|
||||||
|
import org.xbib.jdbc.pool.api.configuration.XbibDataSourceConfiguration.MetricsEnabledListener;
|
||||||
|
import org.xbib.jdbc.pool.api.transaction.TransactionIntegration.ResourceRecoveryFactory;
|
||||||
|
|
||||||
|
public interface Pool extends MetricsEnabledListener, AutoCloseable, ResourceRecoveryFactory {
|
||||||
|
|
||||||
|
void init();
|
||||||
|
|
||||||
|
Connection getConnection() throws SQLException;
|
||||||
|
|
||||||
|
XbibConnectionPoolConfiguration getConfiguration();
|
||||||
|
|
||||||
|
XbibDataSourceMetrics getMetrics();
|
||||||
|
|
||||||
|
XbibDataSourceListener[] getListeners();
|
||||||
|
|
||||||
|
List<XbibPoolInterceptor> getPoolInterceptors();
|
||||||
|
|
||||||
|
void setPoolInterceptors(Collection<? extends XbibPoolInterceptor> list);
|
||||||
|
|
||||||
|
void returnConnectionHandler(ConnectionHandler handler) throws SQLException;
|
||||||
|
|
||||||
|
void flushPool(FlushMode mode);
|
||||||
|
|
||||||
|
boolean isHealthy(boolean newConnection) throws SQLException;
|
||||||
|
|
||||||
|
int defaultJdbcIsolationLevel();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void close();
|
||||||
|
|
||||||
|
long activeCount();
|
||||||
|
|
||||||
|
long maxUsedCount();
|
||||||
|
|
||||||
|
long availableCount();
|
||||||
|
|
||||||
|
long awaitingCount();
|
||||||
|
|
||||||
|
void resetMaxUsedCount();
|
||||||
|
}
|
418
jdbc-pool/src/main/java/org/xbib/jdbc/pool/Poolless.java
Normal file
418
jdbc-pool/src/main/java/org/xbib/jdbc/pool/Poolless.java
Normal file
|
@ -0,0 +1,418 @@
|
||||||
|
package org.xbib.jdbc.pool;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.atomic.LongAccumulator;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import javax.sql.XAConnection;
|
||||||
|
import org.xbib.jdbc.pool.MetricsRepository.EmptyMetricsRepository;
|
||||||
|
import org.xbib.jdbc.pool.api.XbibDataSource;
|
||||||
|
import org.xbib.jdbc.pool.api.XbibDataSourceListener;
|
||||||
|
import org.xbib.jdbc.pool.api.XbibPoolInterceptor;
|
||||||
|
import org.xbib.jdbc.pool.api.configuration.XbibConnectionPoolConfiguration;
|
||||||
|
import org.xbib.jdbc.pool.api.transaction.TransactionIntegration;
|
||||||
|
import org.xbib.jdbc.pool.util.StampedCopyOnWriteArrayList;
|
||||||
|
import org.xbib.jdbc.pool.util.XbibSynchronizer;
|
||||||
|
import static java.lang.Integer.toHexString;
|
||||||
|
import static java.lang.System.identityHashCode;
|
||||||
|
import static java.lang.System.nanoTime;
|
||||||
|
import static java.lang.Thread.currentThread;
|
||||||
|
import static java.util.Arrays.copyOfRange;
|
||||||
|
import static java.util.Collections.unmodifiableList;
|
||||||
|
import static java.util.stream.Collectors.joining;
|
||||||
|
import static java.util.stream.Collectors.toList;
|
||||||
|
import static org.xbib.jdbc.pool.ConnectionHandler.State.CHECKED_OUT;
|
||||||
|
import static org.xbib.jdbc.pool.ConnectionHandler.State.FLUSH;
|
||||||
|
import static org.xbib.jdbc.pool.ConnectionHandler.State.VALIDATION;
|
||||||
|
import static org.xbib.jdbc.pool.api.XbibDataSource.FlushMode.ALL;
|
||||||
|
import static org.xbib.jdbc.pool.api.XbibDataSource.FlushMode.LEAK;
|
||||||
|
import static org.xbib.jdbc.pool.api.configuration.XbibConnectionPoolConfiguration.MultipleAcquisitionAction.OFF;
|
||||||
|
import static org.xbib.jdbc.pool.util.InterceptorHelper.fireOnConnectionAcquiredInterceptor;
|
||||||
|
import static org.xbib.jdbc.pool.util.InterceptorHelper.fireOnConnectionCreateInterceptor;
|
||||||
|
import static org.xbib.jdbc.pool.util.InterceptorHelper.fireOnConnectionDestroyInterceptor;
|
||||||
|
import static org.xbib.jdbc.pool.util.InterceptorHelper.fireOnConnectionReturnInterceptor;
|
||||||
|
import static org.xbib.jdbc.pool.util.ListenerHelper.fireBeforeConnectionAcquire;
|
||||||
|
import static org.xbib.jdbc.pool.util.ListenerHelper.fireBeforeConnectionCreation;
|
||||||
|
import static org.xbib.jdbc.pool.util.ListenerHelper.fireBeforeConnectionDestroy;
|
||||||
|
import static org.xbib.jdbc.pool.util.ListenerHelper.fireBeforeConnectionFlush;
|
||||||
|
import static org.xbib.jdbc.pool.util.ListenerHelper.fireBeforeConnectionReturn;
|
||||||
|
import static org.xbib.jdbc.pool.util.ListenerHelper.fireBeforeConnectionValidation;
|
||||||
|
import static org.xbib.jdbc.pool.util.ListenerHelper.fireOnConnectionAcquired;
|
||||||
|
import static org.xbib.jdbc.pool.util.ListenerHelper.fireOnConnectionCreation;
|
||||||
|
import static org.xbib.jdbc.pool.util.ListenerHelper.fireOnConnectionDestroy;
|
||||||
|
import static org.xbib.jdbc.pool.util.ListenerHelper.fireOnConnectionFlush;
|
||||||
|
import static org.xbib.jdbc.pool.util.ListenerHelper.fireOnConnectionInvalid;
|
||||||
|
import static org.xbib.jdbc.pool.util.ListenerHelper.fireOnConnectionPooled;
|
||||||
|
import static org.xbib.jdbc.pool.util.ListenerHelper.fireOnConnectionValid;
|
||||||
|
import static org.xbib.jdbc.pool.util.ListenerHelper.fireOnInfo;
|
||||||
|
import static org.xbib.jdbc.pool.util.ListenerHelper.fireOnWarning;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alternative implementation of ConnectionPool for the special case of flush-on-close (and min-size == 0)
|
||||||
|
* In particular, this removes the need for the executor.
|
||||||
|
* Also, there is no thread-local connection cache as connections are not reused
|
||||||
|
*/
|
||||||
|
public final class Poolless implements Pool {
|
||||||
|
|
||||||
|
private final XbibConnectionPoolConfiguration configuration;
|
||||||
|
private final XbibDataSourceListener[] listeners;
|
||||||
|
|
||||||
|
private final StampedCopyOnWriteArrayList<ConnectionHandler> allConnections;
|
||||||
|
|
||||||
|
private final XbibSynchronizer synchronizer;
|
||||||
|
private final ConnectionFactory connectionFactory;
|
||||||
|
private final TransactionIntegration transactionIntegration;
|
||||||
|
|
||||||
|
private final LongAccumulator maxUsed = new LongAccumulator(Math::max, Long.MIN_VALUE);
|
||||||
|
private final AtomicInteger activeCount = new AtomicInteger();
|
||||||
|
|
||||||
|
private List<XbibPoolInterceptor> interceptors;
|
||||||
|
private MetricsRepository metricsRepository;
|
||||||
|
private volatile boolean shutdown;
|
||||||
|
|
||||||
|
public Poolless(XbibConnectionPoolConfiguration configuration, XbibDataSourceListener... listeners) {
|
||||||
|
this.configuration = configuration;
|
||||||
|
this.listeners = listeners;
|
||||||
|
|
||||||
|
allConnections = new StampedCopyOnWriteArrayList<>(ConnectionHandler.class);
|
||||||
|
|
||||||
|
synchronizer = new XbibSynchronizer();
|
||||||
|
connectionFactory = new ConnectionFactory(configuration.connectionFactoryConfiguration(), listeners);
|
||||||
|
transactionIntegration = configuration.transactionIntegration();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void init() {
|
||||||
|
if (!configuration.maxLifetime().isZero()) {
|
||||||
|
fireOnInfo(listeners, "Max lifetime not supported in pool-less mode");
|
||||||
|
}
|
||||||
|
if (configuration.validateOnBorrow()) {
|
||||||
|
fireOnInfo(listeners, "Validation on borrow is not supported in pool-less mode");
|
||||||
|
}
|
||||||
|
if (!configuration.idleValidationTimeout().isZero()) {
|
||||||
|
fireOnInfo(listeners, "Idle validation not supported in pool-less mode");
|
||||||
|
}
|
||||||
|
if (!configuration.leakTimeout().isZero()) {
|
||||||
|
fireOnInfo(listeners, "Leak detection not pro-active in pool-less mode");
|
||||||
|
}
|
||||||
|
if (!configuration.reapTimeout().isZero()) {
|
||||||
|
fireOnInfo(listeners, "Connection reap not supported in pool-less mode");
|
||||||
|
}
|
||||||
|
if (configuration.initialSize() != 0) {
|
||||||
|
fireOnInfo(listeners, "Initial size is zero in pool-less mode");
|
||||||
|
}
|
||||||
|
if (configuration.minSize() != 0) {
|
||||||
|
fireOnInfo(listeners, "Min size always zero in pool-less mode");
|
||||||
|
}
|
||||||
|
transactionIntegration.addResourceRecoveryFactory(connectionFactory.hasRecoveryCredentials() ? connectionFactory : this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public XbibConnectionPoolConfiguration getConfiguration() {
|
||||||
|
return configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int defaultJdbcIsolationLevel() {
|
||||||
|
return connectionFactory.defaultJdbcIsolationLevel();
|
||||||
|
}
|
||||||
|
|
||||||
|
public XbibDataSourceListener[] getListeners() {
|
||||||
|
return listeners;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<XbibPoolInterceptor> getPoolInterceptors() {
|
||||||
|
return unmodifiableList(interceptors);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"StringConcatenation", "SingleCharacterStringConcatenation"})
|
||||||
|
public void setPoolInterceptors(Collection<? extends XbibPoolInterceptor> list) {
|
||||||
|
if (list.stream().anyMatch(i -> i.getPriority() < 0)) {
|
||||||
|
throw new IllegalArgumentException("Negative priority values on XbibPoolInterceptor are reserved.");
|
||||||
|
}
|
||||||
|
if (list.isEmpty() && (interceptors == null || interceptors.isEmpty())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
interceptors = list.stream().sorted(XbibPoolInterceptor.DEFAULT_COMPARATOR).collect(toList());
|
||||||
|
|
||||||
|
Function<XbibPoolInterceptor, String> interceptorName = i -> i.getClass().getName() + "@" + toHexString(identityHashCode(i)) + " (priority " + i.getPriority() + ")";
|
||||||
|
fireOnInfo(listeners, "Pool interceptors: " + interceptors.stream().map(interceptorName).collect(joining(" >>> ", "[", "]")));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRecoverable() {
|
||||||
|
return connectionFactory.isRecoverable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public XAConnection getRecoveryConnection() throws SQLException {
|
||||||
|
long stamp = beforeAcquire();
|
||||||
|
checkMultipleAcquisition();
|
||||||
|
|
||||||
|
ConnectionHandler checkedOutHandler = handlerFromSharedCache();
|
||||||
|
fireOnConnectionAcquiredInterceptor(interceptors, checkedOutHandler);
|
||||||
|
afterAcquire(stamp, checkedOutHandler, false);
|
||||||
|
return checkedOutHandler.xaConnectionWrapper();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
transactionIntegration.removeResourceRecoveryFactory(connectionFactory.hasRecoveryCredentials() ? connectionFactory : this);
|
||||||
|
shutdown = true;
|
||||||
|
|
||||||
|
for (ConnectionHandler handler : allConnections) {
|
||||||
|
handler.setState(FLUSH);
|
||||||
|
destroyConnection(handler);
|
||||||
|
}
|
||||||
|
allConnections.clear();
|
||||||
|
|
||||||
|
synchronizer.release(synchronizer.getQueueLength());
|
||||||
|
}
|
||||||
|
|
||||||
|
private long beforeAcquire() throws SQLException {
|
||||||
|
fireBeforeConnectionAcquire(listeners);
|
||||||
|
if (shutdown) {
|
||||||
|
throw new SQLException("This pool is closed and does not handle any more connections!");
|
||||||
|
}
|
||||||
|
return metricsRepository.beforeConnectionAcquire();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkMultipleAcquisition() throws SQLException {
|
||||||
|
if (configuration.multipleAcquisition() != OFF) {
|
||||||
|
for (ConnectionHandler handler : allConnections) {
|
||||||
|
if (handler.getHoldingThread() == currentThread()) {
|
||||||
|
switch (configuration.multipleAcquisition()) {
|
||||||
|
case STRICT:
|
||||||
|
throw new SQLException("Acquisition of multiple connections by the same Thread.");
|
||||||
|
case WARN:
|
||||||
|
fireOnWarning(listeners, "Acquisition of multiple connections by the same Thread. This can lead to pool exhaustion and eventually a deadlock!");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Connection getConnection() throws SQLException {
|
||||||
|
long stamp = beforeAcquire();
|
||||||
|
|
||||||
|
ConnectionHandler checkedOutHandler = handlerFromTransaction();
|
||||||
|
if (checkedOutHandler != null) {
|
||||||
|
// AG-140 - If associate throws here is fine, it's assumed the synchronization that returns the connection has been registered
|
||||||
|
transactionIntegration.associate(checkedOutHandler, checkedOutHandler.getXaResource());
|
||||||
|
afterAcquire(stamp, checkedOutHandler, false);
|
||||||
|
return checkedOutHandler.connectionWrapper();
|
||||||
|
}
|
||||||
|
checkMultipleAcquisition();
|
||||||
|
|
||||||
|
try {
|
||||||
|
checkedOutHandler = handlerFromSharedCache();
|
||||||
|
transactionIntegration.associate(checkedOutHandler, checkedOutHandler.getXaResource());
|
||||||
|
fireOnConnectionAcquiredInterceptor(interceptors, checkedOutHandler);
|
||||||
|
afterAcquire(stamp, checkedOutHandler, true);
|
||||||
|
return checkedOutHandler.connectionWrapper();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
if (checkedOutHandler != null) {
|
||||||
|
// AG-140 - Flush handler to prevent leak
|
||||||
|
flushHandler(checkedOutHandler);
|
||||||
|
}
|
||||||
|
throw t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConnectionHandler handlerFromTransaction() throws SQLException {
|
||||||
|
return (ConnectionHandler) transactionIntegration.getTransactionAware();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConnectionHandler handlerFromSharedCache() throws SQLException {
|
||||||
|
long remaining = configuration.acquisitionTimeout().toNanos();
|
||||||
|
remaining = remaining > 0 ? remaining : Long.MAX_VALUE;
|
||||||
|
try {
|
||||||
|
for (; ; ) {
|
||||||
|
// Try to get a "token" to create a new connection
|
||||||
|
if (activeCount.incrementAndGet() <= configuration.maxSize()) {
|
||||||
|
try {
|
||||||
|
return createConnection();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
activeCount.decrementAndGet();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
activeCount.decrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pool full, will have to wait for a connection to be returned
|
||||||
|
long synchronizationStamp = synchronizer.getStamp();
|
||||||
|
long start = nanoTime();
|
||||||
|
if (remaining < 0 || !synchronizer.tryAcquireNanos(synchronizationStamp, remaining)) {
|
||||||
|
throw new SQLException("Sorry, acquisition timeout!");
|
||||||
|
}
|
||||||
|
if (shutdown) {
|
||||||
|
throw new SQLException("Can't create new connection as the pool is shutting down");
|
||||||
|
}
|
||||||
|
remaining -= nanoTime() - start;
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
currentThread().interrupt();
|
||||||
|
throw new SQLException("Interrupted while acquiring");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("SingleCharacterStringConcatenation")
|
||||||
|
private void afterAcquire(long metricsStamp, ConnectionHandler checkedOutHandler, boolean verifyEnlistment) throws SQLException {
|
||||||
|
metricsRepository.afterConnectionAcquire(metricsStamp);
|
||||||
|
fireOnConnectionAcquired(listeners, checkedOutHandler);
|
||||||
|
|
||||||
|
if (verifyEnlistment && !checkedOutHandler.isEnlisted()) {
|
||||||
|
switch (configuration.transactionRequirement()) {
|
||||||
|
case STRICT:
|
||||||
|
returnConnectionHandler(checkedOutHandler);
|
||||||
|
throw new SQLException("Connection acquired without transaction.");
|
||||||
|
case WARN:
|
||||||
|
fireOnWarning(listeners, new SQLException("Connection acquired without transaction."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!configuration.leakTimeout().isZero() || configuration.multipleAcquisition() != OFF) {
|
||||||
|
if (checkedOutHandler.getHoldingThread() != null && checkedOutHandler.getHoldingThread() != currentThread()) {
|
||||||
|
Throwable warn = new Throwable("Shared connection between threads '" + checkedOutHandler.getHoldingThread().getName() + "' and '" + currentThread().getName() + "'");
|
||||||
|
warn.setStackTrace(checkedOutHandler.getHoldingThread().getStackTrace());
|
||||||
|
fireOnWarning(listeners, warn);
|
||||||
|
}
|
||||||
|
checkedOutHandler.setHoldingThread(currentThread());
|
||||||
|
checkedOutHandler.touch();
|
||||||
|
if (configuration.enhancedLeakReport()) {
|
||||||
|
StackTraceElement[] stackTrace = currentThread().getStackTrace();
|
||||||
|
checkedOutHandler.setAcquisitionStackTrace(copyOfRange(stackTrace, 4, stackTrace.length));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void returnConnectionHandler(ConnectionHandler handler) throws SQLException {
|
||||||
|
fireBeforeConnectionReturn(listeners, handler);
|
||||||
|
try {
|
||||||
|
if (!transactionIntegration.disassociate(handler)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
fireOnConnectionReturnInterceptor(interceptors, handler);
|
||||||
|
flushHandler(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMetricsEnabled(boolean metricsEnabled) {
|
||||||
|
metricsRepository = metricsEnabled ? new DefaultMetricsRepository(this) : new EmptyMetricsRepository();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MetricsRepository getMetrics() {
|
||||||
|
return metricsRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long activeCount() {
|
||||||
|
return activeCount.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long availableCount() {
|
||||||
|
return configuration.maxSize() - activeCount.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long maxUsedCount() {
|
||||||
|
return maxUsed.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetMaxUsedCount() {
|
||||||
|
maxUsed.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long awaitingCount() {
|
||||||
|
return synchronizer.getQueueLength();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isHealthy(boolean newConnection) throws SQLException {
|
||||||
|
// the main difference here is that connect will not block (and potentially timeout) on full pool
|
||||||
|
if (newConnection) {
|
||||||
|
activeCount.incrementAndGet();
|
||||||
|
}
|
||||||
|
ConnectionHandler healthHandler = newConnection ? createConnection() : handlerFromSharedCache();
|
||||||
|
|
||||||
|
try {
|
||||||
|
fireBeforeConnectionValidation(listeners, healthHandler);
|
||||||
|
if (healthHandler.setState(CHECKED_OUT, VALIDATION) && healthHandler.isValid() && healthHandler.setState(VALIDATION, CHECKED_OUT)) {
|
||||||
|
fireOnConnectionValid(listeners, healthHandler);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
metricsRepository.afterConnectionInvalid();
|
||||||
|
fireOnConnectionInvalid(listeners, healthHandler);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
returnConnectionHandler(healthHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConnectionHandler createConnection() throws SQLException {
|
||||||
|
fireBeforeConnectionCreation(listeners);
|
||||||
|
long metricsStamp = metricsRepository.beforeConnectionCreation();
|
||||||
|
|
||||||
|
try {
|
||||||
|
ConnectionHandler handler = new ConnectionHandler(connectionFactory.createConnection(), this);
|
||||||
|
metricsRepository.afterConnectionCreation(metricsStamp);
|
||||||
|
|
||||||
|
fireOnConnectionCreation(listeners, handler);
|
||||||
|
fireOnConnectionCreateInterceptor(interceptors, handler);
|
||||||
|
|
||||||
|
handler.setState(CHECKED_OUT);
|
||||||
|
allConnections.add(handler);
|
||||||
|
|
||||||
|
maxUsed.accumulate(allConnections.size());
|
||||||
|
fireOnConnectionPooled(listeners, handler);
|
||||||
|
|
||||||
|
return handler;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
fireOnWarning(listeners, e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void flushPool(XbibDataSource.FlushMode mode) {
|
||||||
|
if (mode == ALL) {
|
||||||
|
for (ConnectionHandler handler : allConnections) {
|
||||||
|
fireBeforeConnectionFlush(listeners, handler);
|
||||||
|
flushHandler(handler);
|
||||||
|
}
|
||||||
|
} else if (mode == LEAK) {
|
||||||
|
for (ConnectionHandler handler : allConnections) {
|
||||||
|
if (handler.isLeak(configuration.leakTimeout())) {
|
||||||
|
fireBeforeConnectionFlush(listeners, handler);
|
||||||
|
flushHandler(handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void flushHandler(ConnectionHandler handler) {
|
||||||
|
handler.setState(FLUSH);
|
||||||
|
allConnections.remove(handler);
|
||||||
|
activeCount.decrementAndGet();
|
||||||
|
synchronizer.releaseConditional();
|
||||||
|
metricsRepository.afterConnectionFlush();
|
||||||
|
fireOnConnectionFlush(listeners, handler);
|
||||||
|
destroyConnection(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void destroyConnection(ConnectionHandler handler) {
|
||||||
|
fireBeforeConnectionDestroy(listeners, handler);
|
||||||
|
try {
|
||||||
|
fireOnConnectionDestroyInterceptor(interceptors, handler);
|
||||||
|
handler.closeConnection();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
fireOnWarning(listeners, e);
|
||||||
|
}
|
||||||
|
metricsRepository.afterConnectionDestroy();
|
||||||
|
fireOnConnectionDestroy(listeners, handler);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
package org.xbib.jdbc.pool.api;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
import org.xbib.jdbc.pool.api.configuration.XbibDataSourceConfiguration;
|
||||||
|
import static java.util.ServiceLoader.load;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension of the DataSource interface that exposes some of its internals.
|
||||||
|
*/
|
||||||
|
public interface XbibDataSource extends DataSource, Closeable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an XbibDataSource from a supplier of the configuration.
|
||||||
|
*/
|
||||||
|
static XbibDataSource from(Supplier<XbibDataSourceConfiguration> configurationSupplier, XbibDataSourceListener... listeners) throws SQLException {
|
||||||
|
return from(configurationSupplier.get(), listeners);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an XbibDataSource from configuration.
|
||||||
|
*/
|
||||||
|
static XbibDataSource from(XbibDataSourceConfiguration configuration, XbibDataSourceListener... listeners) throws SQLException {
|
||||||
|
for (XbibDataSourceProvider provider : load(XbibDataSourceProvider.class, XbibDataSourceProvider.class.getClassLoader())) {
|
||||||
|
XbibDataSource implementation = provider.getDataSource(configuration, listeners);
|
||||||
|
if (implementation != null) {
|
||||||
|
return implementation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to load the implementation using reflection
|
||||||
|
try {
|
||||||
|
Class<? extends XbibDataSource> dataSourceClass = XbibDataSource.class.getClassLoader().loadClass(configuration.dataSourceImplementation().className()).asSubclass(XbibDataSource.class);
|
||||||
|
Constructor<? extends XbibDataSource> dataSourceConstructor = dataSourceClass.getConstructor(XbibDataSourceConfiguration.class, XbibDataSourceListener[].class);
|
||||||
|
return dataSourceConstructor.newInstance(configuration, listeners);
|
||||||
|
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException |
|
||||||
|
InstantiationException e) {
|
||||||
|
throw new SQLException("Could not load the required implementation", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows inspection of the configuration. Some properties allow read / write.
|
||||||
|
*/
|
||||||
|
XbibDataSourceConfiguration getConfiguration();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows access to metrics. If metrics are not enabled, returns default values.
|
||||||
|
*/
|
||||||
|
XbibDataSourceMetrics getMetrics();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a flush action on the connections of the pool.
|
||||||
|
*/
|
||||||
|
void flush(FlushMode mode);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of pool interceptors. Interceptors are sorted from high to low priority.
|
||||||
|
*/
|
||||||
|
List<XbibPoolInterceptor> getPoolInterceptors();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets pool interceptors.
|
||||||
|
*/
|
||||||
|
void setPoolInterceptors(Collection<? extends XbibPoolInterceptor> interceptors);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a health check. The newConnection parameter determines that a new database connection is established for this purpose, otherwise attempts to get a connection from the pool.
|
||||||
|
* <p>
|
||||||
|
* WARNING: Using a new connection may cause the size of the pool to go over max-size.
|
||||||
|
*/
|
||||||
|
default boolean isHealthy(boolean newConnection) throws SQLException {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modes supported on the flush operation.
|
||||||
|
*/
|
||||||
|
enum FlushMode {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All connections are flushed right away.
|
||||||
|
*/
|
||||||
|
ALL,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Idle connections are flushed.
|
||||||
|
*/
|
||||||
|
IDLE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs on-demand validation of idle connections.
|
||||||
|
*/
|
||||||
|
INVALID,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Active connections are flushed on return. Idle connections are flushed immediately.
|
||||||
|
*/
|
||||||
|
GRACEFUL,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flushes connections that have been in use for longer than the specified leak timeout.
|
||||||
|
*/
|
||||||
|
LEAK,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates connections to met the minimum size of the pool.
|
||||||
|
* Used after and increase of minimum size, to make that change effective immediately.
|
||||||
|
*/
|
||||||
|
FILL
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,138 @@
|
||||||
|
package org.xbib.jdbc.pool.api;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interface defines a set of callback methods that are invoked on events considered important for the state of the pool.
|
||||||
|
* It not only allows for logging this events, but also for black-box testing.
|
||||||
|
* NOTE: implementations of this methods should not block / do expensive operations.
|
||||||
|
*/
|
||||||
|
public interface XbibDataSourceListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This callback is invoked whenever a new connection is about to be created.
|
||||||
|
*/
|
||||||
|
default void beforeConnectionCreation() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This callback is invoked for every new connection.
|
||||||
|
*/
|
||||||
|
default void onConnectionCreation(Connection connection) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This callback is invoked right after a connection is added to the pool. The connection may have been acquired concurrently.
|
||||||
|
*/
|
||||||
|
default void onConnectionPooled(Connection connection) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This callback is invoked whenever an application tries to obtain a connection.
|
||||||
|
*/
|
||||||
|
default void beforeConnectionAcquire() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This callback is invoked when a connection is successfully acquired.
|
||||||
|
*/
|
||||||
|
default void onConnectionAcquire(Connection connection) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This callback is invoked before a connection is returned to the pool.
|
||||||
|
*/
|
||||||
|
default void beforeConnectionReturn(Connection connection) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This callback is invoked right after a connection is returned to the pool. The connection may have been acquired concurrently.
|
||||||
|
*/
|
||||||
|
default void onConnectionReturn(Connection connection) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This callback is invoked before checking the leak timeout of a connection.
|
||||||
|
*/
|
||||||
|
default void beforeConnectionLeak(Connection connection) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This connection is invoked when a connection is held for longer than the leak timeout and reports what thread acquired it.
|
||||||
|
*/
|
||||||
|
default void onConnectionLeak(Connection connection, Thread thread) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This callback is invoked when a connection is about to be checked.
|
||||||
|
*/
|
||||||
|
default void beforeConnectionValidation(Connection connection) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This callback is invoked when a connection was checked and is valid.
|
||||||
|
*/
|
||||||
|
default void onConnectionValid(Connection connection) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This callback is invoked when a connection was checked and is invalid. The connection will be destroyed.
|
||||||
|
*/
|
||||||
|
default void onConnectionInvalid(Connection connection) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This callback is invoked when a connection is about to be flush. It may not be flushed.
|
||||||
|
*/
|
||||||
|
default void beforeConnectionFlush(Connection connection) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This callback is invoked when after a connection is removed from the pool.
|
||||||
|
*/
|
||||||
|
default void onConnectionFlush(Connection connection) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This callback is invoked before checking the idle timeout of a connection.
|
||||||
|
*/
|
||||||
|
default void beforeConnectionReap(Connection connection) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This callback is invoked if a connection is idle in the pool. The connection will be destroyed.
|
||||||
|
*/
|
||||||
|
default void onConnectionReap(Connection connection) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This callback is invoked whenever a connection is about to be destroyed.
|
||||||
|
*/
|
||||||
|
default void beforeConnectionDestroy(Connection connection) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This callback is invoked after a connection is closed.
|
||||||
|
*/
|
||||||
|
default void onConnectionDestroy(Connection connection) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This callback is invoked to report anomalous circumstances that do not prevent the pool from functioning.
|
||||||
|
*/
|
||||||
|
default void onWarning(String message) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This callback is invoked to report anomalous circumstances that do not prevent the pool from functioning.
|
||||||
|
*/
|
||||||
|
default void onWarning(Throwable throwable) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback to allow reporting information of interest, for which a warning might be considered excessive.
|
||||||
|
*/
|
||||||
|
default void onInfo(String message) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,136 @@
|
||||||
|
package org.xbib.jdbc.pool.api;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Several metrics provided by the pool.
|
||||||
|
*/
|
||||||
|
public interface XbibDataSourceMetrics {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of created connections.
|
||||||
|
*/
|
||||||
|
default long creationCount() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Average time for a connection to be created.
|
||||||
|
*/
|
||||||
|
default Duration creationTimeAverage() {
|
||||||
|
return Duration.ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum time for a connection to be created.
|
||||||
|
*/
|
||||||
|
default Duration creationTimeMax() {
|
||||||
|
return Duration.ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Total time waiting for a connections to be created.
|
||||||
|
*/
|
||||||
|
default Duration creationTimeTotal() {
|
||||||
|
return Duration.ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of times a leak was detected. A single connection can be detected multiple times.
|
||||||
|
*/
|
||||||
|
default long leakDetectionCount() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of connections removed from the pool for being invalid.
|
||||||
|
*/
|
||||||
|
default long invalidCount() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of connections removed from the pool, not counting invalid / idle.
|
||||||
|
*/
|
||||||
|
default long flushCount() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of connections removed from the pool for being idle.
|
||||||
|
*/
|
||||||
|
default long reapCount() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of destroyed connections.
|
||||||
|
*/
|
||||||
|
default long destroyCount() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of active connections. This connections are in use and not available to be acquired.
|
||||||
|
*/
|
||||||
|
default long activeCount() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum number of connections active simultaneously.
|
||||||
|
*/
|
||||||
|
default long maxUsedCount() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of idle connections in the pool, available to be acquired.
|
||||||
|
*/
|
||||||
|
default long availableCount() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of times an acquire operation succeeded.
|
||||||
|
*/
|
||||||
|
default long acquireCount() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Average time an application waited to acquire a connection.
|
||||||
|
*/
|
||||||
|
default Duration blockingTimeAverage() {
|
||||||
|
return Duration.ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum time an application waited to acquire a connection.
|
||||||
|
*/
|
||||||
|
default Duration blockingTimeMax() {
|
||||||
|
return Duration.ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Total time applications waited to acquire a connection.
|
||||||
|
*/
|
||||||
|
default Duration blockingTimeTotal() {
|
||||||
|
return Duration.ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Approximate number of threads blocked, waiting to acquire a connection.
|
||||||
|
*/
|
||||||
|
default long awaitingCount() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the metrics.
|
||||||
|
*/
|
||||||
|
default void reset() {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package org.xbib.jdbc.pool.api;
|
||||||
|
|
||||||
|
import org.xbib.jdbc.pool.api.configuration.XbibDataSourceConfiguration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface for providers of XbibDataSource.
|
||||||
|
*/
|
||||||
|
public interface XbibDataSourceProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory method for XbibDataSource. This method must return null if it can't create an XbibDataSource based on the supplied configuration.
|
||||||
|
*/
|
||||||
|
XbibDataSource getDataSource(XbibDataSourceConfiguration configuration, XbibDataSourceListener... listeners);
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
package org.xbib.jdbc.pool.api;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback interface for pool actions.
|
||||||
|
* These differ from the Listener in a few different ways:
|
||||||
|
* They do not have access to the raw Connection
|
||||||
|
* The invoke order is dependent on the operation (incoming / outgoing)
|
||||||
|
* Consistent with the transaction.
|
||||||
|
*/
|
||||||
|
public interface XbibPoolInterceptor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses interceptor priority, followed by {@link Class#getName()} to ensure a consistent ordering.
|
||||||
|
*/
|
||||||
|
Comparator<XbibPoolInterceptor> DEFAULT_COMPARATOR =
|
||||||
|
Comparator.comparingInt(XbibPoolInterceptor::getPriority).thenComparing(i -> i.getClass().getName());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This callback is invoked when a new connection is created.
|
||||||
|
*/
|
||||||
|
default void onConnectionCreate(Connection connection) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This callback is invoked when a connection is successfully acquired.
|
||||||
|
* When in a transactional environment this is invoked only once for multiple acquire calls within the same transaction, before the connection is associated.
|
||||||
|
*/
|
||||||
|
default void onConnectionAcquire(Connection connection) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This callback is invoked before a connection is returned to the pool.
|
||||||
|
* When in a transactional environment this is invoked only once for each transaction, after commit / rollback.
|
||||||
|
* This callback runs after reset, allowing connections to be in the pool in a different state than the described on the configuration.
|
||||||
|
*/
|
||||||
|
default void onConnectionReturn(Connection connection) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This callback is invoked just before a connection is destroyed.
|
||||||
|
*/
|
||||||
|
default void onConnectionDestroy(Connection connection) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows a ordering between multiple interceptors.
|
||||||
|
* Lower priority are executed first. Negative values are reserved.
|
||||||
|
*/
|
||||||
|
default int getPriority() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
21
jdbc-pool/src/main/java/org/xbib/jdbc/pool/api/cache/Acquirable.java
vendored
Normal file
21
jdbc-pool/src/main/java/org/xbib/jdbc/pool/api/cache/Acquirable.java
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package org.xbib.jdbc.pool.api.cache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contract for objects that can be acquired.
|
||||||
|
*/
|
||||||
|
public interface Acquirable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to acquire this object.
|
||||||
|
*
|
||||||
|
* @return true on successful object acquisition, false otherwise.
|
||||||
|
*/
|
||||||
|
boolean acquire();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method signals if the object can't be acquired in future calls to acquire().
|
||||||
|
*
|
||||||
|
* @return true if this object can eventually be acquired in the future.
|
||||||
|
*/
|
||||||
|
boolean isAcquirable();
|
||||||
|
}
|
48
jdbc-pool/src/main/java/org/xbib/jdbc/pool/api/cache/ConnectionCache.java
vendored
Normal file
48
jdbc-pool/src/main/java/org/xbib/jdbc/pool/api/cache/ConnectionCache.java
vendored
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
package org.xbib.jdbc.pool.api.cache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for a cache of connections. It's intended for mapping connections to executing threads.
|
||||||
|
*/
|
||||||
|
public interface ConnectionCache {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An implementation that does not cache.
|
||||||
|
*/
|
||||||
|
static ConnectionCache none() {
|
||||||
|
return new ConnectionCache() {
|
||||||
|
@Override
|
||||||
|
public Acquirable get() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void put(Acquirable acquirable) {
|
||||||
|
// nothing to do
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
// nothing to do
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a acquirable object from cache.
|
||||||
|
*
|
||||||
|
* @return a connection successfully acquired, according to {@link Acquirable#acquire()}
|
||||||
|
*/
|
||||||
|
Acquirable get();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache an acquirable object on this cache.
|
||||||
|
*/
|
||||||
|
void put(Acquirable acquirable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the cache.
|
||||||
|
*/
|
||||||
|
void reset();
|
||||||
|
}
|
135
jdbc-pool/src/main/java/org/xbib/jdbc/pool/api/cache/LocalConnectionCache.java
vendored
Normal file
135
jdbc-pool/src/main/java/org/xbib/jdbc/pool/api/cache/LocalConnectionCache.java
vendored
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
package org.xbib.jdbc.pool.api.cache;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.Deque;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementations of ConnectionCache that rely on {@link ThreadLocal}.
|
||||||
|
*/
|
||||||
|
public interface LocalConnectionCache {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A local cache that stores at most a single connection.
|
||||||
|
*/
|
||||||
|
static ConnectionCache single() {
|
||||||
|
return new ConnectionCache() {
|
||||||
|
|
||||||
|
@SuppressWarnings("ThreadLocalNotStaticFinal")
|
||||||
|
private volatile ThreadLocal<Acquirable> threadLocal;
|
||||||
|
|
||||||
|
{ // instance initializer
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Acquirable get() {
|
||||||
|
Acquirable acquirable = threadLocal.get();
|
||||||
|
return acquirable != null && acquirable.acquire() ? acquirable : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void put(Acquirable acquirable) {
|
||||||
|
if (acquirable.isAcquirable()) {
|
||||||
|
threadLocal.set(acquirable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
threadLocal = new ThreadLocal<>();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A local cache that stores up to a number of connections.
|
||||||
|
*/
|
||||||
|
static ConnectionCache fixed(int size) {
|
||||||
|
return new ConnectionCache() {
|
||||||
|
|
||||||
|
@SuppressWarnings("ThreadLocalNotStaticFinal")
|
||||||
|
private volatile ThreadLocal<Acquirable[]> threadLocal;
|
||||||
|
|
||||||
|
{ // instance initializer
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Acquirable get() {
|
||||||
|
Acquirable[] cacheArray = threadLocal.get();
|
||||||
|
for (int i = cacheArray.length; i > 0; ) { // iterate from last to first
|
||||||
|
Acquirable element = cacheArray[--i];
|
||||||
|
if (element != null) {
|
||||||
|
cacheArray[i] = null;
|
||||||
|
if (element.acquire()) {
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void put(Acquirable acquirable) {
|
||||||
|
if (acquirable.isAcquirable()) {
|
||||||
|
Acquirable[] cacheArray = threadLocal.get();
|
||||||
|
int last = cacheArray.length - 1;
|
||||||
|
if (cacheArray[last] != null) { // always store in last. if there is a previous entry try to move it to other slot
|
||||||
|
int i = last;
|
||||||
|
while (--i > 0 && cacheArray[i] != null) ; // find first free slot
|
||||||
|
cacheArray[i < 0 ? last - 1 : i] = cacheArray[last]; // if no free slot found, override last -1
|
||||||
|
}
|
||||||
|
cacheArray[last] = acquirable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
threadLocal = ThreadLocal.withInitial(() -> new Acquirable[size]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A local cache that stores all connections
|
||||||
|
*/
|
||||||
|
static ConnectionCache full() {
|
||||||
|
return new ConnectionCache() {
|
||||||
|
|
||||||
|
@SuppressWarnings("ThreadLocalNotStaticFinal")
|
||||||
|
private volatile ThreadLocal<Deque<Acquirable>> threadLocal;
|
||||||
|
|
||||||
|
{ // instance initializer
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Acquirable get() {
|
||||||
|
Deque<Acquirable> queue = threadLocal.get();
|
||||||
|
|
||||||
|
for (int i = queue.size(); i > 0; i--) {
|
||||||
|
Acquirable acquirable = queue.removeFirst();
|
||||||
|
if (acquirable.acquire()) {
|
||||||
|
return acquirable;
|
||||||
|
} else if (acquirable.isAcquirable()) {
|
||||||
|
queue.addLast(acquirable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void put(Acquirable acquirable) {
|
||||||
|
if (acquirable.acquire()) {
|
||||||
|
threadLocal.get().addFirst(acquirable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
// ArrayDeque with default initial capacity of 16
|
||||||
|
threadLocal = ThreadLocal.withInitial(ArrayDeque::new);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,149 @@
|
||||||
|
package org.xbib.jdbc.pool.api.configuration;
|
||||||
|
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Properties;
|
||||||
|
import org.xbib.jdbc.pool.api.security.XbibSecurityProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The configuration of the connection factory.
|
||||||
|
*/
|
||||||
|
public interface XbibConnectionFactoryConfiguration {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If connections should have the auto-commit mode on by default. The transaction integration may disable auto-commit when a connection in enrolled in a transaction.
|
||||||
|
*/
|
||||||
|
boolean autoCommit();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If JDBC resources ({@link java.sql.Statement} and {@link java.sql.ResultSet}) should be tracked to be closed if leaked.
|
||||||
|
*/
|
||||||
|
boolean trackJdbcResources();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum time to wait while attempting to connect to a database. Resolution in seconds.
|
||||||
|
*/
|
||||||
|
Duration loginTimeout();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The database URL to connect to.
|
||||||
|
*/
|
||||||
|
String jdbcUrl();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A SQL command to be executed when a connection is created.
|
||||||
|
*/
|
||||||
|
String initialSql();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JDBC driver class to use as a supplier of connections. Must be an implementation of {@link java.sql.Driver}, {@link javax.sql.DataSource} or {@link javax.sql.XADataSource}.
|
||||||
|
* Can be null, in which case the driver will be obtained from the URL (using the {@link java.sql.DriverManager#getDriver(String)} mechanism).
|
||||||
|
*/
|
||||||
|
Class<?> connectionProviderClass();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The isolation level between database transactions.
|
||||||
|
*/
|
||||||
|
IsolationLevel jdbcTransactionIsolation();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A collection of providers that are capable of handling principal / credential objects
|
||||||
|
*/
|
||||||
|
Collection<XbibSecurityProvider> securityProviders();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entity to be authenticated in the database.
|
||||||
|
*/
|
||||||
|
Principal principal();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collection of evidence used for authentication.
|
||||||
|
*/
|
||||||
|
Collection<Object> credentials();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entity to be authenticated in the database for recovery connections. If not set, the principal will be used.
|
||||||
|
*/
|
||||||
|
Principal recoveryPrincipal();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collection of evidence used for authentication for recovery connections. If not set, the credentials will be used.
|
||||||
|
*/
|
||||||
|
Collection<Object> recoveryCredentials();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Other unspecified properties to be passed into the JDBC driver when creating new connections.
|
||||||
|
* NOTE: username and password properties are not allowed, these have to be set using the principal / credentials mechanism.
|
||||||
|
*/
|
||||||
|
Properties jdbcProperties();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override of JDBC properties used for XA drivers. If left empty, the regular JDBC properties are used for both XA and non-XA.
|
||||||
|
*/
|
||||||
|
Properties xaProperties();
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default transaction isolation levels, defined in {@link Connection}.
|
||||||
|
*/
|
||||||
|
enum TransactionIsolation implements IsolationLevel {
|
||||||
|
UNDEFINED, NONE, READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE;
|
||||||
|
|
||||||
|
public static TransactionIsolation fromLevel(int level) {
|
||||||
|
switch (level) {
|
||||||
|
case Connection.TRANSACTION_NONE:
|
||||||
|
return NONE;
|
||||||
|
case Connection.TRANSACTION_READ_UNCOMMITTED:
|
||||||
|
return READ_UNCOMMITTED;
|
||||||
|
case Connection.TRANSACTION_READ_COMMITTED:
|
||||||
|
return READ_COMMITTED;
|
||||||
|
case Connection.TRANSACTION_REPEATABLE_READ:
|
||||||
|
return REPEATABLE_READ;
|
||||||
|
case Connection.TRANSACTION_SERIALIZABLE:
|
||||||
|
return SERIALIZABLE;
|
||||||
|
default:
|
||||||
|
return UNDEFINED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int level() {
|
||||||
|
switch (this) {
|
||||||
|
case READ_UNCOMMITTED:
|
||||||
|
return Connection.TRANSACTION_READ_UNCOMMITTED;
|
||||||
|
case READ_COMMITTED:
|
||||||
|
return Connection.TRANSACTION_READ_COMMITTED;
|
||||||
|
case REPEATABLE_READ:
|
||||||
|
return Connection.TRANSACTION_REPEATABLE_READ;
|
||||||
|
case SERIALIZABLE:
|
||||||
|
return Connection.TRANSACTION_SERIALIZABLE;
|
||||||
|
case NONE:
|
||||||
|
return Connection.TRANSACTION_NONE;
|
||||||
|
default:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDefined() {
|
||||||
|
return this != UNDEFINED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface to define the transaction isolation level.
|
||||||
|
*/
|
||||||
|
interface IsolationLevel {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If a level is not defined it will not be set by the pool (it will use the JDBC driver default).
|
||||||
|
*/
|
||||||
|
boolean isDefined();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The value for transaction isolation level.
|
||||||
|
*/
|
||||||
|
int level();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,281 @@
|
||||||
|
package org.xbib.jdbc.pool.api.configuration;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.time.Duration;
|
||||||
|
import org.xbib.jdbc.pool.api.cache.ConnectionCache;
|
||||||
|
import org.xbib.jdbc.pool.api.transaction.TransactionIntegration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The configuration of the connection pool.
|
||||||
|
*/
|
||||||
|
public interface XbibConnectionPoolConfiguration {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration for the connection factory.
|
||||||
|
*/
|
||||||
|
XbibConnectionFactoryConfiguration connectionFactoryConfiguration();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The connection validation method. Allows customization of the validation operation.
|
||||||
|
*/
|
||||||
|
ConnectionValidator connectionValidator();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows a custom exception sorter. This determines if a connection is still usable after an exception.
|
||||||
|
*/
|
||||||
|
ExceptionSorter exceptionSorter();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Customizable strategy for connection caching.
|
||||||
|
*/
|
||||||
|
ConnectionCache connectionCache();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The transaction layer integration to use.
|
||||||
|
*/
|
||||||
|
TransactionIntegration transactionIntegration();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requires connections to be enlisted into a transaction.
|
||||||
|
*/
|
||||||
|
TransactionRequirement transactionRequirement();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates connections before being acquired (foreground validation) overriding any idle timeout.
|
||||||
|
* Because of the overhead of performing validation on every call, it's recommended to rely on idle valdation instead.
|
||||||
|
*/
|
||||||
|
boolean validateOnBorrow();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connections idle for longer than this time period are validated before being acquired (foreground validation).
|
||||||
|
* A duration of {@link Duration#ZERO} means that this feature is disabled.
|
||||||
|
*/
|
||||||
|
Duration idleValidationTimeout();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connections acquired for longer than this time period may be reported as leaking.
|
||||||
|
* A duration of {@link Duration#ZERO} means that this feature is disabled.
|
||||||
|
*/
|
||||||
|
Duration leakTimeout();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connections idle for longer than this time period are validated (background validation).
|
||||||
|
* A duration of {@link Duration#ZERO} means that this feature is disabled.
|
||||||
|
*/
|
||||||
|
Duration validationTimeout();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connections idle for longer than this time period are flushed from the pool.
|
||||||
|
* A duration of {@link Duration#ZERO} means that this feature is disabled.
|
||||||
|
*/
|
||||||
|
Duration reapTimeout();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connections that are older than this time period are flushed from the pool.
|
||||||
|
* A duration of {@link Duration#ZERO} means that this feature is disabled.
|
||||||
|
*/
|
||||||
|
Duration maxLifetime();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides detailed insights of the connection status when it's reported as a leak (as INFO messages on XbibDataSourceListener).
|
||||||
|
*/
|
||||||
|
boolean enhancedLeakReport();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If connections should be flushed when returning to the pool.
|
||||||
|
*/
|
||||||
|
boolean flushOnClose();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Behaviour when a thread tries to acquire multiple connections.
|
||||||
|
*/
|
||||||
|
MultipleAcquisitionAction multipleAcquisition();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of connections to be created when the pool starts. Can be smaller than min or bigger than max.
|
||||||
|
*/
|
||||||
|
int initialSize();
|
||||||
|
|
||||||
|
// --- Mutable attributes //
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The minimum number of connections on the pool. If the pool has to flush connections it may create connections to keep this amount.
|
||||||
|
*/
|
||||||
|
int minSize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a new minimum number of connections on the pool. When this value increase the pool may temporarily have less connections than the minimum.
|
||||||
|
*/
|
||||||
|
void setMinSize(int size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of connections on the pool. When the number of acquired connections is equal to this value, further requests will block.
|
||||||
|
*/
|
||||||
|
int maxSize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a new maximum number of connections on the pool. When this value decreases the pool may temporarily have more connections than the maximum.
|
||||||
|
*/
|
||||||
|
void setMaxSize(int size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum amount of time a thread may be blocked waiting for a connection. If this time expires and still no connection is available, an exception is thrown.
|
||||||
|
* A duration of {@link Duration#ZERO} means that a thread will wait indefinitely.
|
||||||
|
* In Pool-less this timeout can add to {@link XbibConnectionFactoryConfiguration#loginTimeout()}.
|
||||||
|
*/
|
||||||
|
Duration acquisitionTimeout();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a new amount of time a thread may be blocked. Threads already blocked when this value changes do not see the new value when they unblock.
|
||||||
|
* A duration of {@link Duration#ZERO} means that a thread will wait indefinitely.
|
||||||
|
*/
|
||||||
|
void setAcquisitionTimeout(Duration timeout);
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modes available for transaction requirement.
|
||||||
|
*/
|
||||||
|
enum TransactionRequirement {
|
||||||
|
/**
|
||||||
|
* Enlistment not required.
|
||||||
|
*/
|
||||||
|
OFF,
|
||||||
|
/**
|
||||||
|
* Warn if not enlisted.
|
||||||
|
*/
|
||||||
|
WARN,
|
||||||
|
/**
|
||||||
|
* Throw exception if not enlisted.
|
||||||
|
*/
|
||||||
|
STRICT
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action to perform on acquisition of multiple connections by the same thread.
|
||||||
|
*/
|
||||||
|
enum MultipleAcquisitionAction {
|
||||||
|
/**
|
||||||
|
* No restriction.
|
||||||
|
*/
|
||||||
|
OFF,
|
||||||
|
/**
|
||||||
|
* Warn if thread already holds a connection.
|
||||||
|
*/
|
||||||
|
WARN,
|
||||||
|
/**
|
||||||
|
* Enforces single connection by throwing exception.
|
||||||
|
*/
|
||||||
|
STRICT
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for custom connection validation strategies.
|
||||||
|
*/
|
||||||
|
interface ConnectionValidator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default validation strategy {@link Connection#isValid(int)}
|
||||||
|
*/
|
||||||
|
static ConnectionValidator defaultValidator() {
|
||||||
|
return new ConnectionValidator() {
|
||||||
|
@Override
|
||||||
|
public boolean isValid(Connection connection) {
|
||||||
|
try {
|
||||||
|
return connection.isValid(0);
|
||||||
|
} catch (Exception t) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default validation strategy with a timeout (in seconds).
|
||||||
|
* If the timeout period expires before the operation completes, the connection is invalidated.
|
||||||
|
*/
|
||||||
|
static ConnectionValidator defaultValidatorWithTimeout(int timeout) {
|
||||||
|
return new ConnectionValidator() {
|
||||||
|
@Override
|
||||||
|
public boolean isValid(Connection connection) {
|
||||||
|
try {
|
||||||
|
return connection.isValid(timeout);
|
||||||
|
} catch (Exception t) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A validator that never invalidates connections.
|
||||||
|
*/
|
||||||
|
static ConnectionValidator emptyValidator() {
|
||||||
|
return new ConnectionValidator() {
|
||||||
|
@Override
|
||||||
|
public boolean isValid(Connection connection) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if a connection is valid, false otherwise
|
||||||
|
*/
|
||||||
|
boolean isValid(Connection connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for custom exception sorter strategies. Determines if a connection is still usable after an exception occurs.
|
||||||
|
*/
|
||||||
|
interface ExceptionSorter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default exception sorter. Does not treat any exception as fatal.
|
||||||
|
*/
|
||||||
|
static ExceptionSorter defaultExceptionSorter() {
|
||||||
|
return new ExceptionSorter() {
|
||||||
|
@Override
|
||||||
|
public boolean isFatal(SQLException se) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Never treat an exception as fatal.
|
||||||
|
*/
|
||||||
|
static ExceptionSorter emptyExceptionSorter() {
|
||||||
|
return new ExceptionSorter() {
|
||||||
|
@Override
|
||||||
|
public boolean isFatal(SQLException se) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Treats every exception as fatal.
|
||||||
|
*/
|
||||||
|
static ExceptionSorter fatalExceptionSorter() {
|
||||||
|
return new ExceptionSorter() {
|
||||||
|
@Override
|
||||||
|
public boolean isFatal(SQLException se) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if an exception is considered fatal and the connection is not able for further use, false otherwise
|
||||||
|
*/
|
||||||
|
boolean isFatal(SQLException se);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
package org.xbib.jdbc.pool.api.configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration of an XbibDataSource.
|
||||||
|
*/
|
||||||
|
public interface XbibDataSourceConfiguration {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration of the pool of this DataSource
|
||||||
|
*/
|
||||||
|
XbibConnectionPoolConfiguration connectionPoolConfiguration();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actual XbibDataSource implementation. This allows flexibility to have different implementations of the API.
|
||||||
|
*/
|
||||||
|
DataSourceImplementation dataSourceImplementation();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this XbibDataSource collects metrics.
|
||||||
|
*/
|
||||||
|
boolean metricsEnabled();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables or disables the collection of metrics.
|
||||||
|
*/
|
||||||
|
void setMetricsEnabled(boolean metricsEnabled);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows registration of a callback to be invoked each time there is a change on the status of metrics collection.
|
||||||
|
*/
|
||||||
|
void registerMetricsEnabledListener(MetricsEnabledListener metricsEnabledListener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Available implementations of XbibDataSource.
|
||||||
|
*/
|
||||||
|
enum DataSourceImplementation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Xbib - the database connection pool
|
||||||
|
*/
|
||||||
|
XBIB("org.xbib.jdbc.pool.DataSource"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specialization of the Xbib pool for the flush-on-close use case.
|
||||||
|
*/
|
||||||
|
XBIB_POOLLESS("org.xbib.jdbc.pool.DataSource");
|
||||||
|
|
||||||
|
private final String className;
|
||||||
|
|
||||||
|
DataSourceImplementation(String className) {
|
||||||
|
this.className = className;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String className() {
|
||||||
|
return className;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MetricsEnabledListener {
|
||||||
|
void onMetricsEnabled(boolean metricsEnabled);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,354 @@
|
||||||
|
package org.xbib.jdbc.pool.api.configuration.supplier;
|
||||||
|
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import org.xbib.jdbc.pool.api.configuration.XbibConnectionFactoryConfiguration;
|
||||||
|
import org.xbib.jdbc.pool.api.configuration.XbibConnectionFactoryConfiguration.IsolationLevel;
|
||||||
|
import org.xbib.jdbc.pool.api.configuration.XbibConnectionFactoryConfiguration.TransactionIsolation;
|
||||||
|
import org.xbib.jdbc.pool.api.security.XbibDefaultSecurityProvider;
|
||||||
|
import org.xbib.jdbc.pool.api.security.XbibKerberosSecurityProvider;
|
||||||
|
import org.xbib.jdbc.pool.api.security.XbibSecurityProvider;
|
||||||
|
import static org.xbib.jdbc.pool.api.configuration.XbibConnectionFactoryConfiguration.TransactionIsolation.UNDEFINED;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder of XbibConnectionFactoryConfiguration.
|
||||||
|
*/
|
||||||
|
public class XbibConnectionFactoryConfigurationSupplier implements Supplier<XbibConnectionFactoryConfiguration> {
|
||||||
|
|
||||||
|
private static final String USER_PROPERTY_NAME = "user";
|
||||||
|
private static final String PASSWORD_PROPERTY_NAME = "password";
|
||||||
|
|
||||||
|
boolean autoCommit = true;
|
||||||
|
boolean trackJdbcResources = true;
|
||||||
|
Duration loginTimeout = Duration.ZERO;
|
||||||
|
String jdbcUrl = "";
|
||||||
|
String initialSql = "";
|
||||||
|
Class<?> connectionProviderClass;
|
||||||
|
IsolationLevel transactionIsolation = UNDEFINED;
|
||||||
|
Collection<XbibSecurityProvider> securityProviders = new ArrayList<>();
|
||||||
|
Principal principal;
|
||||||
|
Collection<Object> credentials = new ArrayList<>();
|
||||||
|
Principal recoveryPrincipal;
|
||||||
|
Collection<Object> recoveryCredentials = new ArrayList<>();
|
||||||
|
Properties jdbcProperties = new Properties();
|
||||||
|
Properties xaProperties = new Properties();
|
||||||
|
|
||||||
|
private volatile boolean lock;
|
||||||
|
|
||||||
|
public XbibConnectionFactoryConfigurationSupplier() {
|
||||||
|
lock = false;
|
||||||
|
securityProviders.add(new XbibDefaultSecurityProvider());
|
||||||
|
securityProviders.add(new XbibKerberosSecurityProvider());
|
||||||
|
}
|
||||||
|
|
||||||
|
public XbibConnectionFactoryConfigurationSupplier(XbibConnectionFactoryConfiguration existingConfiguration) {
|
||||||
|
this();
|
||||||
|
if (existingConfiguration == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
autoCommit = existingConfiguration.autoCommit();
|
||||||
|
loginTimeout = existingConfiguration.loginTimeout();
|
||||||
|
jdbcUrl = existingConfiguration.jdbcUrl();
|
||||||
|
initialSql = existingConfiguration.initialSql();
|
||||||
|
connectionProviderClass = existingConfiguration.connectionProviderClass();
|
||||||
|
transactionIsolation = existingConfiguration.jdbcTransactionIsolation();
|
||||||
|
principal = existingConfiguration.principal();
|
||||||
|
credentials = existingConfiguration.credentials();
|
||||||
|
recoveryPrincipal = existingConfiguration.recoveryPrincipal();
|
||||||
|
recoveryCredentials = existingConfiguration.recoveryCredentials();
|
||||||
|
jdbcProperties = existingConfiguration.jdbcProperties();
|
||||||
|
xaProperties = existingConfiguration.xaProperties();
|
||||||
|
securityProviders = existingConfiguration.securityProviders();
|
||||||
|
trackJdbcResources = existingConfiguration.trackJdbcResources();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkLock() {
|
||||||
|
if (lock) {
|
||||||
|
throw new IllegalStateException("Attempt to modify an immutable configuration");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the value of auto-commit for connections on the pool. Default is true.
|
||||||
|
*/
|
||||||
|
public XbibConnectionFactoryConfigurationSupplier autoCommit(boolean autoCommitEnabled) {
|
||||||
|
checkLock();
|
||||||
|
autoCommit = autoCommitEnabled;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets if JDBC resources are tracked to be closed if leaked. Default is true.
|
||||||
|
*/
|
||||||
|
public XbibConnectionFactoryConfigurationSupplier trackJdbcResources(boolean trackJdbcResourcesEnabled) {
|
||||||
|
checkLock();
|
||||||
|
trackJdbcResources = trackJdbcResourcesEnabled;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the login timeout (in seconds). Default is 0 (waits indefinitely)
|
||||||
|
*/
|
||||||
|
public XbibConnectionFactoryConfigurationSupplier loginTimeout(Duration timeout) {
|
||||||
|
checkLock();
|
||||||
|
loginTimeout = timeout;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the database URL to connect to. Default is ""
|
||||||
|
*/
|
||||||
|
public XbibConnectionFactoryConfigurationSupplier jdbcUrl(String jdbcUrlString) {
|
||||||
|
checkLock();
|
||||||
|
jdbcUrl = jdbcUrlString;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the SQL command to be executed when a connection is created.
|
||||||
|
*/
|
||||||
|
public XbibConnectionFactoryConfigurationSupplier initialSql(String initialSqlString) {
|
||||||
|
checkLock();
|
||||||
|
initialSql = initialSqlString;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a class from the JDBC driver to be used as a supplier of connections.
|
||||||
|
* Default is null, meaning the driver will be obtained from the URL (using the {@link java.sql.DriverManager#getDriver(String)} mechanism).
|
||||||
|
*/
|
||||||
|
public XbibConnectionFactoryConfigurationSupplier connectionProviderClass(Class<?> connectionProvider) {
|
||||||
|
checkLock();
|
||||||
|
connectionProviderClass = connectionProvider;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to load a JDBC driver class using its fully qualified name. The classloader used is the one of this class.
|
||||||
|
* This method throws Exception if the class can't be loaded.
|
||||||
|
*/
|
||||||
|
public XbibConnectionFactoryConfigurationSupplier connectionProviderClassName(String connectionProviderName) {
|
||||||
|
try {
|
||||||
|
checkLock();
|
||||||
|
connectionProviderClass = connectionProviderName == null ? null : Class.forName(connectionProviderName);
|
||||||
|
return this;
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
throw new IllegalArgumentException("Unable to load class " + connectionProviderName, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the transaction isolation level. Default is UNDEFINED, meaning that the default isolation level for the JDBC driver is used.
|
||||||
|
*/
|
||||||
|
public XbibConnectionFactoryConfigurationSupplier jdbcTransactionIsolation(TransactionIsolation transactionIsolationLevel) {
|
||||||
|
checkLock();
|
||||||
|
transactionIsolation = transactionIsolationLevel;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows setting a custom transaction isolation level.
|
||||||
|
*/
|
||||||
|
public XbibConnectionFactoryConfigurationSupplier jdbcTransactionIsolation(int customValue) {
|
||||||
|
checkLock();
|
||||||
|
transactionIsolation = new CustomIsolationLevel(customValue);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows setting additional {@link XbibSecurityProvider} to deal with custom principal/credential types.
|
||||||
|
* Default is to have {@link XbibDefaultSecurityProvider} and {@link XbibKerberosSecurityProvider} available by default.
|
||||||
|
*/
|
||||||
|
public XbibConnectionFactoryConfigurationSupplier addSecurityProvider(XbibSecurityProvider provider) {
|
||||||
|
checkLock();
|
||||||
|
securityProviders.add(provider);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the principal to be authenticated in the database. Default is to don't perform authentication.
|
||||||
|
*/
|
||||||
|
public XbibConnectionFactoryConfigurationSupplier principal(Principal loginPrincipal) {
|
||||||
|
checkLock();
|
||||||
|
principal = loginPrincipal;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets credentials to use in order to authenticate to the database. Default is to don't provide any credentials.
|
||||||
|
*/
|
||||||
|
public XbibConnectionFactoryConfigurationSupplier credential(Object credential) {
|
||||||
|
checkLock();
|
||||||
|
credentials.add(credential);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows setting a different principal for recovery connections.
|
||||||
|
*/
|
||||||
|
public XbibConnectionFactoryConfigurationSupplier recoveryPrincipal(Principal loginPrincipal) {
|
||||||
|
checkLock();
|
||||||
|
recoveryPrincipal = loginPrincipal;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows providing a different set of credentials for recovery connections.
|
||||||
|
*/
|
||||||
|
public XbibConnectionFactoryConfigurationSupplier recoveryCredential(Object credential) {
|
||||||
|
checkLock();
|
||||||
|
recoveryCredentials.add(credential);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows setting other, unspecified, properties to be passed to the JDBC driver when creating new connections.
|
||||||
|
* NOTE: username and password properties are not allowed, these have to be set using the principal / credentials mechanism.
|
||||||
|
*/
|
||||||
|
public XbibConnectionFactoryConfigurationSupplier jdbcProperty(String key, String value) {
|
||||||
|
checkLock();
|
||||||
|
jdbcProperties.setProperty(key, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows setting other, unspecified, properties to be passed to the XA Datasource that override JDBC properties.
|
||||||
|
* NOTE: username and password properties are not allowed, these have to be set using the principal / credentials mechanism.
|
||||||
|
*/
|
||||||
|
public XbibConnectionFactoryConfigurationSupplier xaProperty(String key, String value) {
|
||||||
|
checkLock();
|
||||||
|
xaProperties.setProperty(key, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
@SuppressWarnings("stringConcatenation")
|
||||||
|
private void validate() {
|
||||||
|
if (lock) {
|
||||||
|
throw new IllegalStateException("Configuration already supplied");
|
||||||
|
}
|
||||||
|
if (loginTimeout.isNegative()) {
|
||||||
|
throw new IllegalArgumentException("Login timeout must not be negative");
|
||||||
|
}
|
||||||
|
if (jdbcProperties.containsKey(USER_PROPERTY_NAME)) {
|
||||||
|
throw new IllegalArgumentException("Invalid JDBC property '" + USER_PROPERTY_NAME + "': use principal instead.");
|
||||||
|
}
|
||||||
|
if (jdbcProperties.containsKey(PASSWORD_PROPERTY_NAME)) {
|
||||||
|
throw new IllegalArgumentException("Invalid property '" + PASSWORD_PROPERTY_NAME + "': use credential instead.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("ReturnOfInnerClass")
|
||||||
|
public XbibConnectionFactoryConfiguration get() {
|
||||||
|
validate();
|
||||||
|
lock = true;
|
||||||
|
|
||||||
|
return new XbibConnectionFactoryConfiguration() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean autoCommit() {
|
||||||
|
return autoCommit;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean trackJdbcResources() {
|
||||||
|
return trackJdbcResources;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Duration loginTimeout() {
|
||||||
|
return loginTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String jdbcUrl() {
|
||||||
|
return jdbcUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String initialSql() {
|
||||||
|
return initialSql;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> connectionProviderClass() {
|
||||||
|
return connectionProviderClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IsolationLevel jdbcTransactionIsolation() {
|
||||||
|
return transactionIsolation;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<XbibSecurityProvider> securityProviders() {
|
||||||
|
return securityProviders;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Principal principal() {
|
||||||
|
return principal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<Object> credentials() {
|
||||||
|
return credentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Principal recoveryPrincipal() {
|
||||||
|
return recoveryPrincipal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<Object> recoveryCredentials() {
|
||||||
|
return recoveryCredentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Properties jdbcProperties() {
|
||||||
|
return jdbcProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Properties xaProperties() {
|
||||||
|
return xaProperties;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
private static class CustomIsolationLevel implements IsolationLevel {
|
||||||
|
|
||||||
|
private final int customValue;
|
||||||
|
|
||||||
|
public CustomIsolationLevel(int customValue) {
|
||||||
|
this.customValue = customValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDefined() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int level() {
|
||||||
|
return customValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "CUSTOM";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,473 @@
|
||||||
|
package org.xbib.jdbc.pool.api.configuration.supplier;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import org.xbib.jdbc.pool.api.cache.ConnectionCache;
|
||||||
|
import org.xbib.jdbc.pool.api.cache.LocalConnectionCache;
|
||||||
|
import org.xbib.jdbc.pool.api.configuration.XbibConnectionFactoryConfiguration;
|
||||||
|
import org.xbib.jdbc.pool.api.configuration.XbibConnectionPoolConfiguration;
|
||||||
|
import org.xbib.jdbc.pool.api.configuration.XbibConnectionPoolConfiguration.MultipleAcquisitionAction;
|
||||||
|
import org.xbib.jdbc.pool.api.configuration.XbibConnectionPoolConfiguration.TransactionRequirement;
|
||||||
|
import org.xbib.jdbc.pool.api.transaction.TransactionIntegration;
|
||||||
|
import static java.lang.Integer.MAX_VALUE;
|
||||||
|
import static java.time.Duration.ZERO;
|
||||||
|
import static org.xbib.jdbc.pool.api.configuration.XbibConnectionPoolConfiguration.ConnectionValidator.emptyValidator;
|
||||||
|
import static org.xbib.jdbc.pool.api.configuration.XbibConnectionPoolConfiguration.ExceptionSorter.emptyExceptionSorter;
|
||||||
|
import static org.xbib.jdbc.pool.api.transaction.TransactionIntegration.none;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder of XbibConnectionPoolConfiguration.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({"PackageVisibleField", "WeakerAccess"})
|
||||||
|
public class XbibConnectionPoolConfigurationSupplier implements Supplier<XbibConnectionPoolConfiguration> {
|
||||||
|
|
||||||
|
XbibConnectionFactoryConfiguration connectionFactoryConfiguration;
|
||||||
|
|
||||||
|
ConnectionCache connectionCache = LocalConnectionCache.single();
|
||||||
|
TransactionIntegration transactionIntegration = none();
|
||||||
|
TransactionRequirement transactionRequirement = TransactionRequirement.OFF;
|
||||||
|
MultipleAcquisitionAction multipleAcquisitionAction = MultipleAcquisitionAction.OFF;
|
||||||
|
boolean enhancedLeakReport;
|
||||||
|
boolean flushOnClose;
|
||||||
|
int initialSize;
|
||||||
|
volatile int minSize;
|
||||||
|
volatile int maxSize = MAX_VALUE;
|
||||||
|
boolean validateOnBorrow;
|
||||||
|
XbibConnectionPoolConfiguration.ConnectionValidator connectionValidator = emptyValidator();
|
||||||
|
XbibConnectionPoolConfiguration.ExceptionSorter exceptionSorter = emptyExceptionSorter();
|
||||||
|
Duration idleValidationTimeout = ZERO;
|
||||||
|
Duration leakTimeout = ZERO;
|
||||||
|
Duration validationTimeout = ZERO;
|
||||||
|
Duration reapTimeout = ZERO;
|
||||||
|
Duration maxLifetime = ZERO;
|
||||||
|
volatile Duration acquisitionTimeout = ZERO;
|
||||||
|
|
||||||
|
private volatile boolean lock;
|
||||||
|
|
||||||
|
private XbibConnectionFactoryConfigurationSupplier connectionFactoryConfigurationSupplier = new XbibConnectionFactoryConfigurationSupplier();
|
||||||
|
|
||||||
|
public XbibConnectionPoolConfigurationSupplier() {
|
||||||
|
lock = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public XbibConnectionPoolConfigurationSupplier(XbibConnectionPoolConfiguration existingConfiguration) {
|
||||||
|
this();
|
||||||
|
if (existingConfiguration == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
connectionFactoryConfigurationSupplier = new XbibConnectionFactoryConfigurationSupplier(existingConfiguration.connectionFactoryConfiguration());
|
||||||
|
connectionCache = existingConfiguration.connectionCache();
|
||||||
|
transactionIntegration = existingConfiguration.transactionIntegration();
|
||||||
|
transactionRequirement = existingConfiguration.transactionRequirement();
|
||||||
|
multipleAcquisitionAction = existingConfiguration.multipleAcquisition();
|
||||||
|
flushOnClose = existingConfiguration.flushOnClose();
|
||||||
|
enhancedLeakReport = existingConfiguration.enhancedLeakReport();
|
||||||
|
initialSize = existingConfiguration.initialSize();
|
||||||
|
minSize = existingConfiguration.minSize();
|
||||||
|
maxSize = existingConfiguration.maxSize();
|
||||||
|
validateOnBorrow = existingConfiguration.validateOnBorrow();
|
||||||
|
connectionValidator = existingConfiguration.connectionValidator();
|
||||||
|
exceptionSorter = existingConfiguration.exceptionSorter();
|
||||||
|
idleValidationTimeout = existingConfiguration.idleValidationTimeout();
|
||||||
|
leakTimeout = existingConfiguration.leakTimeout();
|
||||||
|
validationTimeout = existingConfiguration.validationTimeout();
|
||||||
|
reapTimeout = existingConfiguration.reapTimeout();
|
||||||
|
maxLifetime = existingConfiguration.maxLifetime();
|
||||||
|
acquisitionTimeout = existingConfiguration.acquisitionTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkLock() {
|
||||||
|
if (lock) {
|
||||||
|
throw new IllegalStateException("Attempt to modify an immutable configuration");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the configuration for the connection factory.
|
||||||
|
*/
|
||||||
|
private XbibConnectionPoolConfigurationSupplier connectionFactoryConfiguration(XbibConnectionFactoryConfiguration configuration) {
|
||||||
|
checkLock();
|
||||||
|
connectionFactoryConfigurationSupplier = new XbibConnectionFactoryConfigurationSupplier(configuration);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the configuration for the connection factory.
|
||||||
|
*/
|
||||||
|
public XbibConnectionPoolConfigurationSupplier connectionFactoryConfiguration(Supplier<? extends XbibConnectionFactoryConfiguration> supplier) {
|
||||||
|
return connectionFactoryConfiguration(supplier.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modifies the configuration of the connection pool.
|
||||||
|
*/
|
||||||
|
public XbibConnectionPoolConfigurationSupplier connectionFactoryConfiguration(Function<? super XbibConnectionFactoryConfigurationSupplier, ? extends XbibConnectionFactoryConfigurationSupplier> function) {
|
||||||
|
return connectionFactoryConfiguration(function.apply(connectionFactoryConfigurationSupplier));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows access to the configuration builder for the connection pool.
|
||||||
|
*/
|
||||||
|
public XbibConnectionFactoryConfigurationSupplier connectionFactoryConfiguration() {
|
||||||
|
return connectionFactoryConfigurationSupplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the connection cache implementation. Default is {@link LocalConnectionCache#single}.
|
||||||
|
*/
|
||||||
|
public XbibConnectionPoolConfigurationSupplier connectionCache(ConnectionCache cache) {
|
||||||
|
checkLock();
|
||||||
|
connectionCache = cache;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the transaction integration instance to use. Default is {@link TransactionIntegration#none()}.
|
||||||
|
*/
|
||||||
|
public XbibConnectionPoolConfigurationSupplier transactionIntegration(TransactionIntegration integration) {
|
||||||
|
checkLock();
|
||||||
|
transactionIntegration = integration;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the transaction requirements for the pool. Default is {@link TransactionRequirement#OFF}.
|
||||||
|
*/
|
||||||
|
public XbibConnectionPoolConfigurationSupplier transactionRequirement(TransactionRequirement requirement) {
|
||||||
|
checkLock();
|
||||||
|
transactionRequirement = requirement;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables enhanced leak report.
|
||||||
|
*/
|
||||||
|
public XbibConnectionPoolConfigurationSupplier enhancedLeakReport() {
|
||||||
|
return enhancedLeakReport(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the behaviour of the pool when a thread tries to acquire multiple connections. Default is {@link MultipleAcquisitionAction#OFF}
|
||||||
|
*/
|
||||||
|
public XbibConnectionPoolConfigurationSupplier multipleAcquisition(MultipleAcquisitionAction action) {
|
||||||
|
checkLock();
|
||||||
|
multipleAcquisitionAction = action;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables or disables enhanced leak report. Default is false.
|
||||||
|
*/
|
||||||
|
public XbibConnectionPoolConfigurationSupplier enhancedLeakReport(boolean enhanced) {
|
||||||
|
checkLock();
|
||||||
|
enhancedLeakReport = enhanced;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables flushing of connections on close.
|
||||||
|
*/
|
||||||
|
public XbibConnectionPoolConfigurationSupplier flushOnClose() {
|
||||||
|
return flushOnClose(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables or disables flushing of connections on close. Default is false.
|
||||||
|
*/
|
||||||
|
public XbibConnectionPoolConfigurationSupplier flushOnClose(boolean flush) {
|
||||||
|
checkLock();
|
||||||
|
flushOnClose = flush;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the number of connections when the pool starts. Must not be negative. Default is zero.
|
||||||
|
*/
|
||||||
|
public XbibConnectionPoolConfigurationSupplier initialSize(int size) {
|
||||||
|
checkLock();
|
||||||
|
initialSize = size;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the minimum number of connections on the pool. Must not be negative and smaller than max. Default is zero.
|
||||||
|
*/
|
||||||
|
public XbibConnectionPoolConfigurationSupplier minSize(int size) {
|
||||||
|
checkLock();
|
||||||
|
minSize = size;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the maximum number of connections on the pool. Must not be negative. Required.
|
||||||
|
*/
|
||||||
|
public XbibConnectionPoolConfigurationSupplier maxSize(int size) {
|
||||||
|
checkLock();
|
||||||
|
maxSize = size;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables validation on borrow. Default is false.
|
||||||
|
*/
|
||||||
|
public XbibConnectionPoolConfigurationSupplier validateOnBorrow(boolean validate) {
|
||||||
|
checkLock();
|
||||||
|
validateOnBorrow = validate;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the connection validation method. Default {@link XbibConnectionPoolConfiguration.ConnectionValidator#emptyValidator()}
|
||||||
|
*/
|
||||||
|
public XbibConnectionPoolConfigurationSupplier connectionValidator(XbibConnectionPoolConfiguration.ConnectionValidator validator) {
|
||||||
|
checkLock();
|
||||||
|
connectionValidator = validator;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the exception sorter. Default {@link XbibConnectionPoolConfiguration.ExceptionSorter#emptyExceptionSorter()}
|
||||||
|
*/
|
||||||
|
public XbibConnectionPoolConfigurationSupplier exceptionSorter(XbibConnectionPoolConfiguration.ExceptionSorter sorter) {
|
||||||
|
checkLock();
|
||||||
|
exceptionSorter = sorter;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the duration of the acquisition timeout. Default is {@link Duration#ZERO} meaning that a thread will wait indefinitely.
|
||||||
|
*/
|
||||||
|
public XbibConnectionPoolConfigurationSupplier acquisitionTimeout(Duration timeout) {
|
||||||
|
checkLock();
|
||||||
|
acquisitionTimeout = timeout;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the duration of idle time for foreground validation to be executed. Default is {@link Duration#ZERO} meaning that this feature is disabled.
|
||||||
|
*/
|
||||||
|
public XbibConnectionPoolConfigurationSupplier idleValidationTimeout(Duration timeout) {
|
||||||
|
checkLock();
|
||||||
|
idleValidationTimeout = timeout;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the duration of the leak timeout detection. Default is {@link Duration#ZERO} meaning that this feature is disabled.
|
||||||
|
*/
|
||||||
|
public XbibConnectionPoolConfigurationSupplier leakTimeout(Duration timeout) {
|
||||||
|
checkLock();
|
||||||
|
leakTimeout = timeout;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the duration of background validation interval. Default is {@link Duration#ZERO} meaning that this feature is disabled.
|
||||||
|
*/
|
||||||
|
public XbibConnectionPoolConfigurationSupplier validationTimeout(Duration timeout) {
|
||||||
|
checkLock();
|
||||||
|
validationTimeout = timeout;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the duration for eviction of idle connections. Default is {@link Duration#ZERO} meaning that this feature is disabled.
|
||||||
|
*/
|
||||||
|
public XbibConnectionPoolConfigurationSupplier reapTimeout(Duration timeout) {
|
||||||
|
checkLock();
|
||||||
|
reapTimeout = timeout;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the duration for the lifetime of connections. Default is {@link Duration#ZERO} meaning that this feature is disabled.
|
||||||
|
*/
|
||||||
|
public XbibConnectionPoolConfigurationSupplier maxLifetime(Duration time) {
|
||||||
|
checkLock();
|
||||||
|
maxLifetime = time;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
private void validate() {
|
||||||
|
if (lock) {
|
||||||
|
throw new IllegalStateException("Configuration already supplied");
|
||||||
|
}
|
||||||
|
if (maxSize == MAX_VALUE) {
|
||||||
|
throw new IllegalArgumentException("max size attribute is mandatory");
|
||||||
|
}
|
||||||
|
if (maxSize <= 0) {
|
||||||
|
throw new IllegalArgumentException("A Positive max size is required");
|
||||||
|
}
|
||||||
|
if (minSize < 0) {
|
||||||
|
throw new IllegalArgumentException("Invalid min size: smaller than 0");
|
||||||
|
}
|
||||||
|
if (minSize > maxSize) {
|
||||||
|
throw new IllegalArgumentException("Invalid min size: greater than max size");
|
||||||
|
}
|
||||||
|
if (initialSize < 0) {
|
||||||
|
throw new IllegalArgumentException("Invalid value for initial size. Must not be negative, and ideally between min size and max size");
|
||||||
|
}
|
||||||
|
if (acquisitionTimeout.isNegative()) {
|
||||||
|
throw new IllegalArgumentException("Acquisition timeout must not be negative");
|
||||||
|
}
|
||||||
|
if (idleValidationTimeout.isNegative()) {
|
||||||
|
throw new IllegalArgumentException("Idle validation timeout must not be negative");
|
||||||
|
}
|
||||||
|
if (leakTimeout.isNegative()) {
|
||||||
|
throw new IllegalArgumentException("Leak detection timeout must not be negative");
|
||||||
|
}
|
||||||
|
if (reapTimeout.isNegative()) {
|
||||||
|
throw new IllegalArgumentException("Reap timeout must not be negative");
|
||||||
|
}
|
||||||
|
if (maxLifetime.isNegative()) {
|
||||||
|
throw new IllegalArgumentException("Max Lifetime must not be negative");
|
||||||
|
}
|
||||||
|
if (validationTimeout.isNegative()) {
|
||||||
|
throw new IllegalArgumentException("Validation timeout must not be negative");
|
||||||
|
}
|
||||||
|
if (connectionFactoryConfigurationSupplier == null) {
|
||||||
|
throw new IllegalArgumentException("Connection factory configuration not defined");
|
||||||
|
}
|
||||||
|
connectionFactoryConfiguration = connectionFactoryConfigurationSupplier.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("ReturnOfInnerClass")
|
||||||
|
public XbibConnectionPoolConfiguration get() {
|
||||||
|
validate();
|
||||||
|
lock = true;
|
||||||
|
|
||||||
|
return new XbibConnectionPoolConfiguration() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public XbibConnectionFactoryConfiguration connectionFactoryConfiguration() {
|
||||||
|
return connectionFactoryConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConnectionCache connectionCache() {
|
||||||
|
return connectionCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TransactionIntegration transactionIntegration() {
|
||||||
|
return transactionIntegration;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TransactionRequirement transactionRequirement() {
|
||||||
|
return transactionRequirement;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean enhancedLeakReport() {
|
||||||
|
return enhancedLeakReport;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean flushOnClose() {
|
||||||
|
return flushOnClose;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MultipleAcquisitionAction multipleAcquisition() {
|
||||||
|
return multipleAcquisitionAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int initialSize() {
|
||||||
|
return initialSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int minSize() {
|
||||||
|
return minSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setMinSize(int size) {
|
||||||
|
if (size < 0) {
|
||||||
|
throw new IllegalArgumentException("Invalid min size: smaller than 0");
|
||||||
|
}
|
||||||
|
if (size > maxSize) {
|
||||||
|
throw new IllegalArgumentException("Invalid min size: greater than max size");
|
||||||
|
}
|
||||||
|
minSize = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int maxSize() {
|
||||||
|
return maxSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setMaxSize(int size) {
|
||||||
|
if (size <= 0) {
|
||||||
|
throw new IllegalArgumentException("A Positive max size is required");
|
||||||
|
}
|
||||||
|
if (size < minSize) {
|
||||||
|
throw new IllegalArgumentException("Invalid max size: smaller than min size");
|
||||||
|
}
|
||||||
|
maxSize = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Duration acquisitionTimeout() {
|
||||||
|
return acquisitionTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAcquisitionTimeout(Duration timeout) {
|
||||||
|
if (timeout.isNegative()) {
|
||||||
|
throw new IllegalArgumentException("Acquisition timeout must not be negative");
|
||||||
|
}
|
||||||
|
acquisitionTimeout = timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean validateOnBorrow() {
|
||||||
|
return validateOnBorrow;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConnectionValidator connectionValidator() {
|
||||||
|
return connectionValidator;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExceptionSorter exceptionSorter() {
|
||||||
|
return exceptionSorter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Duration idleValidationTimeout() {
|
||||||
|
return idleValidationTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Duration leakTimeout() {
|
||||||
|
return leakTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Duration validationTimeout() {
|
||||||
|
return validationTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Duration reapTimeout() {
|
||||||
|
return reapTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Duration maxLifetime() {
|
||||||
|
return maxLifetime;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,139 @@
|
||||||
|
package org.xbib.jdbc.pool.api.configuration.supplier;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import org.xbib.jdbc.pool.api.configuration.XbibConnectionPoolConfiguration;
|
||||||
|
import org.xbib.jdbc.pool.api.configuration.XbibDataSourceConfiguration;
|
||||||
|
import org.xbib.jdbc.pool.api.configuration.XbibDataSourceConfiguration.DataSourceImplementation;
|
||||||
|
import org.xbib.jdbc.pool.api.configuration.XbibDataSourceConfiguration.MetricsEnabledListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder of XbibDataSourceConfiguration.
|
||||||
|
*/
|
||||||
|
public class XbibDataSourceConfigurationSupplier implements Supplier<XbibDataSourceConfiguration> {
|
||||||
|
|
||||||
|
XbibConnectionPoolConfiguration connectionPoolConfiguration;
|
||||||
|
|
||||||
|
DataSourceImplementation dataSourceImplementation = DataSourceImplementation.XBIB;
|
||||||
|
volatile boolean metrics;
|
||||||
|
volatile MetricsEnabledListener metricsEnabledListener;
|
||||||
|
|
||||||
|
private volatile boolean lock;
|
||||||
|
|
||||||
|
private XbibConnectionPoolConfigurationSupplier connectionPoolConfigurationSupplier = new XbibConnectionPoolConfigurationSupplier();
|
||||||
|
|
||||||
|
public XbibDataSourceConfigurationSupplier() {
|
||||||
|
lock = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkLock() {
|
||||||
|
if (lock) {
|
||||||
|
throw new IllegalStateException("Attempt to modify an immutable configuration");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the configuration of the connection pool.
|
||||||
|
*/
|
||||||
|
public XbibDataSourceConfigurationSupplier connectionPoolConfiguration(XbibConnectionPoolConfiguration configuration) {
|
||||||
|
checkLock();
|
||||||
|
connectionPoolConfigurationSupplier = new XbibConnectionPoolConfigurationSupplier(configuration);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the configuration of the connection pool.
|
||||||
|
*/
|
||||||
|
public XbibDataSourceConfigurationSupplier connectionPoolConfiguration(Supplier<? extends XbibConnectionPoolConfiguration> supplier) {
|
||||||
|
return connectionPoolConfiguration(supplier.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modifies the configuration of the connection pool.
|
||||||
|
*/
|
||||||
|
public XbibDataSourceConfigurationSupplier connectionPoolConfiguration(Function<? super XbibConnectionPoolConfigurationSupplier, ? extends XbibConnectionPoolConfigurationSupplier> function) {
|
||||||
|
return connectionPoolConfiguration(function.apply(connectionPoolConfigurationSupplier));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows access to the configuration builder for the connection pool.
|
||||||
|
*/
|
||||||
|
public XbibConnectionPoolConfigurationSupplier connectionPoolConfiguration() {
|
||||||
|
return connectionPoolConfigurationSupplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects the XbibDataSource implementation.
|
||||||
|
*/
|
||||||
|
public XbibDataSourceConfigurationSupplier dataSourceImplementation(DataSourceImplementation implementation) {
|
||||||
|
checkLock();
|
||||||
|
dataSourceImplementation = implementation;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables the collection of metrics.
|
||||||
|
*/
|
||||||
|
public XbibDataSourceConfigurationSupplier metricsEnabled() {
|
||||||
|
return metricsEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables or disables the collection of metrics. The default is false.
|
||||||
|
*/
|
||||||
|
public XbibDataSourceConfigurationSupplier metricsEnabled(boolean metricsEnabled) {
|
||||||
|
checkLock();
|
||||||
|
metrics = metricsEnabled;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
private void validate() {
|
||||||
|
if (lock) {
|
||||||
|
throw new IllegalStateException("Configuration already supplied");
|
||||||
|
}
|
||||||
|
if (connectionPoolConfigurationSupplier == null) {
|
||||||
|
throw new IllegalArgumentException("Connection pool configuration not defined");
|
||||||
|
}
|
||||||
|
connectionPoolConfiguration = connectionPoolConfigurationSupplier.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("ReturnOfInnerClass")
|
||||||
|
public XbibDataSourceConfiguration get() {
|
||||||
|
validate();
|
||||||
|
lock = true;
|
||||||
|
|
||||||
|
return new XbibDataSourceConfiguration() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public XbibConnectionPoolConfiguration connectionPoolConfiguration() {
|
||||||
|
return connectionPoolConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DataSourceImplementation dataSourceImplementation() {
|
||||||
|
return dataSourceImplementation;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean metricsEnabled() {
|
||||||
|
return metrics;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setMetricsEnabled(boolean metricsEnabled) {
|
||||||
|
metrics = metricsEnabled;
|
||||||
|
if (metricsEnabledListener != null) {
|
||||||
|
metricsEnabledListener.onMetricsEnabled(metricsEnabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerMetricsEnabledListener(MetricsEnabledListener listener) {
|
||||||
|
metricsEnabledListener = listener;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,336 @@
|
||||||
|
package org.xbib.jdbc.pool.api.configuration.supplier;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import org.xbib.jdbc.pool.api.configuration.XbibConnectionFactoryConfiguration.TransactionIsolation;
|
||||||
|
import org.xbib.jdbc.pool.api.configuration.XbibConnectionPoolConfiguration.ConnectionValidator;
|
||||||
|
import org.xbib.jdbc.pool.api.configuration.XbibConnectionPoolConfiguration.ExceptionSorter;
|
||||||
|
import org.xbib.jdbc.pool.api.configuration.XbibConnectionPoolConfiguration.MultipleAcquisitionAction;
|
||||||
|
import org.xbib.jdbc.pool.api.configuration.XbibConnectionPoolConfiguration.TransactionRequirement;
|
||||||
|
import org.xbib.jdbc.pool.api.configuration.XbibDataSourceConfiguration;
|
||||||
|
import org.xbib.jdbc.pool.api.configuration.XbibDataSourceConfiguration.DataSourceImplementation;
|
||||||
|
import org.xbib.jdbc.pool.api.exceptionsorter.DB2ExceptionSorter;
|
||||||
|
import org.xbib.jdbc.pool.api.exceptionsorter.MSSQLExceptionSorter;
|
||||||
|
import org.xbib.jdbc.pool.api.exceptionsorter.MySQLExceptionSorter;
|
||||||
|
import org.xbib.jdbc.pool.api.exceptionsorter.OracleExceptionSorter;
|
||||||
|
import org.xbib.jdbc.pool.api.exceptionsorter.PostgreSQLExceptionSorter;
|
||||||
|
import org.xbib.jdbc.pool.api.exceptionsorter.SybaseExceptionSorter;
|
||||||
|
import org.xbib.jdbc.pool.api.security.NamePrincipal;
|
||||||
|
import org.xbib.jdbc.pool.api.security.SimplePassword;
|
||||||
|
import static java.lang.Long.parseLong;
|
||||||
|
import static java.util.function.Function.identity;
|
||||||
|
import static org.xbib.jdbc.pool.api.configuration.XbibConnectionPoolConfiguration.ConnectionValidator.defaultValidator;
|
||||||
|
import static org.xbib.jdbc.pool.api.configuration.XbibConnectionPoolConfiguration.ConnectionValidator.defaultValidatorWithTimeout;
|
||||||
|
import static org.xbib.jdbc.pool.api.configuration.XbibConnectionPoolConfiguration.ConnectionValidator.emptyValidator;
|
||||||
|
import static org.xbib.jdbc.pool.api.configuration.XbibConnectionPoolConfiguration.ExceptionSorter.defaultExceptionSorter;
|
||||||
|
import static org.xbib.jdbc.pool.api.configuration.XbibConnectionPoolConfiguration.ExceptionSorter.emptyExceptionSorter;
|
||||||
|
import static org.xbib.jdbc.pool.api.configuration.XbibConnectionPoolConfiguration.ExceptionSorter.fatalExceptionSorter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenient way to build a configuration.
|
||||||
|
* This class can build a configuration from a *.properties file or a {@link Properties} object.
|
||||||
|
* This class defines keys for all the options and also allows for a prefix when looking for that properties.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
public class XbibPropertiesReader implements Supplier<XbibDataSourceConfiguration> {
|
||||||
|
|
||||||
|
public static final String IMPLEMENTATION = "implementation";
|
||||||
|
public static final String METRICS_ENABLED = "metricsEnabled";
|
||||||
|
|
||||||
|
public static final String MIN_SIZE = "minSize";
|
||||||
|
public static final String MAX_SIZE = "maxSize";
|
||||||
|
public static final String INITIAL_SIZE = "initialSize";
|
||||||
|
public static final String FLUSH_ON_CLOSE = "flushOnClose";
|
||||||
|
public static final String CONNECTION_VALIDATOR = "connectionValidator";
|
||||||
|
public static final String ENHANCED_LEAK_REPORT = "enhancedLeakReport";
|
||||||
|
public static final String EXCEPTION_SORTER = "exceptionSorter";
|
||||||
|
public static final String MULTIPLE_ACQUISITION = "multipleAcquisition";
|
||||||
|
public static final String TRANSACTION_REQUIREMENT = "transactionRequirement";
|
||||||
|
public static final String VALIDATE_ON_BORROW = "validateOnBorrow";
|
||||||
|
|
||||||
|
public static final String ACQUISITION_TIMEOUT = "acquisitionTimeout";
|
||||||
|
public static final String ACQUISITION_TIMEOUT_MS = "acquisitionTimeout_ms";
|
||||||
|
public static final String ACQUISITION_TIMEOUT_S = "acquisitionTimeout_s";
|
||||||
|
public static final String ACQUISITION_TIMEOUT_M = "acquisitionTimeout_m";
|
||||||
|
|
||||||
|
public static final String IDLE_VALIDATION = "idleValidation";
|
||||||
|
public static final String IDLE_VALIDATION_MS = "idleValidation_ms";
|
||||||
|
public static final String IDLE_VALIDATION_S = "idleValidation_s";
|
||||||
|
public static final String IDLE_VALIDATION_M = "idleValidation_m";
|
||||||
|
|
||||||
|
public static final String VALIDATION_TIMEOUT = "validationTimeout";
|
||||||
|
public static final String VALIDATION_TIMEOUT_MS = "validationTimeout_ms";
|
||||||
|
public static final String VALIDATION_TIMEOUT_S = "validationTimeout_s";
|
||||||
|
public static final String VALIDATION_TIMEOUT_M = "validationTimeout_m";
|
||||||
|
|
||||||
|
public static final String LEAK_TIMEOUT = "leakTimeout";
|
||||||
|
public static final String LEAK_TIMEOUT_MS = "leakTimeout_ms";
|
||||||
|
public static final String LEAK_TIMEOUT_S = "leakTimeout_s";
|
||||||
|
public static final String LEAK_TIMEOUT_M = "leakTimeout_m";
|
||||||
|
|
||||||
|
public static final String REAP_TIMEOUT = "reapTimeout";
|
||||||
|
public static final String REAP_TIMEOUT_MS = "reapTimeout_ms";
|
||||||
|
public static final String REAP_TIMEOUT_S = "reapTimeout_s";
|
||||||
|
public static final String REAP_TIMEOUT_M = "reapTimeout_m";
|
||||||
|
|
||||||
|
public static final String MAX_LIFETIME = "maxLifetime";
|
||||||
|
public static final String MAX_LIFETIME_MS = "maxLifetime_ms";
|
||||||
|
public static final String MAX_LIFETIME_S = "maxLifetime_s";
|
||||||
|
public static final String MAX_LIFETIME_M = "maxLifetime_m";
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
public static final String JDBC_URL = "jdbcUrl";
|
||||||
|
public static final String AUTO_COMMIT = "autoCommit";
|
||||||
|
public static final String TRACK_JDBC_RESOURCES = "trackJdbcResources";
|
||||||
|
public static final String LOGIN_TIMEOUT = "loginTimeout";
|
||||||
|
public static final String INITIAL_SQL = "initialSQL";
|
||||||
|
public static final String PROVIDER_CLASS_NAME = "providerClassName";
|
||||||
|
public static final String TRANSACTION_ISOLATION = "jdbcTransactionIsolation";
|
||||||
|
public static final String PRINCIPAL = "principal";
|
||||||
|
public static final String CREDENTIAL = "credential";
|
||||||
|
public static final String RECOVERY_PRINCIPAL = "recoveryPrincipal";
|
||||||
|
public static final String RECOVERY_CREDENTIAL = "recoveryCredential";
|
||||||
|
public static final String JDBC_PROPERTIES = "jdbcProperties";
|
||||||
|
public static final String XA_PROPERTIES = "xaProperties";
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
private final String prefix;
|
||||||
|
|
||||||
|
private final XbibDataSourceConfigurationSupplier dataSourceSupplier;
|
||||||
|
|
||||||
|
public XbibPropertiesReader() {
|
||||||
|
this("");
|
||||||
|
}
|
||||||
|
|
||||||
|
public XbibPropertiesReader(String readerPrefix) {
|
||||||
|
prefix = readerPrefix;
|
||||||
|
dataSourceSupplier = new XbibDataSourceConfigurationSupplier();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accepts the following options:
|
||||||
|
* <ul>
|
||||||
|
* <li>`empty` for the default {@link ConnectionValidator#emptyValidator()}</li>
|
||||||
|
* <li>`default` for {@link ConnectionValidator#defaultValidator()}</li>
|
||||||
|
* <li>`defaultX` for {@link ConnectionValidator#defaultValidatorWithTimeout(int)} where `X` is the timeout in seconds</li>
|
||||||
|
* <li>the name of a class that implements {@link ConnectionValidator}</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public static ConnectionValidator parseConnectionValidator(String connectionValidatorName) {
|
||||||
|
if ("empty".equalsIgnoreCase(connectionValidatorName)) {
|
||||||
|
return emptyValidator();
|
||||||
|
} else if ("default".equalsIgnoreCase(connectionValidatorName)) {
|
||||||
|
return defaultValidator();
|
||||||
|
} else if (connectionValidatorName.regionMatches(true, 0, "default", 0, "default".length())) {
|
||||||
|
return defaultValidatorWithTimeout((int) parseDurationS(connectionValidatorName.substring("default".length())).toSeconds());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Class<? extends ConnectionValidator> validatorClass = Thread.currentThread().getContextClassLoader().loadClass(connectionValidatorName).asSubclass(ConnectionValidator.class);
|
||||||
|
return validatorClass.getDeclaredConstructor().newInstance();
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
throw new IllegalArgumentException("Unknown connection validator " + connectionValidatorName);
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
throw new IllegalArgumentException(connectionValidatorName + " class is not a ConnectionValidator", e);
|
||||||
|
} catch (InvocationTargetException | InstantiationException | IllegalAccessException |
|
||||||
|
NoSuchMethodException e) {
|
||||||
|
throw new IllegalArgumentException("Unable to instantiate ConnectionValidator " + connectionValidatorName, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accepts the following options:
|
||||||
|
* <ul>
|
||||||
|
* <li>`empty` for the default {@link ExceptionSorter#emptyExceptionSorter()}</li>
|
||||||
|
* <li>`default` for {@link ExceptionSorter#defaultExceptionSorter()}</li>
|
||||||
|
* <li>`fatal` for {@link ExceptionSorter#fatalExceptionSorter()}</li>
|
||||||
|
* <li>`DB2` for the {@link DB2ExceptionSorter}</li>
|
||||||
|
* <li>`MSSQL` for the {@link MSSQLExceptionSorter}</li>
|
||||||
|
* <li>`MySQL` for the {@link MySQLExceptionSorter}</li>
|
||||||
|
* <li>`Oracle` for the {@link OracleExceptionSorter}</li>
|
||||||
|
* <li>`Postgres` or `PostgreSQL` for the {@link PostgreSQLExceptionSorter}</li>
|
||||||
|
* <li>`Sybase` for the {@link SybaseExceptionSorter}</li>
|
||||||
|
* <li>the name of a class that implements {@link ExceptionSorter}</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public static ExceptionSorter parseExceptionSorter(String exceptionSorterName) {
|
||||||
|
if ("empty".equalsIgnoreCase(exceptionSorterName)) {
|
||||||
|
return emptyExceptionSorter();
|
||||||
|
} else if ("default".equalsIgnoreCase(exceptionSorterName)) {
|
||||||
|
return defaultExceptionSorter();
|
||||||
|
} else if ("fatal".equalsIgnoreCase(exceptionSorterName)) {
|
||||||
|
return fatalExceptionSorter();
|
||||||
|
} else if ("DB2".equalsIgnoreCase(exceptionSorterName)) {
|
||||||
|
return new DB2ExceptionSorter();
|
||||||
|
} else if ("MSSQL".equalsIgnoreCase(exceptionSorterName)) {
|
||||||
|
return new MSSQLExceptionSorter();
|
||||||
|
} else if ("MySQL".equalsIgnoreCase(exceptionSorterName)) {
|
||||||
|
return new MySQLExceptionSorter();
|
||||||
|
} else if ("Oracle".equalsIgnoreCase(exceptionSorterName)) {
|
||||||
|
return new OracleExceptionSorter();
|
||||||
|
} else if ("Postgres".equalsIgnoreCase(exceptionSorterName) || "PostgreSQL".equalsIgnoreCase(exceptionSorterName)) {
|
||||||
|
return new PostgreSQLExceptionSorter();
|
||||||
|
} else if ("Sybase".equalsIgnoreCase(exceptionSorterName)) {
|
||||||
|
return new SybaseExceptionSorter();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Class<? extends ExceptionSorter> sorterClass = Thread.currentThread().getContextClassLoader().loadClass(exceptionSorterName).asSubclass(ExceptionSorter.class);
|
||||||
|
return sorterClass.getDeclaredConstructor().newInstance();
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
throw new IllegalArgumentException("Unknown exception sorter " + exceptionSorterName);
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
throw new IllegalArgumentException(exceptionSorterName + " class is not a ExceptionSorter", e);
|
||||||
|
} catch (InvocationTargetException | InstantiationException | IllegalAccessException |
|
||||||
|
NoSuchMethodException e) {
|
||||||
|
throw new IllegalArgumentException("Unable to instantiate ExceptionSorter " + exceptionSorterName, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
private static Duration parseDurationMs(String value) {
|
||||||
|
return Duration.ofMillis(parseLong(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Duration parseDurationS(String value) {
|
||||||
|
return Duration.ofSeconds(parseLong(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Duration parseDurationM(String value) {
|
||||||
|
return Duration.ofMinutes(parseLong(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public XbibDataSourceConfiguration get() {
|
||||||
|
return dataSourceSupplier.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public XbibDataSourceConfigurationSupplier modify() {
|
||||||
|
return dataSourceSupplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public XbibPropertiesReader readProperties(Path path) throws IOException {
|
||||||
|
return readProperties(path.toFile());
|
||||||
|
}
|
||||||
|
|
||||||
|
public XbibPropertiesReader readProperties(String filename) throws IOException {
|
||||||
|
return readProperties(new File(filename));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
public XbibPropertiesReader readProperties(File file) throws IOException {
|
||||||
|
try (InputStream inputStream = new FileInputStream(file)) {
|
||||||
|
Properties properties = new Properties();
|
||||||
|
properties.load(inputStream);
|
||||||
|
return readProperties(properties);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public XbibPropertiesReader readProperties(Properties properties) {
|
||||||
|
return readProperties((Map) properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
public XbibPropertiesReader readProperties(Map<String, String> properties) {
|
||||||
|
XbibConnectionPoolConfigurationSupplier connectionPoolSupplier = new XbibConnectionPoolConfigurationSupplier();
|
||||||
|
XbibConnectionFactoryConfigurationSupplier connectionFactorySupplier = new XbibConnectionFactoryConfigurationSupplier();
|
||||||
|
|
||||||
|
apply(dataSourceSupplier::dataSourceImplementation, DataSourceImplementation::valueOf, properties, IMPLEMENTATION);
|
||||||
|
apply(dataSourceSupplier::metricsEnabled, Boolean::parseBoolean, properties, METRICS_ENABLED);
|
||||||
|
|
||||||
|
apply(connectionPoolSupplier::minSize, Integer::parseInt, properties, MIN_SIZE);
|
||||||
|
apply(connectionPoolSupplier::maxSize, Integer::parseInt, properties, MAX_SIZE);
|
||||||
|
apply(connectionPoolSupplier::flushOnClose, Boolean::parseBoolean, properties, FLUSH_ON_CLOSE);
|
||||||
|
apply(connectionPoolSupplier::initialSize, Integer::parseInt, properties, INITIAL_SIZE);
|
||||||
|
apply(connectionPoolSupplier::connectionValidator, XbibPropertiesReader::parseConnectionValidator, properties, CONNECTION_VALIDATOR);
|
||||||
|
apply(connectionPoolSupplier::exceptionSorter, XbibPropertiesReader::parseExceptionSorter, properties, EXCEPTION_SORTER);
|
||||||
|
apply(connectionPoolSupplier::enhancedLeakReport, Boolean::parseBoolean, properties, ENHANCED_LEAK_REPORT);
|
||||||
|
apply(connectionPoolSupplier::multipleAcquisition, MultipleAcquisitionAction::valueOf, properties, MULTIPLE_ACQUISITION);
|
||||||
|
apply(connectionPoolSupplier::transactionRequirement, TransactionRequirement::valueOf, properties, TRANSACTION_REQUIREMENT);
|
||||||
|
apply(connectionPoolSupplier::validateOnBorrow, Boolean::parseBoolean, properties, VALIDATE_ON_BORROW);
|
||||||
|
|
||||||
|
apply(connectionPoolSupplier::acquisitionTimeout, Duration::parse, properties, ACQUISITION_TIMEOUT);
|
||||||
|
apply(connectionPoolSupplier::acquisitionTimeout, XbibPropertiesReader::parseDurationMs, properties, ACQUISITION_TIMEOUT_MS);
|
||||||
|
apply(connectionPoolSupplier::acquisitionTimeout, XbibPropertiesReader::parseDurationS, properties, ACQUISITION_TIMEOUT_S);
|
||||||
|
apply(connectionPoolSupplier::acquisitionTimeout, XbibPropertiesReader::parseDurationM, properties, ACQUISITION_TIMEOUT_M);
|
||||||
|
|
||||||
|
apply(connectionPoolSupplier::idleValidationTimeout, Duration::parse, properties, IDLE_VALIDATION);
|
||||||
|
apply(connectionPoolSupplier::idleValidationTimeout, XbibPropertiesReader::parseDurationMs, properties, IDLE_VALIDATION_MS);
|
||||||
|
apply(connectionPoolSupplier::idleValidationTimeout, XbibPropertiesReader::parseDurationS, properties, IDLE_VALIDATION_S);
|
||||||
|
apply(connectionPoolSupplier::idleValidationTimeout, XbibPropertiesReader::parseDurationM, properties, IDLE_VALIDATION_M);
|
||||||
|
|
||||||
|
apply(connectionPoolSupplier::validationTimeout, Duration::parse, properties, VALIDATION_TIMEOUT);
|
||||||
|
apply(connectionPoolSupplier::validationTimeout, XbibPropertiesReader::parseDurationMs, properties, VALIDATION_TIMEOUT_MS);
|
||||||
|
apply(connectionPoolSupplier::validationTimeout, XbibPropertiesReader::parseDurationS, properties, VALIDATION_TIMEOUT_S);
|
||||||
|
apply(connectionPoolSupplier::validationTimeout, XbibPropertiesReader::parseDurationM, properties, VALIDATION_TIMEOUT_M);
|
||||||
|
|
||||||
|
apply(connectionPoolSupplier::leakTimeout, Duration::parse, properties, LEAK_TIMEOUT);
|
||||||
|
apply(connectionPoolSupplier::leakTimeout, XbibPropertiesReader::parseDurationMs, properties, LEAK_TIMEOUT_MS);
|
||||||
|
apply(connectionPoolSupplier::leakTimeout, XbibPropertiesReader::parseDurationS, properties, LEAK_TIMEOUT_S);
|
||||||
|
apply(connectionPoolSupplier::leakTimeout, XbibPropertiesReader::parseDurationM, properties, LEAK_TIMEOUT_M);
|
||||||
|
|
||||||
|
apply(connectionPoolSupplier::reapTimeout, Duration::parse, properties, REAP_TIMEOUT);
|
||||||
|
apply(connectionPoolSupplier::reapTimeout, XbibPropertiesReader::parseDurationMs, properties, REAP_TIMEOUT_MS);
|
||||||
|
apply(connectionPoolSupplier::reapTimeout, XbibPropertiesReader::parseDurationS, properties, REAP_TIMEOUT_S);
|
||||||
|
apply(connectionPoolSupplier::reapTimeout, XbibPropertiesReader::parseDurationM, properties, REAP_TIMEOUT_M);
|
||||||
|
|
||||||
|
apply(connectionPoolSupplier::maxLifetime, Duration::parse, properties, MAX_LIFETIME);
|
||||||
|
apply(connectionPoolSupplier::maxLifetime, XbibPropertiesReader::parseDurationMs, properties, MAX_LIFETIME_MS);
|
||||||
|
apply(connectionPoolSupplier::maxLifetime, XbibPropertiesReader::parseDurationS, properties, MAX_LIFETIME_S);
|
||||||
|
apply(connectionPoolSupplier::maxLifetime, XbibPropertiesReader::parseDurationM, properties, MAX_LIFETIME_M);
|
||||||
|
|
||||||
|
apply(connectionFactorySupplier::jdbcUrl, identity(), properties, JDBC_URL);
|
||||||
|
apply(connectionFactorySupplier::autoCommit, Boolean::parseBoolean, properties, AUTO_COMMIT);
|
||||||
|
apply(connectionFactorySupplier::trackJdbcResources, Boolean::parseBoolean, properties, TRACK_JDBC_RESOURCES);
|
||||||
|
apply(connectionFactorySupplier::loginTimeout, Duration::parse, properties, LOGIN_TIMEOUT);
|
||||||
|
apply(connectionFactorySupplier::initialSql, identity(), properties, INITIAL_SQL);
|
||||||
|
apply(connectionFactorySupplier::connectionProviderClassName, identity(), properties, PROVIDER_CLASS_NAME);
|
||||||
|
apply(connectionFactorySupplier::jdbcTransactionIsolation, TransactionIsolation::valueOf, properties, TRANSACTION_ISOLATION);
|
||||||
|
apply(connectionFactorySupplier::principal, NamePrincipal::new, properties, PRINCIPAL);
|
||||||
|
apply(connectionFactorySupplier::credential, SimplePassword::new, properties, CREDENTIAL);
|
||||||
|
apply(connectionFactorySupplier::recoveryPrincipal, NamePrincipal::new, properties, RECOVERY_PRINCIPAL);
|
||||||
|
apply(connectionFactorySupplier::recoveryCredential, SimplePassword::new, properties, RECOVERY_CREDENTIAL);
|
||||||
|
applyJdbcProperties(connectionFactorySupplier::jdbcProperty, properties, JDBC_PROPERTIES);
|
||||||
|
applyJdbcProperties(connectionFactorySupplier::xaProperty, properties, XA_PROPERTIES);
|
||||||
|
|
||||||
|
dataSourceSupplier.connectionPoolConfiguration(connectionPoolSupplier.connectionFactoryConfiguration(connectionFactorySupplier));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("StringConcatenation")
|
||||||
|
private <T> void apply(Consumer<? super T> consumer, Function<? super String, T> converter, Map<String, String> properties, String key) {
|
||||||
|
String value = properties.get(prefix + key);
|
||||||
|
if (value != null) {
|
||||||
|
consumer.accept(converter.apply(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"SameParameterValue", "StringConcatenation"})
|
||||||
|
private void applyJdbcProperties(BiConsumer<? super String, ? super String> consumer, Map<String, String> properties, String key) {
|
||||||
|
String propertiesArray = properties.get(prefix + key);
|
||||||
|
if (propertiesArray != null && !propertiesArray.isEmpty()) {
|
||||||
|
for (String property : propertiesArray.split(";")) {
|
||||||
|
String[] keyValue = property.split("=");
|
||||||
|
consumer.accept(keyValue[0], keyValue[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package org.xbib.jdbc.pool.api.exceptionsorter;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import org.xbib.jdbc.pool.api.configuration.XbibConnectionPoolConfiguration.ExceptionSorter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception sorter for DB2 databases.
|
||||||
|
*/
|
||||||
|
public class DB2ExceptionSorter implements ExceptionSorter {
|
||||||
|
|
||||||
|
public DB2ExceptionSorter() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("MagicNumber")
|
||||||
|
public boolean isFatal(SQLException e) {
|
||||||
|
int code = Math.abs(e.getErrorCode());
|
||||||
|
return code == 4470 || code == 4499;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package org.xbib.jdbc.pool.api.exceptionsorter;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import org.xbib.jdbc.pool.api.configuration.XbibConnectionPoolConfiguration.ExceptionSorter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception sorter for MSSQL databases.
|
||||||
|
*/
|
||||||
|
public class MSSQLExceptionSorter implements ExceptionSorter {
|
||||||
|
|
||||||
|
public MSSQLExceptionSorter() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("CallToSuspiciousStringMethod")
|
||||||
|
public boolean isFatal(SQLException e) {
|
||||||
|
return "08S01".equals(e.getSQLState());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package org.xbib.jdbc.pool.api.exceptionsorter;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import org.xbib.jdbc.pool.api.configuration.XbibConnectionPoolConfiguration.ExceptionSorter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception sorter for MySQL / MariaDB databases.
|
||||||
|
*/
|
||||||
|
public class MySQLExceptionSorter implements ExceptionSorter {
|
||||||
|
|
||||||
|
public MySQLExceptionSorter() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("MagicNumber")
|
||||||
|
public boolean isFatal(SQLException e) {
|
||||||
|
if (e.getSQLState() != null && e.getSQLState().startsWith("08")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return switch (e.getErrorCode()) {
|
||||||
|
case 1040, 1042, 1043, 1047, 1081, 1129, 1130, 1045, 1004, 1005, 1015, 1021, 1041, 1037, 1038 -> true;
|
||||||
|
// All other errors
|
||||||
|
default -> false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
package org.xbib.jdbc.pool.api.exceptionsorter;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.Locale;
|
||||||
|
import org.xbib.jdbc.pool.api.configuration.XbibConnectionPoolConfiguration.ExceptionSorter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception sorter for Oracle databases.
|
||||||
|
*/
|
||||||
|
public class OracleExceptionSorter implements ExceptionSorter {
|
||||||
|
|
||||||
|
public OracleExceptionSorter() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings({"CallToSuspiciousStringMethod", "MagicNumber"})
|
||||||
|
public boolean isFatal(SQLException e) {
|
||||||
|
int errorCode = Math.abs(e.getErrorCode());
|
||||||
|
|
||||||
|
if (errorCode == 28 //session has been killed
|
||||||
|
|| errorCode == 600 //Internal oracle error
|
||||||
|
|| errorCode == 1012 //not logged on
|
||||||
|
|| errorCode == 1014 //Oracle shutdown in progress
|
||||||
|
|| errorCode == 1033 //Oracle initialization or shutdown in progress
|
||||||
|
|| errorCode == 1034 //Oracle not available
|
||||||
|
|| errorCode == 1035 //ORACLE only available to users with RESTRICTED SESSION privilege
|
||||||
|
|| errorCode == 1089 //immediate shutdown in progress - no operations are permitted
|
||||||
|
|| errorCode == 1090 //shutdown in progress - connection is not permitted
|
||||||
|
|| errorCode == 1092 //ORACLE instance terminated. Disconnection forced
|
||||||
|
|| errorCode == 1094 //ALTER DATABASE CLOSE in progress. Connections not permitted
|
||||||
|
|| errorCode == 2396 //exceeded maximum idle time, please connect again
|
||||||
|
|| errorCode == 3106 //fatal two-task communication protocol error
|
||||||
|
|| errorCode == 3111 //break received on communication channel
|
||||||
|
|| errorCode == 3113 //end-of-file on communication channel
|
||||||
|
|| errorCode == 3114 //not connected to ORACLE
|
||||||
|
|| errorCode >= 12100 && errorCode <= 12299 // TNS issues
|
||||||
|
|| errorCode == 17002 //connection reset
|
||||||
|
|| errorCode == 17008 //connection closed
|
||||||
|
|| errorCode == 17410 //No more data to read from socket
|
||||||
|
|| errorCode == 17447 //OALL8 is in an inconsistent state
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Exclude oracle user defined error codes (20000 through 20999) from consideration when looking for certain strings.
|
||||||
|
String errorText = (e.getMessage()).toUpperCase(Locale.US);
|
||||||
|
if ((errorCode < 20000 || errorCode >= 21000) && (errorText.contains("SOCKET") || errorText.contains("CONNECTION HAS ALREADY BEEN CLOSED") || errorText.contains("BROKEN PIPE"))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return "08000".equals(e.getSQLState());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package org.xbib.jdbc.pool.api.exceptionsorter;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import org.xbib.jdbc.pool.api.configuration.XbibConnectionPoolConfiguration.ExceptionSorter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception sorter for PostgreSQL databases.
|
||||||
|
*/
|
||||||
|
public class PostgreSQLExceptionSorter implements ExceptionSorter {
|
||||||
|
|
||||||
|
public PostgreSQLExceptionSorter() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFatal(SQLException e) {
|
||||||
|
String sqlState = e.getSQLState();
|
||||||
|
return sqlState != null && sqlState.startsWith("08"); // Class 08 — Connection Exception
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package org.xbib.jdbc.pool.api.exceptionsorter;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.Locale;
|
||||||
|
import org.xbib.jdbc.pool.api.configuration.XbibConnectionPoolConfiguration.ExceptionSorter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception sorter for Sybase databases.
|
||||||
|
*/
|
||||||
|
public class SybaseExceptionSorter implements ExceptionSorter {
|
||||||
|
|
||||||
|
public SybaseExceptionSorter() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFatal(SQLException e) {
|
||||||
|
String errorText = (e.getMessage()).toUpperCase(Locale.US);
|
||||||
|
|
||||||
|
// ERR_CONNECTION_DEAD ERR_IOE_KILLED_CONNECTION CONNECTION CLOSED
|
||||||
|
return errorText.contains("JZ0C0") || errorText.contains("JZ0C1") || errorText.contains("JZ006");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
package org.xbib.jdbc.pool.api.security;
|
||||||
|
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A string that identifies an user account.
|
||||||
|
*/
|
||||||
|
public class NamePrincipal implements Principal {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
public NamePrincipal(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NamePrincipal(char[] name) {
|
||||||
|
this.name = new String(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof Principal p)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return name == null ? p.getName() == null : name.contentEquals(p.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return name == null ? 7 : name.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Properties asProperties() {
|
||||||
|
Properties properties = new Properties();
|
||||||
|
properties.setProperty("user", name);
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
package org.xbib.jdbc.pool.api.security;
|
||||||
|
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Credential that holds a secret string of characters to be used for authentication.
|
||||||
|
*/
|
||||||
|
public class SimplePassword {
|
||||||
|
|
||||||
|
private final String word;
|
||||||
|
|
||||||
|
public SimplePassword(String password) {
|
||||||
|
word = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimplePassword(char[] password) {
|
||||||
|
word = new String(password);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getWord() {
|
||||||
|
return word;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Properties asProperties() {
|
||||||
|
Properties properties = new Properties();
|
||||||
|
properties.setProperty("password", word);
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof SimplePassword that)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return word == null ? that.word == null : word.contentEquals(that.word);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return word == null ? 7 : word.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "*** masked ***";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package org.xbib.jdbc.pool.api.security;
|
||||||
|
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles objects of type {@link NamePrincipal}, {@link SimplePassword} and {@link Principal}
|
||||||
|
*/
|
||||||
|
public class XbibDefaultSecurityProvider implements XbibSecurityProvider {
|
||||||
|
|
||||||
|
public XbibDefaultSecurityProvider() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("InstanceofConcreteClass")
|
||||||
|
public Properties getSecurityProperties(Object securityObject) {
|
||||||
|
if (securityObject instanceof NamePrincipal) {
|
||||||
|
return ((NamePrincipal) securityObject).asProperties();
|
||||||
|
}
|
||||||
|
if (securityObject instanceof SimplePassword) {
|
||||||
|
return ((SimplePassword) securityObject).asProperties();
|
||||||
|
}
|
||||||
|
if (securityObject instanceof Principal) {
|
||||||
|
Properties properties = new Properties();
|
||||||
|
properties.setProperty("user", ((Principal) securityObject).getName());
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package org.xbib.jdbc.pool.api.security;
|
||||||
|
|
||||||
|
import java.util.Properties;
|
||||||
|
import javax.security.auth.kerberos.KerberosTicket;
|
||||||
|
import org.ietf.jgss.GSSCredential;
|
||||||
|
import org.ietf.jgss.GSSException;
|
||||||
|
import org.ietf.jgss.Oid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle objects of type {@link GSSCredential} and {@link KerberosTicket}
|
||||||
|
*/
|
||||||
|
public class XbibKerberosSecurityProvider implements XbibSecurityProvider {
|
||||||
|
|
||||||
|
private static final Properties EMPTY_PROPERTIES = new Properties();
|
||||||
|
|
||||||
|
private static final String KERBEROS_v5 = "1.2.840.113554.1.2.2";
|
||||||
|
|
||||||
|
public XbibKerberosSecurityProvider() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Properties getSecurityProperties(Object securityObject) {
|
||||||
|
if (securityObject instanceof GSSCredential) {
|
||||||
|
try {
|
||||||
|
Properties properties = new Properties();
|
||||||
|
properties.setProperty("user", ((GSSCredential) securityObject).getName(new Oid(KERBEROS_v5)).toString());
|
||||||
|
return properties;
|
||||||
|
} catch (GSSException e) {
|
||||||
|
// nothing we can do
|
||||||
|
return EMPTY_PROPERTIES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (securityObject instanceof KerberosTicket) {
|
||||||
|
return EMPTY_PROPERTIES;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package org.xbib.jdbc.pool.api.security;
|
||||||
|
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface to be implemented in order to extend with custom types of authentication.
|
||||||
|
*/
|
||||||
|
public interface XbibSecurityProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a custom principal / credential objects to properties to be passed to the JDBC driver.
|
||||||
|
*
|
||||||
|
* @return null if not capable of handle the security object, otherwise return a {@link Properties} object even if empty.
|
||||||
|
*/
|
||||||
|
Properties getSecurityProperties(Object securityObject);
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
package org.xbib.jdbc.pool.api.transaction;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface to be implemented by a resource (a connection) that the transaction integration layer will manipulate.
|
||||||
|
*/
|
||||||
|
public interface TransactionAware {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The resource it's now enlisted with a transaction.
|
||||||
|
*/
|
||||||
|
void transactionStart() throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The transaction is about to complete.
|
||||||
|
*
|
||||||
|
* @param successful If the transaction is to complete successfully (commit) or unsuccessfully (rollback)
|
||||||
|
*/
|
||||||
|
void transactionBeforeCompletion(boolean successful);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The resource must commit.
|
||||||
|
*/
|
||||||
|
void transactionCommit() throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The resource must rollback.
|
||||||
|
*/
|
||||||
|
void transactionRollback() throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The transaction ended and the resource is no longer enlisted.
|
||||||
|
*/
|
||||||
|
void transactionEnd() throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a callback trap to prevent lazy / deferred enlistment. We support neither of those features.
|
||||||
|
* This callback is set when the resource is obtained outside the scope of a running transaction and allows the resource to check if it's used within a transaction later on.
|
||||||
|
*/
|
||||||
|
void transactionCheckCallback(SQLCallable<Boolean> transactionCheck);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets access to the raw {@link java.sql.Connection} held by the resource.
|
||||||
|
*/
|
||||||
|
Object getConnection();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The resource is no longer valid and should not be returned to the pool.
|
||||||
|
*/
|
||||||
|
void setFlushOnly();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A callable that can throw {@link SQLException}
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
interface SQLCallable<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute an action that may result in an {@link SQLException}
|
||||||
|
*/
|
||||||
|
T call() throws SQLException;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
package org.xbib.jdbc.pool.api.transaction;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import javax.sql.XAConnection;
|
||||||
|
import javax.transaction.xa.XAResource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We provide an integration point for transaction systems to modify the behaviour of the pool.
|
||||||
|
* The transaction layer can control the lifecycle of connections and define what connections are acquired and when these return to the pool.
|
||||||
|
* It is responsible for commit or rollback of the database transaction.
|
||||||
|
*/
|
||||||
|
public interface TransactionIntegration {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default implementation of the transaction layer, that performs no actions.
|
||||||
|
*/
|
||||||
|
static TransactionIntegration none() {
|
||||||
|
return new TransactionIntegration() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TransactionAware getTransactionAware() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void associate(TransactionAware connection, XAResource xaResource) {
|
||||||
|
// nothing to do
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean disassociate(TransactionAware connection) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addResourceRecoveryFactory(ResourceRecoveryFactory factory) {
|
||||||
|
// nothing to do
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeResourceRecoveryFactory(ResourceRecoveryFactory factory) {
|
||||||
|
// nothing to do
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We inquire the transaction layer for a Tx aware resource (a connection) that can be acquired.
|
||||||
|
* Usually, if there is a resource already associated with the calling thread it is returned.
|
||||||
|
*/
|
||||||
|
TransactionAware getTransactionAware() throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We notify the transaction layer a Tx aware resource (a connection) and it's corresponding XA resource were acquired.
|
||||||
|
* Usually, the resource is associated with the calling thread.
|
||||||
|
*/
|
||||||
|
void associate(TransactionAware transactionAware, XAResource xaResource) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We notify the transaction layer that a Tx aware resource (a connection) is about to be returned to the pool.
|
||||||
|
* Usually, the resource is disassociated from the calling thread and returned to the pool.
|
||||||
|
*
|
||||||
|
* @return true if the Tx aware should return to the pool, false if it should not.
|
||||||
|
*/
|
||||||
|
boolean disassociate(TransactionAware transactionAware) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We call this method on init to register itself as a XA module capable of recovery.
|
||||||
|
*/
|
||||||
|
void addResourceRecoveryFactory(ResourceRecoveryFactory factory);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We call this method on shutdown to de-register itself as a XA module capable of recovery.
|
||||||
|
*/
|
||||||
|
void removeResourceRecoveryFactory(ResourceRecoveryFactory factory);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interface is implemented by the connection factory so that it can provide recovery resources to the transaction layer.
|
||||||
|
*/
|
||||||
|
interface ResourceRecoveryFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The transaction layer can call this method to check if recovery is possible.
|
||||||
|
*/
|
||||||
|
boolean isRecoverable();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The transaction layer can call this method to obtain one connection used for recovery of incomplete transactions.
|
||||||
|
*/
|
||||||
|
XAConnection getRecoveryConnection() throws SQLException;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
package org.xbib.jdbc.pool.util;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
||||||
|
import static java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract class to track auto-closeable resources by having them forming a collection between themselves.
|
||||||
|
* This class is designed to prevent leaks of JDBC wrapper objects.
|
||||||
|
* Once a new wrapper is created it tries to insert itself between the head and the first element of the list (if it exists).
|
||||||
|
* There is the invariant that at any given point in time the list can be traversed from the head and all inserted elements are reachable.
|
||||||
|
* As an implementation detail, the collection formed is actually a stack (FILO behaviour) and is thread-safe.
|
||||||
|
* <p>
|
||||||
|
* The resources do not remove themselves on close. It's assumed that the implementations of this interface are wrappers and the contents are dropped at that moment.
|
||||||
|
* Also, allowing removal of elements would introduce an undesirable amount of complexity to this class.
|
||||||
|
*/
|
||||||
|
public abstract class AutoCloseableElement implements AutoCloseable {
|
||||||
|
|
||||||
|
private static final AtomicReferenceFieldUpdater<AutoCloseableElement, AutoCloseableElement> NEXT_UPDATER = newUpdater(AutoCloseableElement.class, AutoCloseableElement.class, "nextElement");
|
||||||
|
|
||||||
|
private volatile AutoCloseableElement nextElement;
|
||||||
|
|
||||||
|
@SuppressWarnings("ThisEscapedInObjectConstruction")
|
||||||
|
protected AutoCloseableElement(AutoCloseableElement head) {
|
||||||
|
if (head != null) {
|
||||||
|
// point to the first element of the list and attempt to have the head to point at us
|
||||||
|
do {
|
||||||
|
nextElement = head.getNextElement();
|
||||||
|
} while (!head.setNextElement(nextElement, this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a special marker element to be used as head of a collection.
|
||||||
|
*/
|
||||||
|
public static AutoCloseableElement newHead() {
|
||||||
|
return new AutoCloseableElementHead();
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void close() throws SQLException;
|
||||||
|
|
||||||
|
public abstract boolean isClosed() throws Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of resources that were not properly closed. The resources are closed in the process and the collection is cleared.
|
||||||
|
* This method should be invoked on the collection head only, otherwise it may not traverse the whole collection.
|
||||||
|
*/
|
||||||
|
public int closeAllAutocloseableElements() {
|
||||||
|
int count = 0;
|
||||||
|
// if under contention, the call that succeeds to reset the head is the one and only that will traverse the whole collection
|
||||||
|
for (AutoCloseableElement next = resetNextElement(); next != null; next = next.resetNextElement()) {
|
||||||
|
try {
|
||||||
|
if (!next.isClosed()) {
|
||||||
|
count++;
|
||||||
|
if (next instanceof Statement) {
|
||||||
|
try {
|
||||||
|
// AG-231 - we have to cancel the Statement on cleanup to avoid overloading the DB
|
||||||
|
((Statement) next).cancel();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
// ignore and proceed with close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next.close();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean setNextElement(AutoCloseableElement expected, AutoCloseableElement element) {
|
||||||
|
return NEXT_UPDATER.compareAndSet(this, expected, element);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AutoCloseableElement getNextElement() {
|
||||||
|
return NEXT_UPDATER.get(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AutoCloseableElement resetNextElement() {
|
||||||
|
return NEXT_UPDATER.getAndSet(this, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class AutoCloseableElementHead extends AutoCloseableElement {
|
||||||
|
|
||||||
|
private AutoCloseableElementHead() {
|
||||||
|
super(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isClosed() {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws SQLException {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
package org.xbib.jdbc.pool.util;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.List;
|
||||||
|
import org.xbib.jdbc.pool.ConnectionHandler;
|
||||||
|
import org.xbib.jdbc.pool.api.XbibPoolInterceptor;
|
||||||
|
|
||||||
|
@SuppressWarnings({"UtilityClass", "ObjectAllocationInLoop"})
|
||||||
|
public final class InterceptorHelper {
|
||||||
|
|
||||||
|
private InterceptorHelper() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void fireOnConnectionCreateInterceptor(List<? extends XbibPoolInterceptor> interceptors, ConnectionHandler handler) throws SQLException {
|
||||||
|
if (interceptors != null && !interceptors.isEmpty()) {
|
||||||
|
for (XbibPoolInterceptor interceptor : interceptors) {
|
||||||
|
try (Connection connection = handler.detachedWrapper()) {
|
||||||
|
interceptor.onConnectionCreate(connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void fireOnConnectionAcquiredInterceptor(List<? extends XbibPoolInterceptor> interceptors, ConnectionHandler handler) throws SQLException {
|
||||||
|
if (interceptors != null && !interceptors.isEmpty()) {
|
||||||
|
for (XbibPoolInterceptor interceptor : interceptors) {
|
||||||
|
try (Connection connection = handler.detachedWrapper()) {
|
||||||
|
interceptor.onConnectionAcquire(connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void fireOnConnectionReturnInterceptor(List<? extends XbibPoolInterceptor> interceptors, ConnectionHandler handler) throws SQLException {
|
||||||
|
if (interceptors != null && !interceptors.isEmpty()) {
|
||||||
|
for (int i = interceptors.size(); i > 0; ) {
|
||||||
|
try (Connection connection = handler.detachedWrapper()) {
|
||||||
|
interceptors.get(--i).onConnectionReturn(connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void fireOnConnectionDestroyInterceptor(List<? extends XbibPoolInterceptor> interceptors, ConnectionHandler handler) throws SQLException {
|
||||||
|
if (interceptors != null && !interceptors.isEmpty()) {
|
||||||
|
for (int i = interceptors.size(); i > 0; ) {
|
||||||
|
try (Connection connection = handler.detachedWrapper()) {
|
||||||
|
interceptors.get(--i).onConnectionDestroy(connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,158 @@
|
||||||
|
package org.xbib.jdbc.pool.util;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import org.xbib.jdbc.pool.ConnectionHandler;
|
||||||
|
import org.xbib.jdbc.pool.api.XbibDataSourceListener;
|
||||||
|
|
||||||
|
@SuppressWarnings("UtilityClass")
|
||||||
|
public final class ListenerHelper {
|
||||||
|
|
||||||
|
private ListenerHelper() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void fireBeforeConnectionCreation(XbibDataSourceListener[] listeners) {
|
||||||
|
for (XbibDataSourceListener listener : listeners) {
|
||||||
|
listener.beforeConnectionCreation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void fireOnConnectionCreation(XbibDataSourceListener[] listeners, ConnectionHandler handler) {
|
||||||
|
for (XbibDataSourceListener listener : listeners) {
|
||||||
|
listener.onConnectionCreation(handler.rawConnection());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void fireOnConnectionPooled(XbibDataSourceListener[] listeners, ConnectionHandler handler) {
|
||||||
|
for (XbibDataSourceListener listener : listeners) {
|
||||||
|
listener.onConnectionPooled(handler.rawConnection());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void fireBeforeConnectionAcquire(XbibDataSourceListener[] listeners) {
|
||||||
|
for (XbibDataSourceListener listener : listeners) {
|
||||||
|
listener.beforeConnectionAcquire();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void fireOnConnectionAcquired(XbibDataSourceListener[] listeners, ConnectionHandler handler) {
|
||||||
|
for (XbibDataSourceListener listener : listeners) {
|
||||||
|
listener.onConnectionAcquire(handler.rawConnection());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void fireBeforeConnectionReturn(XbibDataSourceListener[] listeners, ConnectionHandler handler) {
|
||||||
|
for (XbibDataSourceListener listener : listeners) {
|
||||||
|
listener.beforeConnectionReturn(handler.rawConnection());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void fireOnConnectionReturn(XbibDataSourceListener[] listeners, ConnectionHandler handler) {
|
||||||
|
for (XbibDataSourceListener listener : listeners) {
|
||||||
|
listener.onConnectionReturn(handler.rawConnection());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void fireBeforeConnectionLeak(XbibDataSourceListener[] listeners, ConnectionHandler handler) {
|
||||||
|
for (XbibDataSourceListener listener : listeners) {
|
||||||
|
listener.beforeConnectionLeak(handler.rawConnection());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"StringConcatenation", "ObjectAllocationInLoop"})
|
||||||
|
public static void fireOnConnectionLeak(XbibDataSourceListener[] listeners, ConnectionHandler handler) {
|
||||||
|
for (XbibDataSourceListener listener : listeners) {
|
||||||
|
Connection connection = handler.rawConnection();
|
||||||
|
listener.onConnectionLeak(connection, handler.getHoldingThread());
|
||||||
|
if (handler.getAcquisitionStackTrace() != null) {
|
||||||
|
if (handler.isEnlisted()) {
|
||||||
|
listener.onInfo("Leaked connection " + connection + " is enlisted. Please make sure the associated transaction completes.");
|
||||||
|
} else {
|
||||||
|
listener.onInfo("Leaked connection " + connection + " is not enlisted. To return it to the pool use the flush(LEAK) operation.");
|
||||||
|
}
|
||||||
|
listener.onInfo("Leaked connection " + connection + " acquired at: " + Arrays.toString(handler.getAcquisitionStackTrace()));
|
||||||
|
}
|
||||||
|
if (handler.getConnectionOperations() != null) {
|
||||||
|
listener.onInfo("Operations executed on leaked connection " + connection + ": " + String.join(", ", handler.getConnectionOperations()));
|
||||||
|
}
|
||||||
|
if (handler.getLastOperationStackTrace() != null) {
|
||||||
|
listener.onInfo("Stack trace of last executed operation on " + connection + ": " + Arrays.toString(handler.getLastOperationStackTrace()));
|
||||||
|
}
|
||||||
|
if (handler.getConnectionOperations() != null && handler.getConnectionOperations().contains("unwrap(Class<T>)")) {
|
||||||
|
listener.onWarning("A possible cause for the leak of connection " + connection + " is a call to the unwrap() method. close() needs to be called on the connection object provided by the pool.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void fireBeforeConnectionValidation(XbibDataSourceListener[] listeners, ConnectionHandler handler) {
|
||||||
|
for (XbibDataSourceListener listener : listeners) {
|
||||||
|
listener.beforeConnectionValidation(handler.rawConnection());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void fireOnConnectionValid(XbibDataSourceListener[] listeners, ConnectionHandler handler) {
|
||||||
|
for (XbibDataSourceListener listener : listeners) {
|
||||||
|
listener.onConnectionValid(handler.rawConnection());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void fireOnConnectionInvalid(XbibDataSourceListener[] listeners, ConnectionHandler handler) {
|
||||||
|
for (XbibDataSourceListener listener : listeners) {
|
||||||
|
listener.onConnectionInvalid(handler.rawConnection());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void fireBeforeConnectionFlush(XbibDataSourceListener[] listeners, ConnectionHandler handler) {
|
||||||
|
for (XbibDataSourceListener listener : listeners) {
|
||||||
|
listener.beforeConnectionFlush(handler.rawConnection());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void fireOnConnectionFlush(XbibDataSourceListener[] listeners, ConnectionHandler handler) {
|
||||||
|
for (XbibDataSourceListener listener : listeners) {
|
||||||
|
listener.onConnectionFlush(handler.rawConnection());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void fireBeforeConnectionReap(XbibDataSourceListener[] listeners, ConnectionHandler handler) {
|
||||||
|
for (XbibDataSourceListener listener : listeners) {
|
||||||
|
listener.beforeConnectionReap(handler.rawConnection());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void fireOnConnectionReap(XbibDataSourceListener[] listeners, ConnectionHandler handler) {
|
||||||
|
for (XbibDataSourceListener listener : listeners) {
|
||||||
|
listener.onConnectionReap(handler.rawConnection());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void fireBeforeConnectionDestroy(XbibDataSourceListener[] listeners, ConnectionHandler handler) {
|
||||||
|
for (XbibDataSourceListener listener : listeners) {
|
||||||
|
listener.beforeConnectionDestroy(handler.rawConnection());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void fireOnConnectionDestroy(XbibDataSourceListener[] listeners, ConnectionHandler handler) {
|
||||||
|
for (XbibDataSourceListener listener : listeners) {
|
||||||
|
listener.onConnectionDestroy(handler.rawConnection());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void fireOnWarning(XbibDataSourceListener[] listeners, String message) {
|
||||||
|
for (XbibDataSourceListener listener : listeners) {
|
||||||
|
listener.onWarning(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void fireOnWarning(XbibDataSourceListener[] listeners, Throwable throwable) {
|
||||||
|
for (XbibDataSourceListener listener : listeners) {
|
||||||
|
listener.onWarning(throwable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void fireOnInfo(XbibDataSourceListener[] listeners, String message) {
|
||||||
|
for (XbibDataSourceListener listener : listeners) {
|
||||||
|
listener.onInfo(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,119 @@
|
||||||
|
package org.xbib.jdbc.pool.util;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.FutureTask;
|
||||||
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
|
import java.util.concurrent.RunnableFuture;
|
||||||
|
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||||
|
import java.util.concurrent.ThreadFactory;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
import org.xbib.jdbc.pool.api.XbibDataSourceListener;
|
||||||
|
import static org.xbib.jdbc.pool.util.ListenerHelper.fireOnWarning;
|
||||||
|
|
||||||
|
public final class PriorityScheduledExecutor extends ScheduledThreadPoolExecutor {
|
||||||
|
|
||||||
|
private static final Runnable EMPTY_TASK = () -> {
|
||||||
|
};
|
||||||
|
|
||||||
|
private final Queue<RunnableFuture<?>> priorityTasks = new ConcurrentLinkedQueue<>();
|
||||||
|
private final XbibDataSourceListener[] listeners;
|
||||||
|
|
||||||
|
public PriorityScheduledExecutor(int executorSize, String threadPrefix, XbibDataSourceListener... listeners) {
|
||||||
|
super(executorSize, new PriorityExecutorThreadFactory(threadPrefix), new CallerRunsPolicy());
|
||||||
|
setRemoveOnCancelPolicy(true);
|
||||||
|
this.listeners = listeners;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
public void executeNow(Runnable priorityTask) {
|
||||||
|
executeNow(new FutureTask<>(priorityTask, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
public <T> Future<T> executeNow(Callable<T> priorityTask) {
|
||||||
|
return executeNow(new FutureTask<>(priorityTask));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
public <T> Future<T> executeNow(RunnableFuture<T> priorityFuture) {
|
||||||
|
if (isShutdown()) {
|
||||||
|
throw new RejectedExecutionException("Task " + priorityFuture + " rejected from " + this);
|
||||||
|
}
|
||||||
|
priorityTasks.add(priorityFuture);
|
||||||
|
if (!priorityFuture.isDone()) {
|
||||||
|
// Submit a task so that the beforeExecute() method gets called
|
||||||
|
execute(EMPTY_TASK);
|
||||||
|
}
|
||||||
|
return priorityFuture;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void beforeExecute(Thread thread, Runnable lowPriorityTask) {
|
||||||
|
// Run all high priority tasks in queue first, then low priority
|
||||||
|
for (RunnableFuture<?> priorityTask; (priorityTask = priorityTasks.poll()) != null; ) {
|
||||||
|
if (isShutdown()) {
|
||||||
|
priorityTask.cancel(false);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
priorityTask.run();
|
||||||
|
afterExecute(priorityTask, null);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
afterExecute(priorityTask, t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.beforeExecute(thread, lowPriorityTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void afterExecute(Runnable r, Throwable t) {
|
||||||
|
if (t != null) {
|
||||||
|
fireOnWarning(listeners, t);
|
||||||
|
}
|
||||||
|
super.afterExecute(r, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdown() {
|
||||||
|
if (!isShutdown()) {
|
||||||
|
executeNow(super::shutdown);
|
||||||
|
execute(this::shutdownNow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Runnable> shutdownNow() {
|
||||||
|
for (RunnableFuture<?> runnableFuture : priorityTasks) {
|
||||||
|
runnableFuture.cancel(true);
|
||||||
|
}
|
||||||
|
List<Runnable> allTasks = new ArrayList<>(priorityTasks);
|
||||||
|
priorityTasks.clear();
|
||||||
|
|
||||||
|
allTasks.addAll(super.shutdownNow());
|
||||||
|
return allTasks;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PriorityExecutorThreadFactory implements ThreadFactory {
|
||||||
|
|
||||||
|
private final AtomicLong threadCount;
|
||||||
|
private final String threadPrefix;
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
PriorityExecutorThreadFactory(String threadPrefix) {
|
||||||
|
this.threadPrefix = threadPrefix;
|
||||||
|
threadCount = new AtomicLong();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Thread newThread(Runnable r) {
|
||||||
|
Thread thread = new Thread(r, threadPrefix + threadCount.incrementAndGet());
|
||||||
|
thread.setDaemon(true);
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,110 @@
|
||||||
|
package org.xbib.jdbc.pool.util;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public final class PropertyInjector {
|
||||||
|
|
||||||
|
private final Class<?> cls;
|
||||||
|
|
||||||
|
private final Map<String, Method> propertySetter = new HashMap<>();
|
||||||
|
|
||||||
|
@SuppressWarnings("CallToSuspiciousStringMethod")
|
||||||
|
public PropertyInjector(Class<?> cls) {
|
||||||
|
this.cls = cls;
|
||||||
|
|
||||||
|
for (Method method : cls.getMethods()) {
|
||||||
|
String name = method.getName();
|
||||||
|
if (method.getParameterCount() == 1 && name.startsWith("set")) {
|
||||||
|
propertySetter.merge(name.substring(3), method, PropertyInjector::methodSelector);
|
||||||
|
} else if (method.getParameterCount() == 2 && "setProperty".equals(name)) {
|
||||||
|
propertySetter.put("Property", method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to resolve ambiguities when multiple methods apply the same property.
|
||||||
|
*/
|
||||||
|
private static Method methodSelector(Method methodOne, Method methodTwo) {
|
||||||
|
// AG-200 - Prefer methods that are not deprecated
|
||||||
|
return methodOne.isAnnotationPresent(Deprecated.class) ? methodTwo : methodOne;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("CallToSuspiciousStringMethod")
|
||||||
|
private static Properties typeConvertProperties(String properties) {
|
||||||
|
if (properties == null) {
|
||||||
|
return new Properties();
|
||||||
|
}
|
||||||
|
properties = properties.trim();
|
||||||
|
if (properties.isEmpty()) {
|
||||||
|
return new Properties();
|
||||||
|
}
|
||||||
|
Properties result = new Properties();
|
||||||
|
for (String property : properties.split(";")) {
|
||||||
|
String[] keyValue = property.split("=");
|
||||||
|
if (keyValue.length != 2) {
|
||||||
|
throw new IllegalArgumentException("Can't convert properties '" + properties + "' to " + Properties.class.getName());
|
||||||
|
}
|
||||||
|
result.put(keyValue[0].trim(), keyValue[1].trim());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("StringConcatenation")
|
||||||
|
public void inject(Object target, String propertyName, String propertyValue) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
|
||||||
|
String realName = propertyName.substring(0, 1).toUpperCase(Locale.ROOT) + propertyName.substring(1);
|
||||||
|
if (propertySetter.containsKey(realName)) {
|
||||||
|
Method method = propertySetter.get(realName);
|
||||||
|
method.invoke(target, typeConvert(propertyValue, method.getParameterTypes()[0]));
|
||||||
|
} else if (propertySetter.containsKey("Property")) {
|
||||||
|
cls.getMethod("setProperty", propertyName.getClass(), propertyValue.getClass()).invoke(target, propertyName, propertyValue);
|
||||||
|
} else {
|
||||||
|
throw new NoSuchMethodException("No setter in class " + cls.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> availableProperties() {
|
||||||
|
return propertySetter.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object typeConvert(String value, Class<?> target) {
|
||||||
|
if (target == String.class) {
|
||||||
|
return value;
|
||||||
|
} else if (target == byte[].class) {
|
||||||
|
return value.getBytes(StandardCharsets.UTF_8);
|
||||||
|
} else if (target == char[].class) {
|
||||||
|
return value.toCharArray();
|
||||||
|
} else if (Boolean.class.isAssignableFrom(target) || Boolean.TYPE.isAssignableFrom(target)) {
|
||||||
|
return Boolean.parseBoolean(value);
|
||||||
|
} else if (Character.class.isAssignableFrom(target) || Character.TYPE.isAssignableFrom(target)) {
|
||||||
|
return value.charAt(0);
|
||||||
|
} else if (Short.class.isAssignableFrom(target) || Short.TYPE.isAssignableFrom(target)) {
|
||||||
|
return Short.parseShort(value);
|
||||||
|
} else if (Integer.class.isAssignableFrom(target) || Integer.TYPE.isAssignableFrom(target)) {
|
||||||
|
return Integer.parseInt(value);
|
||||||
|
} else if (Long.class.isAssignableFrom(target) || Long.TYPE.isAssignableFrom(target)) {
|
||||||
|
return Long.parseLong(value);
|
||||||
|
} else if (Float.class.isAssignableFrom(target) || Float.TYPE.isAssignableFrom(target)) {
|
||||||
|
return Float.parseFloat(value);
|
||||||
|
} else if (Double.class.isAssignableFrom(target) || Double.TYPE.isAssignableFrom(target)) {
|
||||||
|
return Double.parseDouble(value);
|
||||||
|
} else if (Class.class.isAssignableFrom(target)) {
|
||||||
|
try {
|
||||||
|
return cls.getClassLoader().loadClass(value);
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
throw new IllegalArgumentException("ClassNotFoundException: " + e.getMessage());
|
||||||
|
}
|
||||||
|
} else if (Properties.class.isAssignableFrom(target)) {
|
||||||
|
return typeConvertProperties(value);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Can't convert to " + target.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,330 @@
|
||||||
|
package org.xbib.jdbc.pool.util;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ListIterator;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
import java.util.Spliterator;
|
||||||
|
import java.util.concurrent.locks.StampedLock;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.function.UnaryOperator;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import static java.lang.System.arraycopy;
|
||||||
|
import static java.lang.reflect.Array.newInstance;
|
||||||
|
|
||||||
|
public final class StampedCopyOnWriteArrayList<T> implements List<T> {
|
||||||
|
|
||||||
|
private final Iterator<T> emptyIterator = new Iterator<T>() {
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T next() {
|
||||||
|
throw new NoSuchElementException();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private final StampedLock lock;
|
||||||
|
private long optimisticStamp;
|
||||||
|
private T[] data;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public StampedCopyOnWriteArrayList(Class<? extends T> clazz) {
|
||||||
|
data = (T[]) newInstance(clazz, 0);
|
||||||
|
lock = new StampedLock();
|
||||||
|
optimisticStamp = lock.tryOptimisticRead();
|
||||||
|
}
|
||||||
|
|
||||||
|
private T[] getUnderlyingArray() {
|
||||||
|
T[] array = data;
|
||||||
|
if (lock.validate(optimisticStamp)) {
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Acquiring a read lock does not increment the optimistic stamp
|
||||||
|
long stamp = lock.readLock();
|
||||||
|
try {
|
||||||
|
return data;
|
||||||
|
} finally {
|
||||||
|
lock.unlockRead(stamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T get(int index) {
|
||||||
|
return getUnderlyingArray()[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return getUnderlyingArray().length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return size() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T set(int index, T element) {
|
||||||
|
long stamp = lock.writeLock();
|
||||||
|
try {
|
||||||
|
T old = data[index];
|
||||||
|
data[index] = element;
|
||||||
|
return old;
|
||||||
|
} finally {
|
||||||
|
optimisticStamp = lock.tryConvertToOptimisticRead(stamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean add(T element) {
|
||||||
|
long stamp = lock.writeLock();
|
||||||
|
try {
|
||||||
|
data = Arrays.copyOf(data, data.length + 1);
|
||||||
|
data[data.length - 1] = element;
|
||||||
|
return true;
|
||||||
|
} finally {
|
||||||
|
optimisticStamp = lock.tryConvertToOptimisticRead(stamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public T removeLast() {
|
||||||
|
long stamp = lock.writeLock();
|
||||||
|
try {
|
||||||
|
T element = data[data.length - 1];
|
||||||
|
data = Arrays.copyOf(data, data.length - 1);
|
||||||
|
return element;
|
||||||
|
} finally {
|
||||||
|
optimisticStamp = lock.tryConvertToOptimisticRead(stamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean remove(Object element) {
|
||||||
|
int index = indexOf(element);
|
||||||
|
if (index == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
long stamp = lock.writeLock();
|
||||||
|
try {
|
||||||
|
if (index >= data.length || element != data[index]) {
|
||||||
|
// contents changed, need to recheck the position of the element in the array
|
||||||
|
int length = data.length;
|
||||||
|
for (index = 0; index < length; index++) {
|
||||||
|
if (element == data[index]) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (index == data.length) { // not found!
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
T[] newData = Arrays.copyOf(data, data.length - 1);
|
||||||
|
if (data.length - index - 1 != 0) {
|
||||||
|
arraycopy(data, index + 1, newData, index, data.length - index - 1);
|
||||||
|
}
|
||||||
|
data = newData;
|
||||||
|
return true;
|
||||||
|
} finally {
|
||||||
|
optimisticStamp = lock.tryConvertToOptimisticRead(stamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T remove(int index) {
|
||||||
|
long stamp = lock.writeLock();
|
||||||
|
try {
|
||||||
|
T old = data[index];
|
||||||
|
T[] array = Arrays.copyOf(data, data.length - 1);
|
||||||
|
if (data.length - index - 1 != 0) {
|
||||||
|
arraycopy(data, index + 1, array, index, data.length - index - 1);
|
||||||
|
}
|
||||||
|
data = array;
|
||||||
|
return old;
|
||||||
|
} finally {
|
||||||
|
optimisticStamp = lock.tryConvertToOptimisticRead(stamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
long stamp = lock.writeLock();
|
||||||
|
try {
|
||||||
|
data = Arrays.copyOf(data, 0);
|
||||||
|
} finally {
|
||||||
|
optimisticStamp = lock.tryConvertToOptimisticRead(stamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<T> iterator() {
|
||||||
|
T[] array = getUnderlyingArray();
|
||||||
|
return array.length == 0 ? emptyIterator : new UncheckedIterator<>(array);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean addAll(Collection<? extends T> c) {
|
||||||
|
long stamp = lock.writeLock();
|
||||||
|
try {
|
||||||
|
int oldSize = data.length;
|
||||||
|
data = Arrays.copyOf(data, oldSize + c.size());
|
||||||
|
for (T element : c) {
|
||||||
|
data[oldSize++] = element;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} finally {
|
||||||
|
optimisticStamp = lock.tryConvertToOptimisticRead(stamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contains(Object o) {
|
||||||
|
return indexOf(o) != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int indexOf(Object o) {
|
||||||
|
T[] array = getUnderlyingArray();
|
||||||
|
int length = array.length;
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
if (o == array[i]) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void forEach(Consumer<? super T> action) {
|
||||||
|
for (T element : this) {
|
||||||
|
action.accept(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object[] toArray() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <E> E[] toArray(E[] a) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsAll(Collection<?> c) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean addAll(int index, Collection<? extends T> c) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean removeAll(Collection<?> c) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean retainAll(Collection<?> c) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void add(int index, T element) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int lastIndexOf(Object o) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ListIterator<T> listIterator() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ListIterator<T> listIterator(int index) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<T> subList(int fromIndex, int toIndex) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<T> stream() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<T> parallelStream() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Spliterator<T> spliterator() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean removeIf(Predicate<? super T> filter) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void replaceAll(UnaryOperator<T> operator) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sort(Comparator<? super T> c) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
private static final class UncheckedIterator<T> implements Iterator<T> {
|
||||||
|
|
||||||
|
private final int size;
|
||||||
|
|
||||||
|
private final T[] data;
|
||||||
|
|
||||||
|
private int index;
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
UncheckedIterator(T[] array) {
|
||||||
|
data = array;
|
||||||
|
size = data.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return index < size;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T next() {
|
||||||
|
if (index < size) {
|
||||||
|
return data[index++];
|
||||||
|
}
|
||||||
|
throw new NoSuchElementException("No more elements in this list");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,261 @@
|
||||||
|
package org.xbib.jdbc.pool.util;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ListIterator;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
import java.util.Spliterator;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.function.UnaryOperator;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import static java.lang.System.arraycopy;
|
||||||
|
import static java.lang.reflect.Array.newInstance;
|
||||||
|
|
||||||
|
public final class UncheckedArrayList<T> implements List<T> {
|
||||||
|
|
||||||
|
private final Iterator<T> emptyIterator = new Iterator<T>() {
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T next() {
|
||||||
|
throw new NoSuchElementException();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private T[] data;
|
||||||
|
private int size;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private UncheckedArrayList(Class<?> clazz, int capacity) {
|
||||||
|
data = (T[]) newInstance(clazz, capacity);
|
||||||
|
size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UncheckedArrayList(Class<?> clazz) {
|
||||||
|
this(clazz, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UncheckedArrayList(Class<?> clazz, T[] initial) {
|
||||||
|
this(clazz, initial.length);
|
||||||
|
arraycopy(initial, 0, data, 0, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean add(T element) {
|
||||||
|
if (size >= data.length) {
|
||||||
|
data = Arrays.copyOf(data, data.length << 1);
|
||||||
|
}
|
||||||
|
data[size] = element;
|
||||||
|
size++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T get(int index) {
|
||||||
|
return data[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean remove(Object element) {
|
||||||
|
for (int index = size - 1; index >= 0; index--) {
|
||||||
|
if (element == data[index]) {
|
||||||
|
int numMoved = size - index - 1;
|
||||||
|
if (numMoved > 0) {
|
||||||
|
arraycopy(data, index + 1, data, index, numMoved);
|
||||||
|
}
|
||||||
|
data[--size] = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
data[i] = null;
|
||||||
|
}
|
||||||
|
size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return size == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T set(int index, T element) {
|
||||||
|
T old = data[index];
|
||||||
|
data[index] = element;
|
||||||
|
return old;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T remove(int index) {
|
||||||
|
if (size == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
T old = data[index];
|
||||||
|
int moved = size - index - 1;
|
||||||
|
if (moved > 0) {
|
||||||
|
arraycopy(data, index + 1, data, index, moved);
|
||||||
|
}
|
||||||
|
data[--size] = null;
|
||||||
|
return old;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contains(Object o) {
|
||||||
|
for (T element : this) {
|
||||||
|
if (element == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<T> iterator() {
|
||||||
|
return size == 0 ? emptyIterator : new UncheckedIterator<>(data, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object[] toArray() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <E> E[] toArray(E[] a) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsAll(Collection<?> c) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean addAll(Collection<? extends T> c) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean addAll(int index, Collection<? extends T> c) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean removeAll(Collection<?> c) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean retainAll(Collection<?> c) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void add(int index, T element) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int indexOf(Object o) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int lastIndexOf(Object o) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ListIterator<T> listIterator() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ListIterator<T> listIterator(int index) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<T> subList(int fromIndex, int toIndex) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<T> stream() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<T> parallelStream() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void forEach(Consumer<? super T> action) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Spliterator<T> spliterator() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean removeIf(Predicate<? super T> filter) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void replaceAll(UnaryOperator<T> operator) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sort(Comparator<? super T> c) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class UncheckedIterator<T> implements Iterator<T> {
|
||||||
|
|
||||||
|
private final int size;
|
||||||
|
|
||||||
|
private final T[] data;
|
||||||
|
|
||||||
|
private int index;
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
UncheckedIterator(T[] data, int size) {
|
||||||
|
this.data = data;
|
||||||
|
this.size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return index < size;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T next() {
|
||||||
|
if (index < size) {
|
||||||
|
return data[index++];
|
||||||
|
}
|
||||||
|
throw new NoSuchElementException("No more elements in this list");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
package org.xbib.jdbc.pool.util;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import javax.sql.ConnectionEventListener;
|
||||||
|
import javax.sql.StatementEventListener;
|
||||||
|
import javax.sql.XAConnection;
|
||||||
|
import javax.transaction.xa.XAResource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disguises a non-XA connection as an XAConnection. Useful to keep the same logic for pooling both XA and non-XA connections
|
||||||
|
*/
|
||||||
|
public class XAConnectionAdaptor implements XAConnection {
|
||||||
|
|
||||||
|
private final Connection connection;
|
||||||
|
|
||||||
|
public XAConnectionAdaptor(Connection connection) {
|
||||||
|
this.connection = connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Connection getConnection() {
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws SQLException {
|
||||||
|
connection.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addConnectionEventListener(ConnectionEventListener listener) {
|
||||||
|
throw new IllegalArgumentException("no ConnectionEventListener on non-XA connection");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeConnectionEventListener(ConnectionEventListener listener) {
|
||||||
|
throw new IllegalArgumentException("no ConnectionEventListener on non-XA connection");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addStatementEventListener(StatementEventListener listener) {
|
||||||
|
throw new IllegalArgumentException("no StatementEventListener on non-XA connection");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeStatementEventListener(StatementEventListener listener) {
|
||||||
|
throw new IllegalArgumentException("no StatementEventListener on non-XA connection");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public XAResource getXAResource() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package org.xbib.jdbc.pool.util;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.LongAdder;
|
||||||
|
import java.util.concurrent.locks.AbstractQueuedLongSynchronizer;
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public final class XbibSynchronizer extends AbstractQueuedLongSynchronizer {
|
||||||
|
|
||||||
|
private final LongAdder counter = new LongAdder();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean tryAcquire(long value) {
|
||||||
|
return counter.longValue() > value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean tryRelease(long releases) {
|
||||||
|
counter.add(releases);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getStamp() {
|
||||||
|
return counter.sum();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void releaseConditional() {
|
||||||
|
if (hasQueuedThreads()) {
|
||||||
|
release(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,805 @@
|
||||||
|
package org.xbib.jdbc.pool.wrapper;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationHandler;
|
||||||
|
import java.sql.Array;
|
||||||
|
import java.sql.Blob;
|
||||||
|
import java.sql.CallableStatement;
|
||||||
|
import java.sql.Clob;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.DatabaseMetaData;
|
||||||
|
import java.sql.NClob;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.SQLClientInfoException;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.SQLWarning;
|
||||||
|
import java.sql.SQLXML;
|
||||||
|
import java.sql.Savepoint;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.sql.Struct;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import org.xbib.jdbc.pool.ConnectionHandler;
|
||||||
|
import org.xbib.jdbc.pool.util.AutoCloseableElement;
|
||||||
|
import static java.lang.reflect.Proxy.newProxyInstance;
|
||||||
|
|
||||||
|
public final class ConnectionWrapper extends AutoCloseableElement implements Connection {
|
||||||
|
|
||||||
|
private static final String CLOSED_HANDLER_STRING = ConnectionWrapper.class.getSimpleName() + ".CLOSED_CONNECTION";
|
||||||
|
|
||||||
|
private static final InvocationHandler CLOSED_HANDLER = (proxy, method, args) -> switch (method.getName()) {
|
||||||
|
case "abort", "close" -> Void.TYPE;
|
||||||
|
case "isClosed" -> Boolean.TRUE;
|
||||||
|
case "isValid" -> Boolean.FALSE;
|
||||||
|
case "toString" -> CLOSED_HANDLER_STRING;
|
||||||
|
default -> throw new SQLException("Connection is closed");
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final Connection CLOSED_CONNECTION =
|
||||||
|
(Connection) newProxyInstance(Connection.class.getClassLoader(), new Class<?>[]{Connection.class}, CLOSED_HANDLER);
|
||||||
|
|
||||||
|
private static final JdbcResourcesLeakReport JDBC_RESOURCES_NOT_LEAKED = new JdbcResourcesLeakReport(0, 0);
|
||||||
|
|
||||||
|
// Connection.close() does not return the connection to the pool.
|
||||||
|
private final boolean detached;
|
||||||
|
|
||||||
|
// Collection of Statements to close them on close(). If null Statements are not tracked.
|
||||||
|
private final AutoCloseableElement trackedStatements;
|
||||||
|
private final ConnectionHandler handler;
|
||||||
|
private int leakedResultSets;
|
||||||
|
private Connection wrappedConnection;
|
||||||
|
|
||||||
|
public ConnectionWrapper(ConnectionHandler connectionHandler, boolean trackResources) {
|
||||||
|
this(connectionHandler, trackResources, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConnectionWrapper(ConnectionHandler connectionHandler, boolean trackResources, boolean detached) {
|
||||||
|
super(null);
|
||||||
|
handler = connectionHandler;
|
||||||
|
wrappedConnection = connectionHandler.rawConnection();
|
||||||
|
trackedStatements = trackResources ? AutoCloseableElement.newHead() : null;
|
||||||
|
this.detached = detached;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConnectionWrapper(ConnectionHandler connectionHandler, boolean trackResources, AutoCloseableElement head) {
|
||||||
|
super(head);
|
||||||
|
handler = connectionHandler;
|
||||||
|
wrappedConnection = connectionHandler.rawConnection();
|
||||||
|
trackedStatements = trackResources ? AutoCloseableElement.newHead() : null;
|
||||||
|
detached = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConnectionHandler getHandler() {
|
||||||
|
return handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDetached() {
|
||||||
|
return detached;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Statement trackStatement(Statement statement) {
|
||||||
|
if (trackedStatements != null && statement != null) {
|
||||||
|
return new StatementWrapper(this, statement, true, trackedStatements);
|
||||||
|
}
|
||||||
|
return statement;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CallableStatement trackCallableStatement(CallableStatement statement) {
|
||||||
|
if (trackedStatements != null && statement != null) {
|
||||||
|
return new CallableStatementWrapper(this, statement, true, trackedStatements);
|
||||||
|
}
|
||||||
|
return statement;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PreparedStatement trackPreparedStatement(PreparedStatement statement) {
|
||||||
|
if (trackedStatements != null && statement != null) {
|
||||||
|
return new PreparedStatementWrapper(this, statement, true, trackedStatements);
|
||||||
|
}
|
||||||
|
return statement;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JdbcResourcesLeakReport closeTrackedStatements() throws SQLException {
|
||||||
|
if (trackedStatements != null) {
|
||||||
|
return new JdbcResourcesLeakReport(trackedStatements.closeAllAutocloseableElements(), leakedResultSets);
|
||||||
|
}
|
||||||
|
return JDBC_RESOURCES_NOT_LEAKED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws SQLException {
|
||||||
|
handler.traceConnectionOperation("close()");
|
||||||
|
if (wrappedConnection != CLOSED_CONNECTION) {
|
||||||
|
wrappedConnection = CLOSED_CONNECTION;
|
||||||
|
handler.onConnectionWrapperClose(this, closeTrackedStatements());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void abort(Executor executor) throws SQLException {
|
||||||
|
handler.traceConnectionOperation("abort()");
|
||||||
|
try {
|
||||||
|
wrappedConnection = CLOSED_CONNECTION;
|
||||||
|
wrappedConnection.abort(executor);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getAutoCommit() throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("getAutoCommit()");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
return wrappedConnection.getAutoCommit();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAutoCommit(boolean autoCommit) throws SQLException {
|
||||||
|
handler.traceConnectionOperation("setAutoCommit(boolean)");
|
||||||
|
if (autoCommit && handler.isEnlisted()) {
|
||||||
|
handler.setFlushOnly();
|
||||||
|
throw new SQLException("Trying to set autocommit in connection taking part of transaction");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
if (wrappedConnection.getAutoCommit() != autoCommit) {
|
||||||
|
handler.setDirtyAttribute(ConnectionHandler.DirtyAttribute.AUTOCOMMIT);
|
||||||
|
wrappedConnection.setAutoCommit(autoCommit);
|
||||||
|
}
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void commit() throws SQLException {
|
||||||
|
handler.traceConnectionOperation("commit()");
|
||||||
|
if (handler.isEnlisted()) {
|
||||||
|
handler.setFlushOnly();
|
||||||
|
throw new SQLException("Attempting to commit while taking part in a transaction");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
wrappedConnection.commit();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void rollback() throws SQLException {
|
||||||
|
handler.traceConnectionOperation("rollback()");
|
||||||
|
if (handler.isEnlisted()) {
|
||||||
|
handler.setFlushOnly();
|
||||||
|
throw new SQLException("Attempting to rollback while enlisted in a transaction");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
wrappedConnection.rollback();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void rollback(Savepoint savepoint) throws SQLException {
|
||||||
|
handler.traceConnectionOperation("rollback(Savepoint)");
|
||||||
|
if (handler.isEnlisted()) {
|
||||||
|
handler.setFlushOnly();
|
||||||
|
throw new SQLException("Attempting to rollback while enlisted in a transaction");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
wrappedConnection.rollback(savepoint);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearWarnings() throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("clearWarnings()");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
wrappedConnection.clearWarnings();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Clob createClob() throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("createClob()");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
return wrappedConnection.createClob();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Blob createBlob() throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("createBlob()");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
return wrappedConnection.createBlob();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NClob createNClob() throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("createNClob()");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
return wrappedConnection.createNClob();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SQLXML createSQLXML() throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("createSQLXML()");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
return wrappedConnection.createSQLXML();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("createArrayOf(String, Object[])");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
return wrappedConnection.createArrayOf(typeName, elements);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Statement createStatement() throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("createStatement()");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
return trackStatement(wrappedConnection.createStatement());
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("createStatement(int, int)");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
return trackStatement(wrappedConnection.createStatement(resultSetType, resultSetConcurrency));
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("createStatement(int, int, int)");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
return trackStatement(wrappedConnection.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability));
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("createStruct(String, Object[])");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
return wrappedConnection.createStruct(typeName, attributes);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCatalog() throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("getCatalog()");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
return wrappedConnection.getCatalog();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCatalog(String catalog) throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("setCatalog(String)");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
handler.setDirtyAttribute(ConnectionHandler.DirtyAttribute.CATALOG);
|
||||||
|
wrappedConnection.setCatalog(catalog);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getHoldability() throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("getHoldability()");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
return wrappedConnection.getHoldability();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setHoldability(int holdability) throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("setHoldability(int)");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
wrappedConnection.setHoldability(holdability);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Properties getClientInfo() throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("getClientInfo()");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
return wrappedConnection.getClientInfo();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setClientInfo(Properties properties) throws SQLClientInfoException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("setClientInfo(Properties)");
|
||||||
|
wrappedConnection.setClientInfo(properties);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getClientInfo(String name) throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("getClientInfo(String)");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
return wrappedConnection.getClientInfo(name);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DatabaseMetaData getMetaData() throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("getMetaData()");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
return wrappedConnection.getMetaData();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getNetworkTimeout() throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("getNetworkTimeout()");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
return wrappedConnection.getNetworkTimeout();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSchema() throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("getSchema()");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
return wrappedConnection.getSchema();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSchema(String schema) throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("setSchema(String)");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
handler.setDirtyAttribute(ConnectionHandler.DirtyAttribute.SCHEMA);
|
||||||
|
wrappedConnection.setSchema(schema);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Class<?>> getTypeMap() throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("getTypeMap()");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
return wrappedConnection.getTypeMap();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("setTypeMap(Map<String, Class<?>>)");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
wrappedConnection.setTypeMap(map);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTransactionIsolation() throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("getTransactionIsolation()");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
return wrappedConnection.getTransactionIsolation();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTransactionIsolation(int level) throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("setTransactionIsolation(int)");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
handler.setDirtyAttribute(ConnectionHandler.DirtyAttribute.TRANSACTION_ISOLATION);
|
||||||
|
wrappedConnection.setTransactionIsolation(level);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SQLWarning getWarnings() throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("getWarnings()");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
return wrappedConnection.getWarnings();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isClosed() throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("isClosed()");
|
||||||
|
return wrappedConnection.isClosed();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReadOnly() throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("isReadOnly()");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
return wrappedConnection.isReadOnly();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setReadOnly(boolean readOnly) throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("setReadOnly(boolean)");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
wrappedConnection.setReadOnly(readOnly);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid(int timeout) throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("isValid(int)");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
return wrappedConnection.isValid(timeout);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String nativeSQL(String sql) throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("nativeSQL(String)");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
return wrappedConnection.nativeSQL(sql);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CallableStatement prepareCall(String sql) throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("prepareCall(String)");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
return trackCallableStatement(wrappedConnection.prepareCall(sql));
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("prepareCall(String, int, int)");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
return trackCallableStatement(wrappedConnection.prepareCall(sql, resultSetType, resultSetConcurrency));
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("prepareCall(String, int, int, int)");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
return trackCallableStatement(wrappedConnection.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability));
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PreparedStatement prepareStatement(String sql) throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("prepareStatement(String)");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
return trackPreparedStatement(wrappedConnection.prepareStatement(sql));
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("prepareStatement(String, int, int)");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
return trackPreparedStatement(wrappedConnection.prepareStatement(sql, resultSetType, resultSetConcurrency));
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("prepareStatement(String, int, int, int)");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
return trackPreparedStatement(wrappedConnection.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability));
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("prepareStatement(String, int)");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
return trackPreparedStatement(wrappedConnection.prepareStatement(sql, autoGeneratedKeys));
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("prepareStatement(String, int[])");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
return trackPreparedStatement(wrappedConnection.prepareStatement(sql, columnIndexes));
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("prepareStatement(String, String[])");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
return trackPreparedStatement(wrappedConnection.prepareStatement(sql, columnNames));
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void releaseSavepoint(Savepoint savepoint) throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("releaseSavepoint(Savepoint)");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
wrappedConnection.releaseSavepoint(savepoint);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setClientInfo(String name, String value) throws SQLClientInfoException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("setClientInfo(String, String)");
|
||||||
|
wrappedConnection.setClientInfo(name, value);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Savepoint setSavepoint() throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("setSavepoint()");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
return wrappedConnection.setSavepoint();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Savepoint setSavepoint(String name) throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("setSavepoint(String)");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
return wrappedConnection.setSavepoint(name);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("setNetworkTimeout(Executor, int)");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
handler.setDirtyAttribute(ConnectionHandler.DirtyAttribute.NETWORK_TIMEOUT);
|
||||||
|
wrappedConnection.setNetworkTimeout(executor, milliseconds);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T unwrap(Class<T> target) throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("unwrap(Class<T>)");
|
||||||
|
return wrappedConnection.unwrap(target);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isWrapperFor(Class<?> target) throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("isWrapperFor(Class<?>)");
|
||||||
|
return wrappedConnection.isWrapperFor(target);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "wrapped[" + wrappedConnection + (handler.isEnlisted() ? "]<<enrolled" : "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addLeakedResultSets(int leaks) {
|
||||||
|
leakedResultSets += leaks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class JdbcResourcesLeakReport {
|
||||||
|
|
||||||
|
private final int statementCount;
|
||||||
|
|
||||||
|
private final int resultSetCount;
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
JdbcResourcesLeakReport(int statementCount, int resultSetCount) {
|
||||||
|
this.statementCount = statementCount;
|
||||||
|
this.resultSetCount = resultSetCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int statementCount() {
|
||||||
|
return statementCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int resultSetCount() {
|
||||||
|
return resultSetCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasLeak() {
|
||||||
|
return statementCount != 0 || resultSetCount != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,637 @@
|
||||||
|
package org.xbib.jdbc.pool.wrapper;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.lang.reflect.InvocationHandler;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.sql.Array;
|
||||||
|
import java.sql.Blob;
|
||||||
|
import java.sql.Clob;
|
||||||
|
import java.sql.Date;
|
||||||
|
import java.sql.NClob;
|
||||||
|
import java.sql.ParameterMetaData;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.Ref;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.ResultSetMetaData;
|
||||||
|
import java.sql.RowId;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.SQLType;
|
||||||
|
import java.sql.SQLXML;
|
||||||
|
import java.sql.Time;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import org.xbib.jdbc.pool.util.AutoCloseableElement;
|
||||||
|
import static java.lang.reflect.Proxy.newProxyInstance;
|
||||||
|
|
||||||
|
public final class PreparedStatementWrapper extends StatementWrapper implements PreparedStatement {
|
||||||
|
|
||||||
|
static final String CLOSED_PREPARED_STATEMENT_STRING = PreparedStatementWrapper.class.getSimpleName() + ".CLOSED_STATEMENT";
|
||||||
|
|
||||||
|
private static final InvocationHandler CLOSED_HANDLER = (proxy, method, args) -> switch (method.getName()) {
|
||||||
|
case "close" -> Void.TYPE;
|
||||||
|
case "isClosed" -> Boolean.TRUE;
|
||||||
|
case "toString" -> CLOSED_PREPARED_STATEMENT_STRING;
|
||||||
|
default -> throw new SQLException("CallableStatement is closed");
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final PreparedStatement CLOSED_STATEMENT =
|
||||||
|
(PreparedStatement) newProxyInstance(PreparedStatement.class.getClassLoader(), new Class<?>[]{PreparedStatement.class}, CLOSED_HANDLER);
|
||||||
|
|
||||||
|
private PreparedStatement wrappedStatement;
|
||||||
|
|
||||||
|
public PreparedStatementWrapper(ConnectionWrapper connectionWrapper, PreparedStatement statement, boolean trackJdbcResources, AutoCloseableElement head) {
|
||||||
|
super(connectionWrapper, statement, trackJdbcResources, head);
|
||||||
|
wrappedStatement = statement;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws SQLException {
|
||||||
|
wrappedStatement = CLOSED_STATEMENT;
|
||||||
|
super.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResultSet executeQuery() throws SQLException {
|
||||||
|
try {
|
||||||
|
return trackResultSet(wrappedStatement.executeQuery());
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int executeUpdate() throws SQLException {
|
||||||
|
try {
|
||||||
|
return wrappedStatement.executeUpdate();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNull(int parameterIndex, int sqlType) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setNull(parameterIndex, sqlType);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBoolean(int parameterIndex, boolean x) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setBoolean(parameterIndex, x);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setByte(int parameterIndex, byte x) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setByte(parameterIndex, x);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setShort(int parameterIndex, short x) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setShort(parameterIndex, x);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setInt(int parameterIndex, int x) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setInt(parameterIndex, x);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLong(int parameterIndex, long x) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setLong(parameterIndex, x);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFloat(int parameterIndex, float x) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setFloat(parameterIndex, x);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDouble(int parameterIndex, double x) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setDouble(parameterIndex, x);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setBigDecimal(parameterIndex, x);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setString(int parameterIndex, String x) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setString(parameterIndex, x);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBytes(int parameterIndex, byte[] x) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setBytes(parameterIndex, x);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDate(int parameterIndex, Date x) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setDate(parameterIndex, x);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTime(int parameterIndex, Time x) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setTime(parameterIndex, x);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setTimestamp(parameterIndex, x);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setAsciiStream(parameterIndex, x, length);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setUnicodeStream(parameterIndex, x, length);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setBinaryStream(parameterIndex, x, length);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearParameters() throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.clearParameters();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setObject(parameterIndex, x, targetSqlType);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setObject(int parameterIndex, Object x) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setObject(parameterIndex, x);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean execute() throws SQLException {
|
||||||
|
try {
|
||||||
|
return wrappedStatement.execute();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addBatch() throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.addBatch();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setCharacterStream(parameterIndex, reader, length);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRef(int parameterIndex, Ref x) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setRef(parameterIndex, x);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBlob(int parameterIndex, Blob x) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setBlob(parameterIndex, x);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setClob(int parameterIndex, Clob x) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setClob(parameterIndex, x);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setArray(int parameterIndex, Array x) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setArray(parameterIndex, x);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResultSetMetaData getMetaData() throws SQLException {
|
||||||
|
try {
|
||||||
|
return wrappedStatement.getMetaData();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setDate(parameterIndex, x, cal);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setTime(parameterIndex, x, cal);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setTimestamp(parameterIndex, x, cal);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setNull(parameterIndex, sqlType, typeName);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setURL(int parameterIndex, URL x) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setURL(parameterIndex, x);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ParameterMetaData getParameterMetaData() throws SQLException {
|
||||||
|
try {
|
||||||
|
return wrappedStatement.getParameterMetaData();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRowId(int parameterIndex, RowId x) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setRowId(parameterIndex, x);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNString(int parameterIndex, String value) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setNString(parameterIndex, value);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNCharacterStream(int parameterIndex, Reader value, long length) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setNCharacterStream(parameterIndex, value, length);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNClob(int parameterIndex, NClob value) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setNClob(parameterIndex, value);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setClob(int parameterIndex, Reader reader, long length) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setClob(parameterIndex, reader, length);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setBlob(parameterIndex, inputStream, length);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setNClob(parameterIndex, reader, length);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setSQLXML(parameterIndex, xmlObject);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setObject(parameterIndex, x, targetSqlType, scaleOrLength);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setAsciiStream(parameterIndex, x, length);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setBinaryStream(parameterIndex, x, length);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setCharacterStream(parameterIndex, reader, length);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setAsciiStream(parameterIndex, x);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setBinaryStream(parameterIndex, x);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setCharacterStream(parameterIndex, reader);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setNCharacterStream(parameterIndex, value);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setClob(int parameterIndex, Reader reader) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setClob(parameterIndex, reader);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setBlob(parameterIndex, inputStream);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNClob(int parameterIndex, Reader reader) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setNClob(parameterIndex, reader);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- JDBC 4.2 //
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setObject(int parameterIndex, Object x, SQLType targetSqlType, int scaleOrLength) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setObject(parameterIndex, x, targetSqlType, scaleOrLength);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setObject(int parameterIndex, Object x, SQLType targetSqlType) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setObject(parameterIndex, x, targetSqlType);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long executeLargeUpdate() throws SQLException {
|
||||||
|
try {
|
||||||
|
return wrappedStatement.executeLargeUpdate();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,584 @@
|
||||||
|
package org.xbib.jdbc.pool.wrapper;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationHandler;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.SQLWarning;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import org.xbib.jdbc.pool.util.AutoCloseableElement;
|
||||||
|
import static java.lang.reflect.Proxy.newProxyInstance;
|
||||||
|
|
||||||
|
public class StatementWrapper extends AutoCloseableElement implements Statement {
|
||||||
|
|
||||||
|
static final String CLOSED_STATEMENT_STRING = StatementWrapper.class.getSimpleName() + ".CLOSED_STATEMENT";
|
||||||
|
|
||||||
|
private static final InvocationHandler CLOSED_HANDLER = (proxy, method, args) -> switch (method.getName()) {
|
||||||
|
case "close" -> Void.TYPE;
|
||||||
|
case "isClosed" -> Boolean.TRUE;
|
||||||
|
case "toString" -> CLOSED_STATEMENT_STRING;
|
||||||
|
default -> throw new SQLException("Statement is closed");
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final Statement CLOSED_STATEMENT =
|
||||||
|
(Statement) newProxyInstance(Statement.class.getClassLoader(), new Class<?>[]{Statement.class}, CLOSED_HANDLER);
|
||||||
|
|
||||||
|
@SuppressWarnings("ProtectedField")
|
||||||
|
protected final ConnectionWrapper connection;
|
||||||
|
|
||||||
|
// Collection of ResultSet to close them on close(). If null ResultSet are not tracked.
|
||||||
|
private final AutoCloseableElement trackedResultSets;
|
||||||
|
|
||||||
|
private Statement wrappedStatement;
|
||||||
|
|
||||||
|
public StatementWrapper(ConnectionWrapper connectionWrapper, Statement statement, boolean trackResources, AutoCloseableElement head) {
|
||||||
|
super(head);
|
||||||
|
connection = connectionWrapper;
|
||||||
|
wrappedStatement = statement;
|
||||||
|
trackedResultSets = trackResources ? AutoCloseableElement.newHead() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ResultSet trackResultSet(ResultSet resultSet) {
|
||||||
|
if (trackedResultSets != null && resultSet != null) {
|
||||||
|
return new ResultSetWrapper(this, resultSet, trackedResultSets);
|
||||||
|
}
|
||||||
|
return resultSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeTrackedResultSets() throws SQLException {
|
||||||
|
if (trackedResultSets != null) {
|
||||||
|
connection.addLeakedResultSets(trackedResultSets.closeAllAutocloseableElements());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ConnectionWrapper getConnectionWrapper() throws SQLException {
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws SQLException {
|
||||||
|
try {
|
||||||
|
if (wrappedStatement != CLOSED_STATEMENT) {
|
||||||
|
closeTrackedResultSets();
|
||||||
|
wrappedStatement.close();
|
||||||
|
}
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
} finally {
|
||||||
|
wrappedStatement = CLOSED_STATEMENT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void clearWarnings() throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.clearWarnings();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final ResultSet executeQuery(String sql) throws SQLException {
|
||||||
|
try {
|
||||||
|
return trackResultSet(wrappedStatement.executeQuery(sql));
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final int executeUpdate(String sql) throws SQLException {
|
||||||
|
try {
|
||||||
|
return wrappedStatement.executeUpdate(sql);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final int getMaxFieldSize() throws SQLException {
|
||||||
|
try {
|
||||||
|
return wrappedStatement.getMaxFieldSize();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void setMaxFieldSize(int max) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setMaxFieldSize(max);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final int getMaxRows() throws SQLException {
|
||||||
|
try {
|
||||||
|
return wrappedStatement.getMaxRows();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void setMaxRows(int max) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setMaxRows(max);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void setEscapeProcessing(boolean enable) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setEscapeProcessing(enable);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final int getQueryTimeout() throws SQLException {
|
||||||
|
try {
|
||||||
|
return wrappedStatement.getQueryTimeout();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void setQueryTimeout(int seconds) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setQueryTimeout(seconds);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void cancel() throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.cancel();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void setCursorName(String name) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setCursorName(name);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final boolean execute(String sql) throws SQLException {
|
||||||
|
try {
|
||||||
|
return wrappedStatement.execute(sql);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final ResultSet getResultSet() throws SQLException {
|
||||||
|
try {
|
||||||
|
return trackResultSet(wrappedStatement.getResultSet());
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final int getUpdateCount() throws SQLException {
|
||||||
|
try {
|
||||||
|
return wrappedStatement.getUpdateCount();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final boolean getMoreResults() throws SQLException {
|
||||||
|
try {
|
||||||
|
return wrappedStatement.getMoreResults();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final int getFetchDirection() throws SQLException {
|
||||||
|
try {
|
||||||
|
return wrappedStatement.getFetchDirection();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void setFetchDirection(int direction) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setFetchDirection(direction);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final int getFetchSize() throws SQLException {
|
||||||
|
try {
|
||||||
|
return wrappedStatement.getFetchSize();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void setFetchSize(int rows) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setFetchSize(rows);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final int getResultSetConcurrency() throws SQLException {
|
||||||
|
try {
|
||||||
|
return wrappedStatement.getResultSetConcurrency();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final int getResultSetType() throws SQLException {
|
||||||
|
try {
|
||||||
|
return wrappedStatement.getResultSetType();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void addBatch(String sql) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.addBatch(sql);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void clearBatch() throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.clearBatch();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final int[] executeBatch() throws SQLException {
|
||||||
|
try {
|
||||||
|
return wrappedStatement.executeBatch();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final Connection getConnection() throws SQLException {
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final boolean getMoreResults(int current) throws SQLException {
|
||||||
|
try {
|
||||||
|
return wrappedStatement.getMoreResults(current);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final ResultSet getGeneratedKeys() throws SQLException {
|
||||||
|
try {
|
||||||
|
return trackResultSet(wrappedStatement.getGeneratedKeys());
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
|
||||||
|
try {
|
||||||
|
return wrappedStatement.executeUpdate(sql, autoGeneratedKeys);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
|
||||||
|
try {
|
||||||
|
return wrappedStatement.executeUpdate(sql, columnIndexes);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final int executeUpdate(String sql, String[] columnNames) throws SQLException {
|
||||||
|
try {
|
||||||
|
return wrappedStatement.executeUpdate(sql, columnNames);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
|
||||||
|
try {
|
||||||
|
return wrappedStatement.execute(sql, autoGeneratedKeys);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final boolean execute(String sql, int[] columnIndexes) throws SQLException {
|
||||||
|
try {
|
||||||
|
return wrappedStatement.execute(sql, columnIndexes);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final boolean execute(String sql, String[] columnNames) throws SQLException {
|
||||||
|
try {
|
||||||
|
return wrappedStatement.execute(sql, columnNames);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final int getResultSetHoldability() throws SQLException {
|
||||||
|
try {
|
||||||
|
return wrappedStatement.getResultSetHoldability();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final boolean isPoolable() throws SQLException {
|
||||||
|
try {
|
||||||
|
return wrappedStatement.isPoolable();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void setPoolable(boolean poolable) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setPoolable(poolable);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void closeOnCompletion() throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.closeOnCompletion();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final boolean isCloseOnCompletion() throws SQLException {
|
||||||
|
try {
|
||||||
|
return wrappedStatement.isCloseOnCompletion();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final SQLWarning getWarnings() throws SQLException {
|
||||||
|
try {
|
||||||
|
return wrappedStatement.getWarnings();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final boolean isClosed() throws SQLException {
|
||||||
|
try {
|
||||||
|
return wrappedStatement.isClosed();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- JDBC 4.2 //
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getLargeUpdateCount() throws SQLException {
|
||||||
|
try {
|
||||||
|
return wrappedStatement.getLargeUpdateCount();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getLargeMaxRows() throws SQLException {
|
||||||
|
try {
|
||||||
|
return wrappedStatement.getLargeMaxRows();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLargeMaxRows(long max) throws SQLException {
|
||||||
|
try {
|
||||||
|
wrappedStatement.setLargeMaxRows(max);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long[] executeLargeBatch() throws SQLException {
|
||||||
|
try {
|
||||||
|
return wrappedStatement.executeLargeBatch();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long executeLargeUpdate(String sql) throws SQLException {
|
||||||
|
try {
|
||||||
|
return wrappedStatement.executeLargeUpdate(sql);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long executeLargeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
|
||||||
|
try {
|
||||||
|
return wrappedStatement.executeLargeUpdate(sql, autoGeneratedKeys);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long executeLargeUpdate(String sql, int[] columnIndexes) throws SQLException {
|
||||||
|
try {
|
||||||
|
return wrappedStatement.executeLargeUpdate(sql, columnIndexes);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long executeLargeUpdate(String sql, String[] columnNames) throws SQLException {
|
||||||
|
try {
|
||||||
|
return wrappedStatement.executeLargeUpdate(sql, columnNames);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final <T> T unwrap(Class<T> target) throws SQLException {
|
||||||
|
try {
|
||||||
|
return wrappedStatement.unwrap(target);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final boolean isWrapperFor(Class<?> target) throws SQLException {
|
||||||
|
try {
|
||||||
|
return wrappedStatement.isWrapperFor(target);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
connection.getHandler().setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final String toString() {
|
||||||
|
return "wrapped[ " + wrappedStatement + " ]";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,112 @@
|
||||||
|
package org.xbib.jdbc.pool.wrapper;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationHandler;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import javax.sql.ConnectionEventListener;
|
||||||
|
import javax.sql.StatementEventListener;
|
||||||
|
import javax.sql.XAConnection;
|
||||||
|
import javax.transaction.xa.XAResource;
|
||||||
|
import org.xbib.jdbc.pool.ConnectionHandler;
|
||||||
|
import org.xbib.jdbc.pool.util.AutoCloseableElement;
|
||||||
|
import static java.lang.reflect.Proxy.newProxyInstance;
|
||||||
|
|
||||||
|
public final class XAConnectionWrapper extends AutoCloseableElement implements XAConnection {
|
||||||
|
|
||||||
|
private static final String CLOSED_HANDLER_STRING = XAConnectionWrapper.class.getSimpleName() + ".CLOSED_XA_CONNECTION";
|
||||||
|
|
||||||
|
private static final InvocationHandler CLOSED_HANDLER = (proxy, method, args) -> switch (method.getName()) {
|
||||||
|
case "close" -> Void.TYPE;
|
||||||
|
case "isClosed" -> Boolean.TRUE;
|
||||||
|
case "toString" -> CLOSED_HANDLER_STRING;
|
||||||
|
default -> throw new SQLException("XAConnection is closed");
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final XAConnection CLOSED_XA_CONNECTION =
|
||||||
|
(XAConnection) newProxyInstance(XAConnection.class.getClassLoader(), new Class<?>[]{XAConnection.class}, CLOSED_HANDLER);
|
||||||
|
|
||||||
|
// Collection of XAResources to close them on close(). If null Statements are not tracked.
|
||||||
|
private final AutoCloseableElement trackedXAResources;
|
||||||
|
|
||||||
|
private final ConnectionHandler handler;
|
||||||
|
|
||||||
|
private XAConnection wrappedXAConnection;
|
||||||
|
|
||||||
|
public XAConnectionWrapper(ConnectionHandler connectionHandler, XAConnection xaConnection, boolean trackResources) {
|
||||||
|
super(null);
|
||||||
|
handler = connectionHandler;
|
||||||
|
wrappedXAConnection = xaConnection;
|
||||||
|
trackedXAResources = trackResources ? newHead() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private XAResource trackXAResource(XAResource resource) {
|
||||||
|
if (trackedXAResources != null && resource != null) {
|
||||||
|
return new XAResourceWrapper(handler, resource, trackedXAResources);
|
||||||
|
}
|
||||||
|
return resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isClosed() throws SQLException {
|
||||||
|
return wrappedXAConnection == CLOSED_XA_CONNECTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws SQLException {
|
||||||
|
handler.traceConnectionOperation("close()");
|
||||||
|
if (wrappedXAConnection != CLOSED_XA_CONNECTION) {
|
||||||
|
wrappedXAConnection = CLOSED_XA_CONNECTION;
|
||||||
|
|
||||||
|
trackedXAResources.closeAllAutocloseableElements();
|
||||||
|
handler.transactionEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public XAResource getXAResource() throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("getXAResource()");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
return trackXAResource(handler.getXaResource());
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Connection getConnection() throws SQLException {
|
||||||
|
try {
|
||||||
|
handler.traceConnectionOperation("getConnection()");
|
||||||
|
handler.verifyEnlistment();
|
||||||
|
return new ConnectionWrapper(handler, trackedXAResources != null, true);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
handler.setFlushOnly(se);
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addConnectionEventListener(ConnectionEventListener listener) {
|
||||||
|
handler.traceConnectionOperation("addConnectionEventListener()");
|
||||||
|
wrappedXAConnection.addConnectionEventListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeConnectionEventListener(ConnectionEventListener listener) {
|
||||||
|
handler.traceConnectionOperation("removeConnectionEventListener()");
|
||||||
|
wrappedXAConnection.removeConnectionEventListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addStatementEventListener(StatementEventListener listener) {
|
||||||
|
handler.traceConnectionOperation("addStatementEventListener()");
|
||||||
|
wrappedXAConnection.addStatementEventListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeStatementEventListener(StatementEventListener listener) {
|
||||||
|
handler.traceConnectionOperation("removeStatementEventListener()");
|
||||||
|
wrappedXAConnection.removeStatementEventListener(listener);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
package org.xbib.jdbc.pool.wrapper;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationHandler;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import javax.transaction.xa.XAException;
|
||||||
|
import javax.transaction.xa.XAResource;
|
||||||
|
import javax.transaction.xa.Xid;
|
||||||
|
import org.xbib.jdbc.pool.ConnectionHandler;
|
||||||
|
import org.xbib.jdbc.pool.util.AutoCloseableElement;
|
||||||
|
import static java.lang.reflect.Proxy.newProxyInstance;
|
||||||
|
|
||||||
|
public class XAResourceWrapper extends AutoCloseableElement implements XAResource {
|
||||||
|
|
||||||
|
private static final String CLOSED_HANDLER_STRING = XAResourceWrapper.class.getSimpleName() + ".CLOSED_XA_RESOURCE";
|
||||||
|
|
||||||
|
private static final InvocationHandler CLOSED_HANDLER = (proxy, method, args) -> switch (method.getName()) {
|
||||||
|
case "close" -> Void.TYPE;
|
||||||
|
case "isClosed" -> Boolean.TRUE;
|
||||||
|
case "toString" -> CLOSED_HANDLER_STRING;
|
||||||
|
default -> throw new SQLException("XAConnection for the XAResource is closed");
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final XAResource CLOSED_XA_RESOURCE =
|
||||||
|
(XAResource) newProxyInstance(XAResource.class.getClassLoader(), new Class<?>[]{XAResource.class}, CLOSED_HANDLER);
|
||||||
|
|
||||||
|
private final ConnectionHandler handler;
|
||||||
|
private XAResource wrappedXAResource;
|
||||||
|
|
||||||
|
public XAResourceWrapper(ConnectionHandler connectionHandler, XAResource resource, AutoCloseableElement head) {
|
||||||
|
super(head);
|
||||||
|
handler = connectionHandler;
|
||||||
|
wrappedXAResource = resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isClosed() throws Exception {
|
||||||
|
return wrappedXAResource == CLOSED_XA_RESOURCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws SQLException {
|
||||||
|
handler.traceConnectionOperation("xaResource.close()");
|
||||||
|
if (wrappedXAResource != CLOSED_XA_RESOURCE) {
|
||||||
|
wrappedXAResource = CLOSED_XA_RESOURCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void commit(Xid xid, boolean onePhase) throws XAException {
|
||||||
|
handler.traceConnectionOperation("xaResource.commit()");
|
||||||
|
wrappedXAResource.commit(xid, onePhase);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void end(Xid xid, int flags) throws XAException {
|
||||||
|
handler.traceConnectionOperation("xaResource.end()");
|
||||||
|
wrappedXAResource.end(xid, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void forget(Xid xid) throws XAException {
|
||||||
|
handler.traceConnectionOperation("xaResource.forget()");
|
||||||
|
wrappedXAResource.forget(xid);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTransactionTimeout() throws XAException {
|
||||||
|
handler.traceConnectionOperation("xaResource.getTransactionTimeout()");
|
||||||
|
return wrappedXAResource.getTransactionTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSameRM(XAResource xares) throws XAException {
|
||||||
|
handler.traceConnectionOperation("xaResource.isSameRM()");
|
||||||
|
return wrappedXAResource.isSameRM(xares);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int prepare(Xid xid) throws XAException {
|
||||||
|
handler.traceConnectionOperation("xaResource.prepare()");
|
||||||
|
return wrappedXAResource.prepare(xid);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Xid[] recover(int flag) throws XAException {
|
||||||
|
handler.traceConnectionOperation("xaResource.recover()");
|
||||||
|
return wrappedXAResource.recover(flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void rollback(Xid xid) throws XAException {
|
||||||
|
handler.traceConnectionOperation("xaResource.rollback()");
|
||||||
|
wrappedXAResource.rollback(xid);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean setTransactionTimeout(int seconds) throws XAException {
|
||||||
|
handler.traceConnectionOperation("xaResource.setTransactionTimeout()");
|
||||||
|
return wrappedXAResource.setTransactionTimeout(seconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start(Xid xid, int flags) throws XAException {
|
||||||
|
handler.traceConnectionOperation("xaResource.start()");
|
||||||
|
wrappedXAResource.start(xid, flags);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
dependencies {
|
dependencies {
|
||||||
api project(':jdbc-connection-pool')
|
|
||||||
testImplementation project(':jdbc-test')
|
testImplementation project(':jdbc-test')
|
||||||
testImplementation testLibs.derby
|
testImplementation testLibs.derby
|
||||||
testImplementation testLibs.hsqldb
|
testImplementation testLibs.hsqldb
|
||||||
|
|
|
@ -5,7 +5,6 @@ import org.xbib.jdbc.query.flavor.Hsql;
|
||||||
|
|
||||||
module org.xbib.jdbc.query {
|
module org.xbib.jdbc.query {
|
||||||
uses Flavor;
|
uses Flavor;
|
||||||
requires org.xbib.jdbc.connection.pool;
|
|
||||||
requires java.sql;
|
requires java.sql;
|
||||||
exports org.xbib.jdbc.query;
|
exports org.xbib.jdbc.query;
|
||||||
provides Flavor with Derby, Hsql, H2;
|
provides Flavor with Derby, Hsql, H2;
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
package org.xbib.jdbc.query;
|
package org.xbib.jdbc.query;
|
||||||
|
|
||||||
import org.xbib.jdbc.connection.pool.PoolConfig;
|
|
||||||
import org.xbib.jdbc.connection.pool.PoolDataSource;
|
|
||||||
import org.xbib.jdbc.query.util.Metric;
|
import org.xbib.jdbc.query.util.Metric;
|
||||||
|
|
||||||
import javax.sql.DataSource;
|
import javax.sql.DataSource;
|
||||||
|
@ -9,7 +7,6 @@ import java.sql.Connection;
|
||||||
import java.sql.DriverManager;
|
import java.sql.DriverManager;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
@ -24,8 +21,6 @@ public final class DatabaseProvider implements Supplier<Database> {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(DatabaseProvider.class.getName());
|
private static final Logger logger = Logger.getLogger(DatabaseProvider.class.getName());
|
||||||
|
|
||||||
private static final AtomicInteger poolNameCounter = new AtomicInteger(1);
|
|
||||||
|
|
||||||
private final DatabaseProviderBuilderImpl builder;
|
private final DatabaseProviderBuilderImpl builder;
|
||||||
|
|
||||||
private Connection connection;
|
private Connection connection;
|
||||||
|
@ -53,36 +48,6 @@ public final class DatabaseProvider implements Supplier<Database> {
|
||||||
}, new OptionsDefault(Flavor.valueOf(flavor)));
|
}, new OptionsDefault(Flavor.valueOf(flavor)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Configure the database from the following properties read from the provided configuration:
|
|
||||||
* <pre>
|
|
||||||
* database.url=... Database connect string (required)
|
|
||||||
* database.user=... Authenticate as this user (optional if provided in url)
|
|
||||||
* database.password=... User password (optional if user and password provided in
|
|
||||||
* url; prompted on standard input if user is provided and
|
|
||||||
* password is not)
|
|
||||||
* database.pool.size=... How many connections in the connection pool (default 10).
|
|
||||||
* database.driver.class The driver to initialize with Class.forName(). This will
|
|
||||||
* be guessed from the database.url if not provided.
|
|
||||||
* database.flavor A {@link Flavor}. If this is not provided the flavor will be guessed based on the
|
|
||||||
* value for database.url, if possible.
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* <p>The database flavor will be guessed based on the URL.</p>
|
|
||||||
*
|
|
||||||
* <p>A database pool will be created using jdbc-connection-pool.</p>
|
|
||||||
*/
|
|
||||||
public static DatabaseProviderBuilder builder(Config config) {
|
|
||||||
String flavorString = config.getString("database.flavor");
|
|
||||||
if (flavorString == null) {
|
|
||||||
String url = config.getString("database.url");
|
|
||||||
if (url == null) {
|
|
||||||
throw new DatabaseException("You must provide database.url");
|
|
||||||
}
|
|
||||||
flavorString = Flavor.fromJdbcUrl(url).getName();
|
|
||||||
}
|
|
||||||
return builder(createDataSource(config), flavorString);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builder method to create and initialize an instance of this class using
|
* Builder method to create and initialize an instance of this class using
|
||||||
|
@ -137,31 +102,6 @@ public final class DatabaseProvider implements Supplier<Database> {
|
||||||
}, new OptionsDefault(Flavor.fromJdbcUrl(url)));
|
}, new OptionsDefault(Flavor.fromJdbcUrl(url)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DataSource createDataSource(Config config) {
|
|
||||||
String url = config.getString("database.url");
|
|
||||||
if (url == null) {
|
|
||||||
throw new DatabaseException("You must provide database.url");
|
|
||||||
}
|
|
||||||
PoolConfig poolConfig = new PoolConfig();
|
|
||||||
poolConfig.setPoolName(config.getString("database.pool.name", "database-pool-" + poolNameCounter.getAndAdd(1)));
|
|
||||||
poolConfig.setUrl(config.getString("database.url"));
|
|
||||||
// provide driver class only if in config
|
|
||||||
String driverClassName = config.getString("database.driver.class");
|
|
||||||
if (driverClassName != null) {
|
|
||||||
poolConfig.setDriverClassName(config.getString("database.driver.class"));
|
|
||||||
}
|
|
||||||
poolConfig.setUsername(config.getString("database.user"));
|
|
||||||
poolConfig.setPassword(config.getString("database.password"));
|
|
||||||
poolConfig.setMaximumPoolSize(config.getInteger("database.pool.size", 8));
|
|
||||||
poolConfig.setAutoCommit(false);
|
|
||||||
try {
|
|
||||||
return new PoolDataSource(poolConfig);
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.log(Level.SEVERE, e.getMessage(), e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void transact(final DbCode code) {
|
public void transact(final DbCode code) {
|
||||||
boolean complete = false;
|
boolean complete = false;
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
dependencies {
|
dependencies {
|
||||||
api project(":jdbc-query")
|
api project(":jdbc-query")
|
||||||
|
implementation project(":jdbc-connection-pool")
|
||||||
implementation testLibs.junit.jupiter.api
|
implementation testLibs.junit.jupiter.api
|
||||||
implementation testLibs.hamcrest
|
implementation testLibs.hamcrest
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue