package com.vladmihalcea.flexypool; import com.vladmihalcea.flexypool.adaptor.DataSourcePoolAdapter; import com.vladmihalcea.flexypool.adaptor.PoolAdapter; import com.vladmihalcea.flexypool.adaptor.PoolAdapterFactory; import com.vladmihalcea.flexypool.config.Configuration; import com.vladmihalcea.flexypool.config.PropertyLoader; import com.vladmihalcea.flexypool.connection.ConnectionPoolCallback; import com.vladmihalcea.flexypool.connection.ConnectionProxyFactory; import com.vladmihalcea.flexypool.connection.ConnectionRequestContext; import com.vladmihalcea.flexypool.connection.Credentials; import com.vladmihalcea.flexypool.event.ConnectionAcquireTimeThresholdExceededEvent; import com.vladmihalcea.flexypool.event.ConnectionLeaseTimeThresholdExceededEvent; import com.vladmihalcea.flexypool.event.EventListenerResolver; import com.vladmihalcea.flexypool.event.EventPublisher; import com.vladmihalcea.flexypool.exception.AcquireTimeoutException; import com.vladmihalcea.flexypool.exception.CantAcquireConnectionException; import com.vladmihalcea.flexypool.lifecycle.LifeCycleCallback; import com.vladmihalcea.flexypool.metric.Histogram; import com.vladmihalcea.flexypool.metric.Metrics; import com.vladmihalcea.flexypool.metric.MetricsFactory; import com.vladmihalcea.flexypool.metric.Timer; import com.vladmihalcea.flexypool.strategy.ConnectionAcquiringStrategy; import com.vladmihalcea.flexypool.strategy.ConnectionAcquiringStrategyFactory; import org.slf4j.LoggerFactory; import javax.sql.DataSource; import java.io.PrintWriter; import java.sql.Connection; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Logger; /** * <code>FlexyPoolDataSource</code> is a {@link DataSource} wrapper that allows multiple * {@link ConnectionAcquiringStrategy} to be applied when trying to acquireConnection a database {@link java.sql.Connection}. * This is how you'd configure it suing Spring JavaConfig: * <p> * <pre> * * {@code @Autowired} private PoolingDataSource poolingDataSource; * * {@code @Bean} public Configuration configuration() { * return new Configuration.Factory<PoolingDataSource>( * UUID.randomUUID().toString(), * poolingDataSource, * BitronixPoolAdapter.FACTORY * ).build(); * } * * {@code @Bean} public FlexyPoolDataSource dataSource() { * Configuration configuration = configuration(); * return new FlexyPoolDataSource(configuration, * new IncrementPoolOnTimeoutConnectionAcquiringStrategy.Factory(5), * new RetryConnectionAcquiringStrategy.Factory(2) * ); * } * </pre> * * @author Vlad Mihalcea * @since 1.0 */ public class FlexyPoolDataSource<T extends DataSource> implements DataSource, LifeCycleCallback, ConnectionPoolCallback { private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(FlexyPoolDataSource.class); private static class FlexyPoolDataSourceConfiguration<D extends DataSource> { private final Configuration<D> configuration; private final List<ConnectionAcquiringStrategyFactory<? extends ConnectionAcquiringStrategy, D>> connectionAcquiringStrategyFactories; public FlexyPoolDataSourceConfiguration( Configuration<D> configuration, List<ConnectionAcquiringStrategyFactory<? extends ConnectionAcquiringStrategy, D>> connectionAcquiringStrategyFactories) { this.configuration = configuration; this.connectionAcquiringStrategyFactories = connectionAcquiringStrategyFactories; } public Configuration<D> getConfiguration() { return configuration; } public List<ConnectionAcquiringStrategyFactory<? extends ConnectionAcquiringStrategy, D>> getConnectionAcquiringStrategyFactories() { return connectionAcquiringStrategyFactories; } } private static class ConfigurationLoader<D extends DataSource> { private final PropertyLoader propertyLoader = new PropertyLoader(); private final FlexyPoolDataSourceConfiguration<D> flexyPoolDataSourceConfiguration; @SuppressWarnings("unchecked") public ConfigurationLoader() { D dataSource = propertyLoader.getDataSource(); flexyPoolDataSourceConfiguration = init(dataSource); } public ConfigurationLoader(D dataSource) { flexyPoolDataSourceConfiguration = init(dataSource); } private FlexyPoolDataSourceConfiguration<D> init(D dataSource) { return new FlexyPoolDataSourceConfiguration<D>( configuration(dataSource), connectionAcquiringStrategyFactories() ); } @SuppressWarnings("unchecked") private Configuration<D> configuration(D dataSource) { String uniqueName = propertyLoader.getUniqueName(); PoolAdapterFactory<D> poolAdapterFactory = propertyLoader.getPoolAdapterFactory(); MetricsFactory metricsFactory = propertyLoader.getMetricsFactory(); ConnectionProxyFactory connectionProxyFactory = propertyLoader.getConnectionProxyFactory(); Integer metricLogReporterMillis = propertyLoader.getMetricLogReporterMillis(); Boolean jmxEnabled = propertyLoader.isJmxEnabled(); Boolean jmxAutoStart = propertyLoader.isJmxAutoStart(); EventListenerResolver eventListenerResolver = propertyLoader.getEventListenerResolver(); Long connectionAcquireTimeThresholdMillis = propertyLoader.getConnectionAcquireTimeThresholdMillis(); Long connectionLeaseTimeThresholdMillis = propertyLoader.getConnectionLeaseTimeThresholdMillis(); if (poolAdapterFactory == null) { poolAdapterFactory = (PoolAdapterFactory<D>) DataSourcePoolAdapter.FACTORY; } Configuration.Builder<D> configurationBuilder = new Configuration.Builder<D>( uniqueName, dataSource, poolAdapterFactory ); if (metricsFactory != null) { configurationBuilder.setMetricsFactory(metricsFactory); } if (connectionProxyFactory != null) { configurationBuilder.setConnectionProxyFactory(connectionProxyFactory); } if (metricLogReporterMillis != null) { configurationBuilder.setMetricLogReporterMillis(metricLogReporterMillis); } if (jmxEnabled != null) { configurationBuilder.setJmxEnabled(jmxEnabled); } if (jmxAutoStart != null) { configurationBuilder.setJmxAutoStart(jmxAutoStart); } if (eventListenerResolver != null) { configurationBuilder.setEventListenerResolver(eventListenerResolver); } if (connectionAcquireTimeThresholdMillis != null) { configurationBuilder.setConnectionAcquireTimeThresholdMillis(connectionAcquireTimeThresholdMillis); } if (connectionLeaseTimeThresholdMillis != null) { configurationBuilder.setConnectionLeaseTimeThresholdMillis(connectionLeaseTimeThresholdMillis); } return configurationBuilder.build(); } private List<ConnectionAcquiringStrategyFactory<? extends ConnectionAcquiringStrategy, D>> connectionAcquiringStrategyFactories() { return propertyLoader.getConnectionAcquiringStrategyFactories(); } public FlexyPoolDataSourceConfiguration<D> getFlexyPoolDataSourceConfiguration() { return flexyPoolDataSourceConfiguration; } } public static final String OVERALL_CONNECTION_ACQUIRE_MILLIS = "overallConnectionAcquireMillis"; public static final String CONCURRENT_CONNECTIONS_HISTOGRAM = "concurrentConnectionsHistogram"; public static final String CONCURRENT_CONNECTION_REQUESTS_HISTOGRAM = "concurrentConnectionRequestsHistogram"; public static final String CONNECTION_LEASE_MILLIS = "connectionLeaseMillis"; private final String uniqueName; private final PoolAdapter<T> poolAdapter; private final T targetDataSource; private final Metrics metrics; private final Timer connectionAcquireTotalTimer; private final Histogram concurrentConnectionCountHistogram; private final Histogram concurrentConnectionRequestCountHistogram; private final Timer connectionLeaseTimer; private final ConnectionProxyFactory connectionProxyFactory; private final Collection<ConnectionAcquiringStrategy> connectionAcquiringStrategies = new LinkedHashSet<ConnectionAcquiringStrategy>(); private AtomicLong concurrentConnectionCount = new AtomicLong(); private AtomicLong concurrentConnectionRequestCount = new AtomicLong(); private final EventPublisher eventPublisher; private final long connectionAcquireTimeThresholdMillis; private final long connectionLeaseTimeThresholdMillis; /** * Initialize <code>FlexyPoolDataSource</code> from {@link Configuration} and the array of {@link ConnectionAcquiringStrategyFactory} * * @param configuration configuration * @param connectionAcquiringStrategyFactories array of {@link ConnectionAcquiringStrategyFactory} */ public FlexyPoolDataSource(final Configuration<T> configuration, ConnectionAcquiringStrategyFactory<? extends ConnectionAcquiringStrategy, T>... connectionAcquiringStrategyFactories) { this(configuration, Arrays.asList(connectionAcquiringStrategyFactories)); } /** * Initialize <code>FlexyPoolDataSource</code> from declarative properties configuration. */ public FlexyPoolDataSource() { this(new ConfigurationLoader<T>().getFlexyPoolDataSourceConfiguration()); } /** * Initialize <code>FlexyPoolDataSource</code> from declarative properties configuration and using the given * target {@link DataSource} * * @param targetDataSource target {@link DataSource} */ public FlexyPoolDataSource(T targetDataSource) { this(new ConfigurationLoader<T>(targetDataSource).getFlexyPoolDataSourceConfiguration()); } /** * Initialize <code>FlexyPoolDataSource</code> from {@link Configuration} and the associated list of {@link ConnectionAcquiringStrategyFactory} * * @param configuration configuration * @param connectionAcquiringStrategyFactories list of {@link ConnectionAcquiringStrategyFactory} */ private FlexyPoolDataSource(final Configuration<T> configuration, List<ConnectionAcquiringStrategyFactory<? extends ConnectionAcquiringStrategy, T>> connectionAcquiringStrategyFactories) { this.uniqueName = configuration.getUniqueName(); this.poolAdapter = configuration.getPoolAdapter(); this.targetDataSource = poolAdapter.getTargetDataSource(); this.metrics = configuration.getMetrics(); this.connectionAcquireTotalTimer = metrics.timer(OVERALL_CONNECTION_ACQUIRE_MILLIS); this.concurrentConnectionCountHistogram = metrics.histogram(CONCURRENT_CONNECTIONS_HISTOGRAM); this.concurrentConnectionRequestCountHistogram = metrics.histogram(CONCURRENT_CONNECTION_REQUESTS_HISTOGRAM); this.connectionLeaseTimer = metrics.timer(CONNECTION_LEASE_MILLIS); this.connectionProxyFactory = configuration.getConnectionProxyFactory(); if (connectionAcquiringStrategyFactories.isEmpty()) { LOGGER.info("FlexyPool is not using any strategy!"); } for (ConnectionAcquiringStrategyFactory<? extends ConnectionAcquiringStrategy, T> connectionAcquiringStrategyFactory : connectionAcquiringStrategyFactories) { connectionAcquiringStrategies.add(connectionAcquiringStrategyFactory.newInstance(configuration)); } eventPublisher = configuration.getEventPublisher(); connectionAcquireTimeThresholdMillis = configuration.getConnectionAcquireTimeThresholdMillis(); connectionLeaseTimeThresholdMillis = configuration.getConnectionLeaseTimeThresholdMillis(); } /** * Initialize <code>FlexyPoolDataSource</code> from {@link FlexyPoolDataSourceConfiguration} * * @param flexyPoolDataSourceConfiguration configuration */ private FlexyPoolDataSource(FlexyPoolDataSourceConfiguration<T> flexyPoolDataSourceConfiguration) { this(flexyPoolDataSourceConfiguration.getConfiguration(), flexyPoolDataSourceConfiguration.getConnectionAcquiringStrategyFactories()); } /** * {@inheritDoc} */ @Override public Connection getConnection() throws SQLException { return getConnection(new ConnectionRequestContext.Builder() .build()); } /** * {@inheritDoc} */ @Override public Connection getConnection(final String username, final String password) throws SQLException { return getConnection(new ConnectionRequestContext.Builder() .setCredentials(new Credentials(username, password)) .build()); } /** * Try to obtain a connection by going through all available strategies * * @param context context * @return connection * @throws SQLException */ private Connection getConnection(ConnectionRequestContext context) throws SQLException { concurrentConnectionRequestCountHistogram.update(concurrentConnectionRequestCount.incrementAndGet()); long startNanos = System.nanoTime(); try { Connection connection = null; if (!connectionAcquiringStrategies.isEmpty()) { for (ConnectionAcquiringStrategy strategy : connectionAcquiringStrategies) { try { connection = strategy.getConnection(context); break; } catch (AcquireTimeoutException e) { LOGGER.warn("Couldn't retrieve connection from strategy {} with context {}", strategy, context); } } } else { connection = poolAdapter.getConnection(context); } if (connection != null) { return connectionProxyFactory.newInstance(connection, this); } else { throw new CantAcquireConnectionException("Couldn't acquire connection for current strategies: " + connectionAcquiringStrategies); } } finally { long endNanos = System.nanoTime(); long acquireDurationMillis = TimeUnit.NANOSECONDS.toMillis(endNanos - startNanos); connectionAcquireTotalTimer.update(acquireDurationMillis, TimeUnit.MILLISECONDS); concurrentConnectionRequestCountHistogram.update(concurrentConnectionRequestCount.decrementAndGet()); if (acquireDurationMillis > connectionAcquireTimeThresholdMillis) { eventPublisher.publish(new ConnectionAcquireTimeThresholdExceededEvent( uniqueName, connectionAcquireTimeThresholdMillis, acquireDurationMillis )); LOGGER.info("Connection acquired in {} millis, while threshold is set to {} in {} FlexyPoolDataSource", acquireDurationMillis, connectionAcquireTimeThresholdMillis, uniqueName); } } } /** * {@inheritDoc} */ @Override public void acquireConnection() { concurrentConnectionCountHistogram.update(concurrentConnectionCount.incrementAndGet()); } /** * {@inheritDoc} */ @Override public void releaseConnection(long leaseDurationNanos) { concurrentConnectionCountHistogram.update(concurrentConnectionCount.decrementAndGet()); long leaseDurationMillis = TimeUnit.NANOSECONDS.toMillis(leaseDurationNanos); connectionLeaseTimer.update(leaseDurationMillis, TimeUnit.MILLISECONDS); if (leaseDurationMillis > connectionLeaseTimeThresholdMillis) { eventPublisher.publish(new ConnectionLeaseTimeThresholdExceededEvent( uniqueName, connectionLeaseTimeThresholdMillis, leaseDurationMillis )); LOGGER.info("Connection leased for {} millis, while threshold is set to {} in {} FlexyPoolDataSource", leaseDurationMillis, connectionLeaseTimeThresholdMillis, uniqueName); } } /** * {@inheritDoc} */ @Override public PrintWriter getLogWriter() throws SQLException { return targetDataSource.getLogWriter(); } /** * {@inheritDoc} */ @Override public void setLogWriter(PrintWriter out) throws SQLException { targetDataSource.setLogWriter(out); } /** * {@inheritDoc} */ @Override public int getLoginTimeout() throws SQLException { return targetDataSource.getLoginTimeout(); } /** * {@inheritDoc} */ @Override public void setLoginTimeout(int seconds) throws SQLException { targetDataSource.setLoginTimeout(seconds); } /** * {@inheritDoc} */ @Override public <T> T unwrap(Class<T> iface) throws SQLException { return targetDataSource.unwrap(iface); } /** * {@inheritDoc} */ @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return targetDataSource.isWrapperFor(iface); } /** * JDBC 4.1 method, available to work with both java 1.6 and java 1.7 */ public Logger getParentLogger() throws SQLFeatureNotSupportedException { return Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); } /** * {@inheritDoc} */ @Override public void start() { metrics.start(); } /** * {@inheritDoc} */ @Override public void stop() { metrics.stop(); } }