relax pool dependency

This commit is contained in:
Jörg Prante 2024-12-29 16:47:00 +01:00
parent 844b288a18
commit ac788cb95b
61 changed files with 12647 additions and 69 deletions

View file

@ -1,3 +1,3 @@
group = org.xbib
name = database
version = 2.2.0
version = 2.3.0

View file

@ -3,7 +3,6 @@ import org.xbib.jdbc.query.Flavor;
module org.xbib.jdbc.oracle {
requires org.xbib.jdbc.query;
requires org.xbib.jdbc.connection.pool;
requires com.oracle.database.jdbc;
requires java.sql;
uses Flavor;

View file

@ -1,7 +1,6 @@
package org.xbib.jdbc.oracle;
import oracle.jdbc.OraclePreparedStatement;
import org.xbib.jdbc.connection.pool.ProxyPreparedStatement;
import org.xbib.jdbc.query.Flavor;
import java.sql.PreparedStatement;
@ -45,7 +44,13 @@ public class Oracle implements Flavor {
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 {
if (preparedStatement instanceof ProxyPreparedStatement) {
ProxyPreparedStatement proxyPreparedStatement = (ProxyPreparedStatement) preparedStatement;
@ -53,14 +58,20 @@ public class Oracle implements Flavor {
} else {
((OraclePreparedStatement) preparedStatement).setBinaryFloat(i, floatValue);
}
}
}*/
@Override
public String typeDouble() {
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 {
if (preparedStatement instanceof ProxyPreparedStatement) {
ProxyPreparedStatement proxyPreparedStatement = (ProxyPreparedStatement) preparedStatement;
@ -68,7 +79,7 @@ public class Oracle implements Flavor {
} else {
((OraclePreparedStatement) preparedStatement).setBinaryDouble(i, doubleValue);
}
}
}*/
@Override
public String typeBigDecimal(int size, int precision) {

View 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;
}

View file

@ -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());
}
}
}
}

View file

@ -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
}
}

View 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);
}
}
}

View 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");
}
}

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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";
}
}
}

View 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();
}

View 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);
}
}

View file

@ -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
}
}

View file

@ -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) {
}
}

View file

@ -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() {
}
}

View file

@ -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);
}

View file

@ -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;
}
}

View 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();
}

View 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();
}

View 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);
}
};
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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";
}
}
}

View file

@ -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;
}
};
}
}

View file

@ -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;
}
};
}
}

View file

@ -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]);
}
}
}
}

View file

@ -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;
}
}

View file

@ -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());
}
}

View file

@ -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;
};
}
}

View file

@ -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());
}
}

View file

@ -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
}
}

View file

@ -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");
}
}

View file

@ -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;
}
}

View file

@ -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 ***";
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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();
}
}
}

View file

@ -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);
}
}
}
}
}

View file

@ -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);
}
}
}

View file

@ -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;
}
}
}

View file

@ -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());
}
}
}

View file

@ -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");
}
}
}

View file

@ -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");
}
}
}

View file

@ -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;
}
}

View file

@ -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

View file

@ -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;
}
}
}

View file

@ -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

View file

@ -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 + " ]";
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -1,5 +1,4 @@
dependencies {
api project(':jdbc-connection-pool')
testImplementation project(':jdbc-test')
testImplementation testLibs.derby
testImplementation testLibs.hsqldb

View file

@ -5,7 +5,6 @@ import org.xbib.jdbc.query.flavor.Hsql;
module org.xbib.jdbc.query {
uses Flavor;
requires org.xbib.jdbc.connection.pool;
requires java.sql;
exports org.xbib.jdbc.query;
provides Flavor with Derby, Hsql, H2;

View file

@ -1,7 +1,5 @@
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 javax.sql.DataSource;
@ -9,7 +7,6 @@ import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import java.util.logging.Level;
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 AtomicInteger poolNameCounter = new AtomicInteger(1);
private final DatabaseProviderBuilderImpl builder;
private Connection connection;
@ -53,36 +48,6 @@ public final class DatabaseProvider implements Supplier<Database> {
}, 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
@ -137,31 +102,6 @@ public final class DatabaseProvider implements Supplier<Database> {
}, 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) {
boolean complete = false;
try {

View file

@ -1,5 +1,6 @@
dependencies {
api project(":jdbc-query")
implementation project(":jdbc-connection-pool")
implementation testLibs.junit.jupiter.api
implementation testLibs.hamcrest
}