/*
* Copyright (C) 2013 Brett Wooldridge
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zaxxer.hikari;
import com.zaxxer.hikari.metrics.MetricsTrackerFactory;
import com.zaxxer.hikari.pool.HikariPool;
import com.zaxxer.hikari.pool.HikariPool.PoolInitializationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.sql.DataSource;
import java.io.Closeable;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.concurrent.atomic.AtomicBoolean;
import static com.zaxxer.hikari.pool.HikariPool.POOL_NORMAL;
The HikariCP pooled DataSource.
Author: Brett Wooldridge
/**
* The HikariCP pooled DataSource.
*
* @author Brett Wooldridge
*/
public class HikariDataSource extends HikariConfig implements DataSource, Closeable
{
private static final Logger LOGGER = LoggerFactory.getLogger(HikariDataSource.class);
private final AtomicBoolean isShutdown = new AtomicBoolean();
private final HikariPool fastPathPool;
private volatile HikariPool pool;
Default constructor. Setters are used to configure the pool. Using this constructor vs. HikariDataSource(HikariConfig)
will result in getConnection()
performance that is slightly lower due to lazy initialization checks. The first call to getConnection()
starts the pool. Once the pool is started, the configuration is "sealed" and no further configuration changes are possible -- except via HikariConfigMXBean
methods. /**
* Default constructor. Setters are used to configure the pool. Using
* this constructor vs. {@link #HikariDataSource(HikariConfig)} will
* result in {@link #getConnection()} performance that is slightly lower
* due to lazy initialization checks.
*
* The first call to {@link #getConnection()} starts the pool. Once the pool
* is started, the configuration is "sealed" and no further configuration
* changes are possible -- except via {@link HikariConfigMXBean} methods.
*/
public HikariDataSource()
{
super();
fastPathPool = null;
}
Construct a HikariDataSource with the specified configuration. The HikariConfig
is copied and the pool is started by invoking this constructor. The HikariConfig
can be modified without affecting the HikariDataSource and used to initialize another HikariDataSource instance. Params: - configuration – a HikariConfig instance
/**
* Construct a HikariDataSource with the specified configuration. The
* {@link HikariConfig} is copied and the pool is started by invoking this
* constructor.
*
* The {@link HikariConfig} can be modified without affecting the HikariDataSource
* and used to initialize another HikariDataSource instance.
*
* @param configuration a HikariConfig instance
*/
public HikariDataSource(HikariConfig configuration)
{
configuration.validate();
configuration.copyStateTo(this);
LOGGER.info("{} - Starting...", configuration.getPoolName());
pool = fastPathPool = new HikariPool(this);
LOGGER.info("{} - Start completed.", configuration.getPoolName());
this.seal();
}
// ***********************************************************************
// DataSource methods
// ***********************************************************************
{@inheritDoc} /** {@inheritDoc} */
@Override
public Connection getConnection() throws SQLException
{
if (isClosed()) {
throw new SQLException("HikariDataSource " + this + " has been closed.");
}
if (fastPathPool != null) {
return fastPathPool.getConnection();
}
// See http://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java
HikariPool result = pool;
if (result == null) {
synchronized (this) {
result = pool;
if (result == null) {
validate();
LOGGER.info("{} - Starting...", getPoolName());
try {
pool = result = new HikariPool(this);
this.seal();
}
catch (PoolInitializationException pie) {
if (pie.getCause() instanceof SQLException) {
throw (SQLException) pie.getCause();
}
else {
throw pie;
}
}
LOGGER.info("{} - Start completed.", getPoolName());
}
}
}
return result.getConnection();
}
{@inheritDoc} /** {@inheritDoc} */
@Override
public Connection getConnection(String username, String password) throws SQLException
{
throw new SQLFeatureNotSupportedException();
}
{@inheritDoc} /** {@inheritDoc} */
@Override
public PrintWriter getLogWriter() throws SQLException
{
HikariPool p = pool;
return (p != null ? p.getUnwrappedDataSource().getLogWriter() : null);
}
{@inheritDoc} /** {@inheritDoc} */
@Override
public void setLogWriter(PrintWriter out) throws SQLException
{
HikariPool p = pool;
if (p != null) {
p.getUnwrappedDataSource().setLogWriter(out);
}
}
{@inheritDoc} /** {@inheritDoc} */
@Override
public void setLoginTimeout(int seconds) throws SQLException
{
HikariPool p = pool;
if (p != null) {
p.getUnwrappedDataSource().setLoginTimeout(seconds);
}
}
{@inheritDoc} /** {@inheritDoc} */
@Override
public int getLoginTimeout() throws SQLException
{
HikariPool p = pool;
return (p != null ? p.getUnwrappedDataSource().getLoginTimeout() : 0);
}
{@inheritDoc} /** {@inheritDoc} */
@Override
public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException
{
throw new SQLFeatureNotSupportedException();
}
{@inheritDoc} /** {@inheritDoc} */
@Override
@SuppressWarnings("unchecked")
public <T> T unwrap(Class<T> iface) throws SQLException
{
if (iface.isInstance(this)) {
return (T) this;
}
HikariPool p = pool;
if (p != null) {
final DataSource unwrappedDataSource = p.getUnwrappedDataSource();
if (iface.isInstance(unwrappedDataSource)) {
return (T) unwrappedDataSource;
}
if (unwrappedDataSource != null) {
return unwrappedDataSource.unwrap(iface);
}
}
throw new SQLException("Wrapped DataSource is not an instance of " + iface);
}
{@inheritDoc} /** {@inheritDoc} */
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException
{
if (iface.isInstance(this)) {
return true;
}
HikariPool p = pool;
if (p != null) {
final DataSource unwrappedDataSource = p.getUnwrappedDataSource();
if (iface.isInstance(unwrappedDataSource)) {
return true;
}
if (unwrappedDataSource != null) {
return unwrappedDataSource.isWrapperFor(iface);
}
}
return false;
}
// ***********************************************************************
// HikariConfigMXBean methods
// ***********************************************************************
{@inheritDoc} /** {@inheritDoc} */
@Override
public void setMetricRegistry(Object metricRegistry)
{
boolean isAlreadySet = getMetricRegistry() != null;
super.setMetricRegistry(metricRegistry);
HikariPool p = pool;
if (p != null) {
if (isAlreadySet) {
throw new IllegalStateException("MetricRegistry can only be set one time");
}
else {
p.setMetricRegistry(super.getMetricRegistry());
}
}
}
{@inheritDoc} /** {@inheritDoc} */
@Override
public void setMetricsTrackerFactory(MetricsTrackerFactory metricsTrackerFactory)
{
boolean isAlreadySet = getMetricsTrackerFactory() != null;
super.setMetricsTrackerFactory(metricsTrackerFactory);
HikariPool p = pool;
if (p != null) {
if (isAlreadySet) {
throw new IllegalStateException("MetricsTrackerFactory can only be set one time");
}
else {
p.setMetricsTrackerFactory(super.getMetricsTrackerFactory());
}
}
}
{@inheritDoc} /** {@inheritDoc} */
@Override
public void setHealthCheckRegistry(Object healthCheckRegistry)
{
boolean isAlreadySet = getHealthCheckRegistry() != null;
super.setHealthCheckRegistry(healthCheckRegistry);
HikariPool p = pool;
if (p != null) {
if (isAlreadySet) {
throw new IllegalStateException("HealthCheckRegistry can only be set one time");
}
else {
p.setHealthCheckRegistry(super.getHealthCheckRegistry());
}
}
}
// ***********************************************************************
// HikariCP-specific methods
// ***********************************************************************
Returns true
if the pool as been started and is not suspended or shutdown. Returns: true
if the pool as been started and is not suspended or shutdown.
/**
* Returns {@code true} if the pool as been started and is not suspended or shutdown.
*
* @return {@code true} if the pool as been started and is not suspended or shutdown.
*/
public boolean isRunning()
{
return pool != null && pool.poolState == POOL_NORMAL;
}
Get the HikariPoolMXBean
for this HikariDataSource instance. If this method is called on a HikariDataSource
that has been constructed without a HikariConfig
instance, and before an initial call to #getConnection()
, the return value will be null
. Returns: the HikariPoolMXBean
instance, or null
.
/**
* Get the {@code HikariPoolMXBean} for this HikariDataSource instance. If this method is called on
* a {@code HikariDataSource} that has been constructed without a {@code HikariConfig} instance,
* and before an initial call to {@code #getConnection()}, the return value will be {@code null}.
*
* @return the {@code HikariPoolMXBean} instance, or {@code null}.
*/
public HikariPoolMXBean getHikariPoolMXBean()
{
return pool;
}
Get the HikariConfigMXBean
for this HikariDataSource instance. Returns: the HikariConfigMXBean
instance.
/**
* Get the {@code HikariConfigMXBean} for this HikariDataSource instance.
*
* @return the {@code HikariConfigMXBean} instance.
*/
public HikariConfigMXBean getHikariConfigMXBean()
{
return this;
}
Evict a connection from the pool. If the connection has already been closed (returned to the pool)
this may result in a "soft" eviction; the connection will be evicted sometime in the future if it is
currently in use. If the connection has not been closed, the eviction is immediate.
Params: - connection – the connection to evict from the pool
/**
* Evict a connection from the pool. If the connection has already been closed (returned to the pool)
* this may result in a "soft" eviction; the connection will be evicted sometime in the future if it is
* currently in use. If the connection has not been closed, the eviction is immediate.
*
* @param connection the connection to evict from the pool
*/
public void evictConnection(Connection connection)
{
HikariPool p;
if (!isClosed() && (p = pool) != null && connection.getClass().getName().startsWith("com.zaxxer.hikari")) {
p.evictConnection(connection);
}
}
Shutdown the DataSource and its associated pool.
/**
* Shutdown the DataSource and its associated pool.
*/
@Override
public void close()
{
if (isShutdown.getAndSet(true)) {
return;
}
HikariPool p = pool;
if (p != null) {
try {
LOGGER.info("{} - Shutdown initiated...", getPoolName());
p.shutdown();
LOGGER.info("{} - Shutdown completed.", getPoolName());
}
catch (InterruptedException e) {
LOGGER.warn("{} - Interrupted during closing", getPoolName(), e);
Thread.currentThread().interrupt();
}
}
}
Determine whether the HikariDataSource has been closed.
Returns: true if the HikariDataSource has been closed, false otherwise
/**
* Determine whether the HikariDataSource has been closed.
*
* @return true if the HikariDataSource has been closed, false otherwise
*/
public boolean isClosed()
{
return isShutdown.get();
}
{@inheritDoc} /** {@inheritDoc} */
@Override
public String toString()
{
return "HikariDataSource (" + pool + ")";
}
}