dropwizard/dropwizard

View on GitHub
dropwizard-db/src/main/java/io/dropwizard/db/DataSourceFactory.java

Summary

Maintainability
D
2 days
Test Coverage
package io.dropwizard.db;

import com.codahale.metrics.MetricRegistry;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.dropwizard.util.Duration;
import io.dropwizard.validation.MinDuration;
import io.dropwizard.validation.ValidationMethod;
import org.apache.tomcat.jdbc.pool.PoolProperties;

import javax.annotation.Nullable;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.sql.Connection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
import java.util.concurrent.TimeUnit;

/**
 * A factory for pooled {@link ManagedDataSource}s.
 * <p/>
 * <b>Configuration Parameters:</b>
 * <table>
 *     <tr>
 *         <td>Name</td>
 *         <td>Default</td>
 *         <td>Description</td>
 *     </tr>
 *     <tr>
 *         <td>{@code url}</td>
 *         <td><b>REQUIRED</b></td>
 *         <td>The URL of the server.</td>
 *     </tr>
 *     <tr>
 *         <td>{@code driverClass}</td>
 *         <td><b>none</b></td>
 *         <td>
 *             The fully qualified class name of the JDBC driver class.
 *             Only required if there were no JDBC drivers registered in {@code META-INF/services/java.sql.Driver}.
 *         </td>
 *     </tr>
 *     <tr>
 *         <td>{@code user}</td>
 *         <td><b>none</b></td>
 *         <td>The username used to connect to the server.</td>
 *     </tr>
 *     <tr>
 *         <td>{@code password}</td>
 *         <td>none</td>
 *         <td>The password used to connect to the server.</td>
 *     </tr>
 *     <tr>
 *         <td>{@code removeAbandoned}</td>
 *         <td>{@code false}</td>
 *         <td>
 *             Remove abandoned connections if they exceed the {@code removeAbandonedTimeout}.
 *             If set to {@code true} a connection is considered abandoned and eligible for removal if it has
 *             been in use longer than the {@code removeAbandonedTimeout} and the condition for
 *             {@code abandonWhenPercentageFull} is met.
 *         </td>
 *     </tr>
 *     <tr>
 *         <td>{@code removeAbandonedTimeout}</td>
 *         <td>60 seconds</td>
 *         <td>
 *             The time before a database connection can be considered abandoned.
 *         </td>
 *     </tr>
 *     <tr>
 *         <td>{@code abandonWhenPercentageFull}</td>
 *         <td>0</td>
 *         <td>
 *             Connections that have been abandoned (timed out) won't get closed and reported up
 *             unless the number of connections in use are above the percentage defined by
 *             {@code abandonWhenPercentageFull}. The value should be between 0-100.
 *         </td>
 *     </tr>
 *     <tr>
 *         <td>{@code alternateUsernamesAllowed}</td>
 *         <td>{@code false}</td>
 *         <td>
 *             Set to true if the call
 *             {@link javax.sql.DataSource#getConnection(String, String) getConnection(username,password)}
 *             is allowed. This is used for when the pool is used by an application accessing
 *             multiple schemas. There is a performance impact turning this option on, even when not
 *             used.
 *         </td>
 *     </tr>
 *     <tr>
 *         <td>{@code commitOnReturn}</td>
 *         <td>{@code false}</td>
 *         <td>
 *             Set to true if you want the connection pool to commit any pending transaction when a
 *             connection is returned.
 *         </td>
 *     </tr>
 *     <tr>
 *         <td>{@code rollbackOnReturn}</td>
 *         <td>{@code false}</td>
 *         <td>
 *             Set to true if you want the connection pool to rollback any pending transaction when a
 *             connection is returned.
 *         </td>
 *     </tr>
 *     <tr>
 *         <td>{@code autoCommitByDefault}</td>
 *         <td>JDBC driver's default</td>
 *         <td>The default auto-commit state of the connections.</td>
 *     </tr>
 *     <tr>
 *         <td>{@code readOnlyByDefault}</td>
 *         <td>JDBC driver's default</td>
 *         <td>The default read-only state of the connections.</td>
 *     </tr>
 *     <tr>
 *         <td>{@code properties}</td>
 *         <td>none</td>
 *         <td>Any additional JDBC driver parameters.</td>
 *     </tr>
 *     <tr>
 *         <td>{@code defaultCatalog}</td>
 *         <td>none</td>
 *         <td>The default catalog to use for the connections.</td>
 *     </tr>
 *     <tr>
 *         <td>{@code defaultTransactionIsolation}</td>
 *         <td>JDBC driver default</td>
 *         <td>
 *             The default transaction isolation to use for the connections. Can be one of
 *             {@code none}, {@code default}, {@code read-uncommitted}, {@code read-committed},
 *             {@code repeatable-read}, or {@code serializable}.
 *         </td>
 *     </tr>
 *     <tr>
 *         <td>{@code useFairQueue}</td>
 *         <td>{@code true}</td>
 *         <td>
 *             If {@code true}, calls to {@code getConnection} are handled in a FIFO manner.
 *         </td>
 *     </tr>
 *     <tr>
 *         <td>{@code initialSize}</td>
 *         <td>10</td>
 *         <td>
 *             The initial size of the connection pool. May be zero, which will allow you to start
 *             the connection pool without requiring the DB to be up. In the latter case the {@link #minSize}
 *             must also be set to zero.
 *         </td>
 *     </tr>
 *     <tr>
 *         <td>{@code minSize}</td>
 *         <td>10</td>
 *         <td>
 *             The minimum size of the connection pool.
 *         </td>
 *     </tr>
 *     <tr>
 *         <td>{@code maxSize}</td>
 *         <td>100</td>
 *         <td>
 *             The maximum size of the connection pool.
 *         </td>
 *     </tr>
 *     <tr>
 *         <td>{@code initializationQuery}</td>
 *         <td>none</td>
 *         <td>
 *             A custom query to be run when a connection is first created.
 *         </td>
 *     </tr>
 *     <tr>
 *         <td>{@code logAbandonedConnections}</td>
 *         <td>{@code false}</td>
 *         <td>
 *             If {@code true}, logs stack traces of abandoned connections.
 *         </td>
 *     </tr>
 *     <tr>
 *         <td>{@code logValidationErrors}</td>
 *         <td>{@code false}</td>
 *         <td>
 *             If {@code true}, logs errors when connections fail validation.
 *         </td>
 *     </tr>
 *     <tr>
 *         <td>{@code maxConnectionAge}</td>
 *         <td>none</td>
 *         <td>
 *             If set, connections which have been open for longer than {@code maxConnectionAge} are
 *             closed when returned.
 *         </td>
 *     </tr>
 *     <tr>
 *         <td>{@code maxWaitForConnection}</td>
 *         <td>30 seconds</td>
 *         <td>
 *             If a request for a connection is blocked for longer than this period, an exception
 *             will be thrown.
 *         </td>
 *     </tr>
 *     <tr>
 *         <td>{@code minIdleTime}</td>
 *         <td>1 minute</td>
 *         <td>
 *             The minimum amount of time an connection must sit idle in the pool before it is
 *             eligible for eviction.
 *         </td>
 *     </tr>
 *     <tr>
 *         <td>{@code validationQuery}</td>
 *         <td>{@code SELECT 1}</td>
 *         <td>
 *             The SQL query that will be used to validate connections from this pool before
 *             returning them to the caller or pool. If specified, this query does not have to
 *             return any data, it just can't throw a SQLException.
 *         </td>
 *     </tr>
 *     <tr>
 *         <td>{@code validationQueryTimeout}</td>
 *         <td>none</td>
 *         <td>
 *             The timeout before a connection validation queries fail.
 *         </td>
 *     </tr>
 *     <tr>
 *         <td>{@code checkConnectionWhileIdle}</td>
 *         <td>{@code true}</td>
 *         <td>
 *             Set to true if query validation should take place while the connection is idle.
 *         </td>
 *     </tr>
 *     <tr>
 *         <td>{@code checkConnectionOnBorrow}</td>
 *         <td>{@code false}</td>
 *         <td>
 *             Whether or not connections will be validated before being borrowed from the pool. If
 *             the connection fails to validate, it will be dropped from the pool, and another will
 *             be borrowed.
 *         </td>
 *     </tr>
 *     <tr>
 *         <td>{@code checkConnectionOnConnect}</td>
 *         <td>{@code true}</td>
 *         <td>
 *             Whether or not connections will be validated before being added to the pool. If the
 *             connection fails to validate, it won't be added to the pool.
 *         </td>
 *     </tr>
 *     <tr>
 *         <td>{@code checkConnectionOnReturn}</td>
 *         <td>{@code false}</td>
 *         <td>
 *             Whether or not connections will be validated after being returned to the pool. If
 *             the connection fails to validate, it will be dropped from the pool.
 *         </td>
 *     </tr>
 *     <tr>
 *         <td>{@code autoCommentsEnabled}</td>
 *         <td>{@code true}</td>
 *         <td>
 *             Whether or not ORMs should automatically add comments.
 *         </td>
 *     </tr>
 *     <tr>
 *         <td>{@code evictionInterval}</td>
 *         <td>5 seconds</td>
 *         <td>
 *             The amount of time to sleep between runs of the idle connection validation, abandoned
 *             cleaner and idle pool resizing.
 *         </td>
 *     </tr>
 *     <tr>
 *         <td>{@code validationInterval}</td>
 *         <td>30 seconds</td>
 *         <td>
 *             To avoid excess validation, only run validation once every interval.
 *         </td>
 *     </tr>
 *     <tr>
 *         <td>{@code validatorClassName}</td>
 *         <td>(none)</td>
 *         <td>
 *             Name of a class of a custom {@link org.apache.tomcat.jdbc.pool.Validator}
 *             implementation, which will be used for validating connections.
 *         </td>
 *     </tr>
 *     <tr>
 *         <td>{@code jdbcInterceptors}</td>
 *         <td>(none)</td>
 *         <td>
 *             A semicolon separated list of classnames extending
 *             {@link org.apache.tomcat.jdbc.pool.JdbcInterceptor}
 *         </td>
 *     </tr>
 *     <tr>
 *         <td>{@code ignoreExceptionOnPreLoad}</td>
 *         <td>{@code false}</td>
 *         <td>
 *             Flag whether ignore error of connection creation while initializing the pool. Set to
 *             true if you want to ignore error of connection creation while initializing the pool.
 *             Set to false if you want to fail the initialization of the pool by throwing exception.
 *         </td>
 *     </tr>
 * </table>
 */
public class DataSourceFactory implements PooledDataSourceFactory {

    private static final String DEFAULT_VALIDATION_QUERY = "/* Health Check */ SELECT 1";

    @SuppressWarnings("UnusedDeclaration")
    public enum TransactionIsolation {
        NONE(Connection.TRANSACTION_NONE),
        DEFAULT(org.apache.tomcat.jdbc.pool.DataSourceFactory.UNKNOWN_TRANSACTIONISOLATION),
        READ_UNCOMMITTED(Connection.TRANSACTION_READ_UNCOMMITTED),
        READ_COMMITTED(Connection.TRANSACTION_READ_COMMITTED),
        REPEATABLE_READ(Connection.TRANSACTION_REPEATABLE_READ),
        SERIALIZABLE(Connection.TRANSACTION_SERIALIZABLE);

        private final int value;

        TransactionIsolation(int value) {
            this.value = value;
        }

        public int get() {
            return value;
        }
    }

    @Nullable
    private String driverClass;

    @Min(0)
    @Max(100)
    private int abandonWhenPercentageFull = 0;

    private boolean alternateUsernamesAllowed = false;

    private boolean commitOnReturn = false;

    private boolean rollbackOnReturn = false;

    @Nullable
    private Boolean autoCommitByDefault;

    @Nullable
    private Boolean readOnlyByDefault;

    @Nullable
    private String user;

    @Nullable
    private String password;

    @NotEmpty
    private String url = "";

    @NotNull
    private Map<String, String> properties = new LinkedHashMap<>();

    @Nullable
    private String defaultCatalog;

    @NotNull
    private TransactionIsolation defaultTransactionIsolation = TransactionIsolation.DEFAULT;

    private boolean useFairQueue = true;

    @Min(0)
    private int initialSize = 10;

    @Min(0)
    private int minSize = 10;

    @Min(1)
    private int maxSize = 100;

    @Nullable
    private String initializationQuery;

    private boolean logAbandonedConnections = false;

    private boolean logValidationErrors = false;

    @MinDuration(value = 0, unit = TimeUnit.MILLISECONDS, inclusive = false)
    @Nullable
    private Duration maxConnectionAge;

    @NotNull
    @MinDuration(value = 0, unit = TimeUnit.MILLISECONDS, inclusive = false)
    private Duration maxWaitForConnection = Duration.seconds(30);

    @NotNull
    @MinDuration(value = 0, unit = TimeUnit.MILLISECONDS, inclusive = false)
    private Duration minIdleTime = Duration.minutes(1);

    private Optional<String> validationQuery = Optional.of(DEFAULT_VALIDATION_QUERY);

    @MinDuration(value = 0, unit = TimeUnit.MILLISECONDS, inclusive = false)
    @Nullable
    private Duration validationQueryTimeout;

    private boolean checkConnectionWhileIdle = true;

    private boolean checkConnectionOnBorrow = false;

    private boolean checkConnectionOnConnect = true;

    private boolean checkConnectionOnReturn = false;

    private boolean autoCommentsEnabled = true;

    @NotNull
    @MinDuration(value = 0, unit = TimeUnit.MILLISECONDS, inclusive = false)
    private Duration evictionInterval = Duration.seconds(5);

    @NotNull
    @MinDuration(value = 50, unit = TimeUnit.MILLISECONDS)
    private Duration validationInterval = Duration.seconds(30);

    private Optional<String> validatorClassName = Optional.empty();

    private boolean removeAbandoned = false;

    @NotNull
    @MinDuration(value = 0, unit = TimeUnit.MILLISECONDS, inclusive = false)
    private Duration removeAbandonedTimeout = Duration.seconds(60L);

    private Optional<String> jdbcInterceptors = Optional.empty();

    private boolean ignoreExceptionOnPreLoad = false;

    @JsonProperty
    @Override
    public boolean isAutoCommentsEnabled() {
        return autoCommentsEnabled;
    }

    @JsonProperty
    public void setAutoCommentsEnabled(boolean autoCommentsEnabled) {
        this.autoCommentsEnabled = autoCommentsEnabled;
    }

    @Nullable
    @JsonProperty
    @Override
    public String getDriverClass() {
        return driverClass;
    }

    @JsonProperty
    public void setDriverClass(@Nullable String driverClass) {
        this.driverClass = driverClass;
    }

    @JsonProperty
    @Nullable
    public String getUser() {
        return user;
    }

    @JsonProperty
    public void setUser(@Nullable String user) {
        this.user = user;
    }

    @JsonProperty
    @Nullable
    public String getPassword() {
        return password;
    }

    @JsonProperty
    public void setPassword(@Nullable String password) {
        this.password = password;
    }

    @JsonProperty
    @Override
    public String getUrl() {
        return url;
    }

    @JsonProperty
    public void setUrl(String url) {
        this.url = url;
    }

    @JsonProperty
    @Override
    public Map<String, String> getProperties() {
        return properties;
    }

    @JsonProperty
    public void setProperties(Map<String, String> properties) {
        this.properties = properties;
    }

    @JsonProperty
    public Duration getMaxWaitForConnection() {
        return maxWaitForConnection;
    }

    @JsonProperty
    public void setMaxWaitForConnection(Duration maxWaitForConnection) {
        this.maxWaitForConnection = maxWaitForConnection;
    }

    @Override
    @JsonProperty
    public Optional<String> getValidationQuery() {
        return validationQuery;
    }

    /**
     * @deprecated use {@link #getValidationQuery()} instead
     */
    @Override
    @Deprecated
    @JsonIgnore
    public String getHealthCheckValidationQuery() {
        return getValidationQuery().orElse(DEFAULT_VALIDATION_QUERY);
    }

    @JsonProperty
    public void setValidationQuery(@Nullable String validationQuery) {
        this.validationQuery = Optional.ofNullable(validationQuery);
    }

    @JsonProperty
    public int getMinSize() {
        return minSize;
    }

    @JsonProperty
    public void setMinSize(int minSize) {
        this.minSize = minSize;
    }

    @JsonProperty
    public int getMaxSize() {
        return maxSize;
    }

    @JsonProperty
    public void setMaxSize(int maxSize) {
        this.maxSize = maxSize;
    }

    @JsonProperty
    public boolean getCheckConnectionWhileIdle() {
        return checkConnectionWhileIdle;
    }

    @JsonProperty
    public void setCheckConnectionWhileIdle(boolean checkConnectionWhileIdle) {
        this.checkConnectionWhileIdle = checkConnectionWhileIdle;
    }

    /**
     * @deprecated use {@link #getReadOnlyByDefault()} instead
     */
    @Deprecated
    @JsonProperty
    public boolean isDefaultReadOnly() {
        return Boolean.TRUE.equals(readOnlyByDefault);
    }

    /**
     * @deprecated use {@link #setReadOnlyByDefault(Boolean)} instead
     */
    @Deprecated
    @JsonProperty
    public void setDefaultReadOnly(boolean defaultReadOnly) {
        readOnlyByDefault = defaultReadOnly;
    }

    @JsonIgnore
    @ValidationMethod(message = ".minSize must be less than or equal to maxSize")
    public boolean isMinSizeLessThanMaxSize() {
        return minSize <= maxSize;
    }

    @JsonIgnore
    @ValidationMethod(message = ".initialSize must be less than or equal to maxSize")
    public boolean isInitialSizeLessThanMaxSize() {
        return initialSize <= maxSize;
    }

    @JsonIgnore
    @ValidationMethod(message = ".initialSize must be greater than or equal to minSize")
    public boolean isInitialSizeGreaterThanMinSize() {
        return minSize <= initialSize;
    }

    @JsonProperty
    public int getAbandonWhenPercentageFull() {
        return abandonWhenPercentageFull;
    }

    @JsonProperty
    public void setAbandonWhenPercentageFull(int percentage) {
        this.abandonWhenPercentageFull = percentage;
    }

    @JsonProperty
    public boolean isAlternateUsernamesAllowed() {
        return alternateUsernamesAllowed;
    }

    @JsonProperty
    public void setAlternateUsernamesAllowed(boolean allow) {
        this.alternateUsernamesAllowed = allow;
    }

    @JsonProperty
    public boolean getCommitOnReturn() {
        return commitOnReturn;
    }

    @JsonProperty
    public boolean getRollbackOnReturn() {
        return rollbackOnReturn;
    }

    @JsonProperty
    public void setCommitOnReturn(boolean commitOnReturn) {
        this.commitOnReturn = commitOnReturn;
    }

    @JsonProperty
    public void setRollbackOnReturn(boolean rollbackOnReturn) {
        this.rollbackOnReturn = rollbackOnReturn;
    }

    @JsonProperty
    @Nullable
    public Boolean getAutoCommitByDefault() {
        return autoCommitByDefault;
    }

    @JsonProperty
    public void setAutoCommitByDefault(@Nullable Boolean autoCommit) {
        this.autoCommitByDefault = autoCommit;
    }

    @JsonProperty
    @Nullable
    public String getDefaultCatalog() {
        return defaultCatalog;
    }

    @JsonProperty
    public void setDefaultCatalog(@Nullable String defaultCatalog) {
        this.defaultCatalog = defaultCatalog;
    }

    @JsonProperty
    @Nullable
    public Boolean getReadOnlyByDefault() {
        return readOnlyByDefault;
    }

    @JsonProperty
    public void setReadOnlyByDefault(@Nullable Boolean readOnlyByDefault) {
        this.readOnlyByDefault = readOnlyByDefault;
    }

    @JsonProperty
    public TransactionIsolation getDefaultTransactionIsolation() {
        return defaultTransactionIsolation;
    }

    @JsonProperty
    public void setDefaultTransactionIsolation(TransactionIsolation isolation) {
        this.defaultTransactionIsolation = isolation;
    }

    @JsonProperty
    public boolean getUseFairQueue() {
        return useFairQueue;
    }

    @JsonProperty
    public void setUseFairQueue(boolean fair) {
        this.useFairQueue = fair;
    }

    @JsonProperty
    public int getInitialSize() {
        return initialSize;
    }

    @JsonProperty
    public void setInitialSize(int initialSize) {
        this.initialSize = initialSize;
    }

    @JsonProperty
    @Nullable
    public String getInitializationQuery() {
        return initializationQuery;
    }

    @JsonProperty
    public void setInitializationQuery(@Nullable String query) {
        this.initializationQuery = query;
    }

    @JsonProperty
    public boolean getLogAbandonedConnections() {
        return logAbandonedConnections;
    }

    @JsonProperty
    public void setLogAbandonedConnections(boolean log) {
        this.logAbandonedConnections = log;
    }

    @JsonProperty
    public boolean getLogValidationErrors() {
        return logValidationErrors;
    }

    @JsonProperty
    public void setLogValidationErrors(boolean log) {
        this.logValidationErrors = log;
    }

    @JsonProperty
    public Optional<Duration> getMaxConnectionAge() {
        return Optional.ofNullable(maxConnectionAge);
    }

    @JsonProperty
    public void setMaxConnectionAge(@Nullable Duration age) {
        this.maxConnectionAge = age;
    }

    @JsonProperty
    public Duration getMinIdleTime() {
        return minIdleTime;
    }

    @JsonProperty
    public void setMinIdleTime(Duration time) {
        this.minIdleTime = time;
    }

    @JsonProperty
    public boolean getCheckConnectionOnBorrow() {
        return checkConnectionOnBorrow;
    }

    @JsonProperty
    public void setCheckConnectionOnBorrow(boolean checkConnectionOnBorrow) {
        this.checkConnectionOnBorrow = checkConnectionOnBorrow;
    }

    @JsonProperty
    public boolean getCheckConnectionOnConnect() {
        return checkConnectionOnConnect;
    }

    @JsonProperty
    public void setCheckConnectionOnConnect(boolean checkConnectionOnConnect) {
        this.checkConnectionOnConnect = checkConnectionOnConnect;
    }

    @JsonProperty
    public boolean getCheckConnectionOnReturn() {
        return checkConnectionOnReturn;
    }

    @JsonProperty
    public void setCheckConnectionOnReturn(boolean checkConnectionOnReturn) {
        this.checkConnectionOnReturn = checkConnectionOnReturn;
    }

    @JsonProperty
    public Duration getEvictionInterval() {
        return evictionInterval;
    }

    @JsonProperty
    public void setEvictionInterval(Duration interval) {
        this.evictionInterval = interval;
    }

    @JsonProperty
    public Duration getValidationInterval() {
        return validationInterval;
    }

    @JsonProperty
    public void setValidationInterval(Duration validationInterval) {
        this.validationInterval = validationInterval;
    }

    @Override
    @JsonProperty
    public Optional<Duration> getValidationQueryTimeout() {
        return Optional.ofNullable(validationQueryTimeout);
    }

    @JsonProperty
    public Optional<String> getValidatorClassName() {
        return validatorClassName;
    }

    @JsonProperty
    public void setValidatorClassName(Optional<String> validatorClassName) {
        this.validatorClassName = validatorClassName;
    }

    /**
     * @deprecated use {@link #getValidationQueryTimeout()} instead
     */
    @Override
    @Deprecated
    @JsonIgnore
    public Optional<Duration> getHealthCheckValidationTimeout() {
        return getValidationQueryTimeout();
    }

    @JsonProperty
    public void setValidationQueryTimeout(@Nullable Duration validationQueryTimeout) {
        this.validationQueryTimeout = validationQueryTimeout;
    }

    @JsonProperty
    public boolean isRemoveAbandoned() {
        return removeAbandoned;
    }

    @JsonProperty
    public void setRemoveAbandoned(boolean removeAbandoned) {
        this.removeAbandoned = removeAbandoned;
    }

    @JsonProperty
    public Duration getRemoveAbandonedTimeout() {
        return removeAbandonedTimeout;
    }

    @JsonProperty
    public void setRemoveAbandonedTimeout(Duration removeAbandonedTimeout) {
        this.removeAbandonedTimeout = Objects.requireNonNull(removeAbandonedTimeout);
    }

    @JsonProperty
    public Optional<String> getJdbcInterceptors() {
        return jdbcInterceptors;
    }

    @JsonProperty
    public void setJdbcInterceptors(Optional<String> jdbcInterceptors) {
        this.jdbcInterceptors = jdbcInterceptors;
    }

    @JsonProperty
    public boolean isIgnoreExceptionOnPreLoad() {
        return ignoreExceptionOnPreLoad;
    }

    @JsonProperty
    public void setIgnoreExceptionOnPreLoad(boolean ignoreExceptionOnPreLoad) {
        this.ignoreExceptionOnPreLoad = ignoreExceptionOnPreLoad;
    }

    @Override
    public void asSingleConnectionPool() {
        minSize = 1;
        maxSize = 1;
        initialSize = 1;
    }

    @Override
    public ManagedDataSource build(MetricRegistry metricRegistry, String name) {
        final Properties dbProperties = new Properties();
        properties.forEach(dbProperties::setProperty);

        final PoolProperties poolConfig = new PoolProperties();
        poolConfig.setAbandonWhenPercentageFull(abandonWhenPercentageFull);
        poolConfig.setAlternateUsernameAllowed(alternateUsernamesAllowed);
        poolConfig.setCommitOnReturn(commitOnReturn);
        poolConfig.setRollbackOnReturn(rollbackOnReturn);
        poolConfig.setDbProperties(dbProperties);
        poolConfig.setDefaultAutoCommit(autoCommitByDefault);
        poolConfig.setDefaultCatalog(defaultCatalog);
        poolConfig.setDefaultReadOnly(readOnlyByDefault);
        poolConfig.setDefaultTransactionIsolation(defaultTransactionIsolation.get());
        poolConfig.setDriverClassName(driverClass);
        poolConfig.setFairQueue(useFairQueue);
        poolConfig.setIgnoreExceptionOnPreLoad(ignoreExceptionOnPreLoad);
        poolConfig.setInitialSize(initialSize);
        poolConfig.setInitSQL(initializationQuery);
        poolConfig.setLogAbandoned(logAbandonedConnections);
        poolConfig.setLogValidationErrors(logValidationErrors);
        poolConfig.setMaxActive(maxSize);
        poolConfig.setMaxIdle(maxSize);
        poolConfig.setMinIdle(minSize);

        getMaxConnectionAge().map(Duration::toMilliseconds).ifPresent(poolConfig::setMaxAge);
        poolConfig.setMaxWait((int) maxWaitForConnection.toMilliseconds());
        poolConfig.setMinEvictableIdleTimeMillis((int) minIdleTime.toMilliseconds());
        poolConfig.setName(name);
        poolConfig.setUrl(url);
        poolConfig.setUsername(user);
        poolConfig.setPassword(user != null && password == null ? "" : password);
        poolConfig.setRemoveAbandoned(removeAbandoned);
        poolConfig.setRemoveAbandonedTimeout((int) removeAbandonedTimeout.toSeconds());

        poolConfig.setTestWhileIdle(checkConnectionWhileIdle);
        validationQuery.ifPresent(poolConfig::setValidationQuery);
        poolConfig.setTestOnBorrow(checkConnectionOnBorrow);
        poolConfig.setTestOnConnect(checkConnectionOnConnect);
        poolConfig.setTestOnReturn(checkConnectionOnReturn);
        poolConfig.setTimeBetweenEvictionRunsMillis((int) evictionInterval.toMilliseconds());
        poolConfig.setValidationInterval(validationInterval.toMilliseconds());

        getValidationQueryTimeout().map(x -> (int) x.toSeconds()).ifPresent(poolConfig::setValidationQueryTimeout);
        validatorClassName.ifPresent(poolConfig::setValidatorClassName);
        jdbcInterceptors.ifPresent(poolConfig::setJdbcInterceptors);
        return new ManagedPooledDataSource(poolConfig, metricRegistry);
    }
}