    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018package org.apache.commons.dbcp2;
    019
    020import java.sql.Connection;
    021import java.sql.SQLException;
    022import java.sql.Statement;
    023import java.util.Collection;
    024import java.util.Objects;
    025import java.util.concurrent.atomic.AtomicLong;
    026
    027import javax.management.ObjectName;
    028
    029import org.apache.commons.logging.Log;
    030import org.apache.commons.logging.LogFactory;
    031import org.apache.commons.pool2.KeyedObjectPool;
    032import org.apache.commons.pool2.ObjectPool;
    033import org.apache.commons.pool2.PooledObject;
    034import org.apache.commons.pool2.PooledObjectFactory;
    035import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
    036import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
    037import org.apache.commons.pool2.impl.DefaultPooledObject;
    038
    039/**
    040 * A {@link PooledObjectFactory} that creates {@link PoolableConnection}s.
    041 *
    042 * @since 2.0
    043 */
    044public class PoolableConnectionFactory implements PooledObjectFactory<PoolableConnection> {
    045
    046    private static final Log log = LogFactory.getLog(PoolableConnectionFactory.class);
    047
    048    /**
    049     * Creates a new {@code PoolableConnectionFactory}.
    050     *
    051     * @param connFactory
    052     *            the {@link ConnectionFactory} from which to obtain base {@link Connection}s
    053     * @param dataSourceJmxObjectName
    054     *            The JMX object name, may be null.
    055     */
    056    public PoolableConnectionFactory(final ConnectionFactory connFactory, final ObjectName dataSourceJmxObjectName) {
    057        this.connectionFactory = connFactory;
    058        this.dataSourceJmxObjectName = dataSourceJmxObjectName;
    059    }
    060
    061    /**
    062     * Sets the query I use to {@link #validateObject validate} {@link Connection}s. Should return at least one row. If
    063     * not specified, {@link Connection#isValid(int)} will be used to validate connections.
    064     *
    065     * @param validationQuery
    066     *            a query to use to {@link #validateObject validate} {@link Connection}s.
    067     */
    068    public void setValidationQuery(final String validationQuery) {
    069        this.validationQuery = validationQuery;
    070    }
    071
    072    /**
    073     * Sets the validation query timeout, the amount of time, in seconds, that connection validation will wait for a
    074     * response from the database when executing a validation query. Use a value less than or equal to 0 for no timeout.
    075     *
    076     * @param validationQueryTimeoutSeconds
    077     *            new validation query timeout value in seconds
    078     */
    079    public void setValidationQueryTimeout(final int validationQueryTimeoutSeconds) {
    080        this.validationQueryTimeoutSeconds = validationQueryTimeoutSeconds;
    081    }
    082
    083    /**
    084     * Sets the SQL statements I use to initialize newly created {@link Connection}s. Using {@code null} turns off
    085     * connection initialization.
    086     *
    087     * @param connectionInitSqls
    088     *            SQL statement to initialize {@link Connection}s.
    089     */
    090    public void setConnectionInitSql(final Collection<String> connectionInitSqls) {
    091        this.connectionInitSqls = connectionInitSqls;
    092    }
    093
    094    /**
    095     * Sets the {@link ObjectPool} in which to pool {@link Connection}s.
    096     *
    097     * @param pool
    098     *            the {@link ObjectPool} in which to pool those {@link Connection}s
    099     */
    100    public synchronized void setPool(final ObjectPool<PoolableConnection> pool) {
    101        if (null != this.pool && pool != this.pool) {
    102            try {
    103                this.pool.close();
    104            } catch (final Exception e) {
    105                // ignored !?!
    106            }
    107        }
    108        this.pool = pool;
    109    }
    110
    111    /**
    112     * Returns the {@link ObjectPool} in which {@link Connection}s are pooled.
    113     *
    114     * @return the connection pool
    115     */
    116    public synchronized ObjectPool<PoolableConnection> getPool() {
    117        return pool;
    118    }
    119
    120    /**
    121     * Sets the default "read only" setting for borrowed {@link Connection}s
    122     *
    123     * @param defaultReadOnly
    124     *            the default "read only" setting for borrowed {@link Connection}s
    125     */
    126    public void setDefaultReadOnly(final Boolean defaultReadOnly) {
    127        this.defaultReadOnly = defaultReadOnly;
    128    }
    129
    130    /**
    131     * Sets the default "auto commit" setting for borrowed {@link Connection}s
    132     *
    133     * @param defaultAutoCommit
    134     *            the default "auto commit" setting for borrowed {@link Connection}s
    135     */
    136    public void setDefaultAutoCommit(final Boolean defaultAutoCommit) {
    137        this.defaultAutoCommit = defaultAutoCommit;
    138    }
    139
    140    /**
    141     * Sets the default "Transaction Isolation" setting for borrowed {@link Connection}s
    142     *
    143     * @param defaultTransactionIsolation
    144     *            the default "Transaction Isolation" setting for returned {@link Connection}s
    145     */
    146    public void setDefaultTransactionIsolation(final int defaultTransactionIsolation) {
    147        this.defaultTransactionIsolation = defaultTransactionIsolation;
    148    }
    149
    150    /**
    151     * Sets the default "catalog" setting for borrowed {@link Connection}s
    152     *
    153     * @param defaultCatalog
    154     *            the default "catalog" setting for borrowed {@link Connection}s
    155     */
    156    public void setDefaultCatalog(final String defaultCatalog) {
    157        this.defaultCatalog = defaultCatalog;
    158    }
    159
    160    public void setCacheState(final boolean cacheState) {
    161        this.cacheState = cacheState;
    162    }
    163
    164    public void setPoolStatements(final boolean poolStatements) {
    165        this.poolStatements = poolStatements;
    166    }
    167
    168    /**
    169     * Deprecated due to typo in method name.
    170     *
    171     * @param maxOpenPreparedStatements
    172     *            The maximum number of open prepared statements.
    173     * @deprecated Use {@link #setMaxOpenPreparedStatements(int)}.
    174     */
    175    @Deprecated // Due to typo in method name.
    176    public void setMaxOpenPrepatedStatements(final int maxOpenPreparedStatements) {
    177        setMaxOpenPreparedStatements(maxOpenPreparedStatements);
    178    }
    179
    180    /**
    181     * Sets the maximum number of open prepared statements.
    182     *
    183     * @param maxOpenPreparedStatements
    184     *            The maximum number of open prepared statements.
    185     */
    186    public void setMaxOpenPreparedStatements(final int maxOpenPreparedStatements) {
    187        this.maxOpenPreparedStatements = maxOpenPreparedStatements;
    188    }
    189
    190    /**
    191     * Sets the maximum lifetime in milliseconds of a connection after which the connection will always fail activation,
    192     * passivation and validation. A value of zero or less indicates an infinite lifetime. The default value is -1.
    193     *
    194     * @param maxConnLifetimeMillis
    195     *            The maximum lifetime in milliseconds.
    196     */
    197    public void setMaxConnLifetimeMillis(final long maxConnLifetimeMillis) {
    198        this.maxConnLifetimeMillis = maxConnLifetimeMillis;
    199    }
    200
    201    public boolean isEnableAutoCommitOnReturn() {
    202        return enableAutoCommitOnReturn;
    203    }
    204
    205    public void setEnableAutoCommitOnReturn(final boolean enableAutoCommitOnReturn) {
    206        this.enableAutoCommitOnReturn = enableAutoCommitOnReturn;
    207    }
    208
    209    public boolean isRollbackOnReturn() {
    210        return rollbackOnReturn;
    211    }
    212
    213    public void setRollbackOnReturn(final boolean rollbackOnReturn) {
    214        this.rollbackOnReturn = rollbackOnReturn;
    215    }
    216
    217    public Integer getDefaultQueryTimeout() {
    218        return defaultQueryTimeoutSeconds;
    219    }
    220
    221    public void setDefaultQueryTimeout(final Integer defaultQueryTimeoutSeconds) {
    222        this.defaultQueryTimeoutSeconds = defaultQueryTimeoutSeconds;
    223    }
    224
    225    /**
    226     * SQL_STATE codes considered to signal fatal conditions.
    227     * <p>
    228     * Overrides the defaults in {@link Utils#DISCONNECTION_SQL_CODES} (plus anything starting with
    229     * {@link Utils#DISCONNECTION_SQL_CODE_PREFIX}). If this property is non-null and {@link #isFastFailValidation()} is
    230     * {@code true}, whenever connections created by this factory generate exceptions with SQL_STATE codes in this list,
    231     * they will be marked as "fatally disconnected" and subsequent validations will fail fast (no attempt at isValid or
    232     * validation query).
    233     * </p>
    234     * <p>
    235     * If {@link #isFastFailValidation()} is {@code false} setting this property has no effect.
    236     * </p>
    237     *
    238     * @return SQL_STATE codes overriding defaults
    239     * @since 2.1
    240     */
    241    public Collection<String> getDisconnectionSqlCodes() {
    242        return disconnectionSqlCodes;
    243    }
    244
    245    /**
    246     * @param disconnectionSqlCodes
    247     *            The disconnection SQL codes.
    248     * @see #getDisconnectionSqlCodes()
    249     * @since 2.1
    250     */
    251    public void setDisconnectionSqlCodes(final Collection<String> disconnectionSqlCodes) {
    252        this.disconnectionSqlCodes = disconnectionSqlCodes;
    253    }
    254
    255    /**
    256     * True means that validation will fail immediately for connections that have previously thrown SQLExceptions with
    257     * SQL_STATE indicating fatal disconnection errors.
    258     *
    259     * @return true if connections created by this factory will fast fail validation.
    260     * @see #setDisconnectionSqlCodes(Collection)
    261     * @since 2.1
    262     */
    263    public boolean isFastFailValidation() {
    264        return fastFailValidation;
    265    }
    266
    267    /**
    268     * @see #isFastFailValidation()
    269     * @param fastFailValidation
    270     *            true means connections created by this factory will fast fail validation
    271     * @since 2.1
    272     */
    273    public void setFastFailValidation(final boolean fastFailValidation) {
    274        this.fastFailValidation = fastFailValidation;
    275    }
    276
    277    @Override
    278    public PooledObject<PoolableConnection> makeObject() throws Exception {
    279        Connection conn = connectionFactory.createConnection();
    280        if (conn == null) {
    281            throw new IllegalStateException("Connection factory returned null from createConnection");
    282        }
    283        try {
    284            initializeConnection(conn);
    285        } catch (final SQLException sqle) {
    286            // Make sure the connection is closed
    287            try {
    288                conn.close();
    289            } catch (final SQLException ignore) {
    290                // ignore
    291            }
    292            // Rethrow original exception so it is visible to caller
    293            throw sqle;
    294        }
    295
    296        final long connIndex = connectionIndex.getAndIncrement();
    297
    298        if (poolStatements) {
    299            conn = new PoolingConnection(conn);
    300            final GenericKeyedObjectPoolConfig config = new GenericKeyedObjectPoolConfig();
    301            config.setMaxTotalPerKey(-1);
    302            config.setBlockWhenExhausted(false);
    303            config.setMaxWaitMillis(0);
    304            config.setMaxIdlePerKey(1);
    305            config.setMaxTotal(maxOpenPreparedStatements);
    306            if (dataSourceJmxObjectName != null) {
    307                final StringBuilder base = new StringBuilder(dataSourceJmxObjectName.toString());
    308                base.append(Constants.JMX_CONNECTION_BASE_EXT);
    309                base.append(Long.toString(connIndex));
    310                config.setJmxNameBase(base.toString());
    311                config.setJmxNamePrefix(Constants.JMX_STATEMENT_POOL_PREFIX);
    312            } else {
    313                config.setJmxEnabled(false);
    314            }
    315            final KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> stmtPool = new GenericKeyedObjectPool<>(
    316                    (PoolingConnection) conn, config);
    317            ((PoolingConnection) conn).setStatementPool(stmtPool);
    318            ((PoolingConnection) conn).setCacheState(cacheState);
    319        }
    320
    321        // Register this connection with JMX
    322        ObjectName connJmxName;
    323        if (dataSourceJmxObjectName == null) {
    324            connJmxName = null;
    325        } else {
    326            connJmxName = new ObjectName(
    327                    dataSourceJmxObjectName.toString() + Constants.JMX_CONNECTION_BASE_EXT + connIndex);
    328        }
    329
    330        final PoolableConnection pc = new PoolableConnection(conn, pool, connJmxName, disconnectionSqlCodes,
    331                fastFailValidation);
    332        pc.setCacheState(cacheState);
    333
    334        return new DefaultPooledObject<>(pc);
    335    }
    336
    337    protected void initializeConnection(final Connection conn) throws SQLException {
    338        final Collection<String> sqls = connectionInitSqls;
    339        if (conn.isClosed()) {
    340            throw new SQLException("initializeConnection: connection closed");
    341        }
    342        if (null != sqls) {
    343            try (Statement stmt = conn.createStatement()) {
    344                for (final String sql : sqls) {
    345                    Objects.requireNonNull(sql, "null connectionInitSqls element");
    346                    stmt.execute(sql);
    347                }
    348            }
    349        }
    350    }
    351
    352    @Override
    353    public void destroyObject(final PooledObject<PoolableConnection> p) throws Exception {
    354        p.getObject().reallyClose();
    355    }
    356
    357    @Override
    358    public boolean validateObject(final PooledObject<PoolableConnection> p) {
    359        try {
    360            validateLifetime(p);
    361
    362            validateConnection(p.getObject());
    363            return true;
    364        } catch (final Exception e) {
    365            if (log.isDebugEnabled()) {
    366                log.debug(Utils.getMessage("poolableConnectionFactory.validateObject.fail"), e);
    367            }
    368            return false;
    369        }
    370    }
    371
    372    public void validateConnection(final PoolableConnection conn) throws SQLException {
    373        if (conn.isClosed()) {
    374            throw new SQLException("validateConnection: connection closed");
    375        }
    376        conn.validate(validationQuery, validationQueryTimeoutSeconds);
    377    }
    378
    379    @Override
    380    public void passivateObject(final PooledObject<PoolableConnection> p) throws Exception {
    381
    382        validateLifetime(p);
    383
    384        final PoolableConnection conn = p.getObject();
    385        Boolean connAutoCommit = null;
    386        if (rollbackOnReturn) {
    387            connAutoCommit = Boolean.valueOf(conn.getAutoCommit());
    388            if (!connAutoCommit.booleanValue() && !conn.isReadOnly()) {
    389                conn.rollback();
    390            }
    391        }
    392
    393        conn.clearWarnings();
    394
    395        // DBCP-97 / DBCP-399 / DBCP-351 Idle connections in the pool should
    396        // have autoCommit enabled
    397        if (enableAutoCommitOnReturn) {
    398            if (connAutoCommit == null) {
    399                connAutoCommit = Boolean.valueOf(conn.getAutoCommit());
    400            }
    401            if (!connAutoCommit.booleanValue()) {
    402                conn.setAutoCommit(true);
    403            }
    404        }
    405
    406        conn.passivate();
    407    }
    408
    409    @Override
    410    public void activateObject(final PooledObject<PoolableConnection> p) throws Exception {
    411
    412        validateLifetime(p);
    413
    414        final PoolableConnection conn = p.getObject();
    415        conn.activate();
    416
    417        if (defaultAutoCommit != null && conn.getAutoCommit() != defaultAutoCommit.booleanValue()) {
    418            conn.setAutoCommit(defaultAutoCommit.booleanValue());
    419        }
    420        if (defaultTransactionIsolation != UNKNOWN_TRANSACTIONISOLATION
    421                && conn.getTransactionIsolation() != defaultTransactionIsolation) {
    422            conn.setTransactionIsolation(defaultTransactionIsolation);
    423        }
    424        if (defaultReadOnly != null && conn.isReadOnly() != defaultReadOnly.booleanValue()) {
    425            conn.setReadOnly(defaultReadOnly.booleanValue());
    426        }
    427        if (defaultCatalog != null && !defaultCatalog.equals(conn.getCatalog())) {
    428            conn.setCatalog(defaultCatalog);
    429        }
    430        conn.setDefaultQueryTimeout(defaultQueryTimeoutSeconds);
    431    }
    432
    433    private void validateLifetime(final PooledObject<PoolableConnection> p) throws Exception {
    434        if (maxConnLifetimeMillis > 0) {
    435            final long lifetime = System.currentTimeMillis() - p.getCreateTime();
    436            if (lifetime > maxConnLifetimeMillis) {
    437                throw new LifetimeExceededException(Utils.getMessage("connectionFactory.lifetimeExceeded",
    438                        Long.valueOf(lifetime), Long.valueOf(maxConnLifetimeMillis)));
    439            }
    440        }
    441    }
    442
    443    protected ConnectionFactory getConnectionFactory() {
    444        return connectionFactory;
    445    }
    446
    447    protected boolean getPoolStatements() {
    448        return poolStatements;
    449    }
    450
    451    protected int getMaxOpenPreparedStatements() {
    452        return maxOpenPreparedStatements;
    453    }
    454
    455    protected boolean getCacheState() {
    456        return cacheState;
    457    }
    458
    459    protected ObjectName getDataSourceJmxName() {
    460        return dataSourceJmxObjectName;
    461    }
    462
    463    protected AtomicLong getConnectionIndex() {
    464        return connectionIndex;
    465    }
    466
    467    private final ConnectionFactory connectionFactory;
    468    private final ObjectName dataSourceJmxObjectName;
    469    private volatile String validationQuery;
    470    private volatile int validationQueryTimeoutSeconds = -1;
    471    private Collection<String> connectionInitSqls;
    472    private Collection<String> disconnectionSqlCodes;
    473    private boolean fastFailValidation;
    474    private volatile ObjectPool<PoolableConnection> pool;
    475    private Boolean defaultReadOnly;
    476    private Boolean defaultAutoCommit;
    477    private boolean enableAutoCommitOnReturn = true;
    478    private boolean rollbackOnReturn = true;
    479    private int defaultTransactionIsolation = UNKNOWN_TRANSACTIONISOLATION;
    480    private String defaultCatalog;
    481    private boolean cacheState;
    482    private boolean poolStatements;
    483    private int maxOpenPreparedStatements = GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL_PER_KEY;
    484    private long maxConnLifetimeMillis = -1;
    485    private final AtomicLong connectionIndex = new AtomicLong(0);
    486    private Integer defaultQueryTimeoutSeconds;
    487
    488    /**
    489     * Internal constant to indicate the level is not set.
    490     */
    491    static final int UNKNOWN_TRANSACTIONISOLATION = -1;
    492}