    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     * Internal constant to indicate the level is not set.
    050     */
    051    static final int UNKNOWN_TRANSACTION_ISOLATION = -1;
    052
    053    private final ConnectionFactory connectionFactory;
    054
    055    private final ObjectName dataSourceJmxObjectName;
    056
    057    private volatile String validationQuery;
    058
    059    private volatile int validationQueryTimeoutSeconds = -1;
    060
    061    private Collection<String> connectionInitSqls;
    062
    063    private Collection<String> disconnectionSqlCodes;
    064
    065    private boolean fastFailValidation = true;
    066
    067    private volatile ObjectPool<PoolableConnection> pool;
    068
    069    private Boolean defaultReadOnly;
    070
    071    private Boolean defaultAutoCommit;
    072
    073    private boolean autoCommitOnReturn = true;
    074
    075    private boolean rollbackOnReturn = true;
    076
    077    private int defaultTransactionIsolation = UNKNOWN_TRANSACTION_ISOLATION;
    078
    079    private String defaultCatalog;
    080
    081    private String defaultSchema;
    082
    083    private boolean cacheState;
    084
    085    private boolean poolStatements;
    086
    087    private int maxOpenPreparedStatements = GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL_PER_KEY;
    088
    089    private long maxConnLifetimeMillis = -1;
    090
    091    private final AtomicLong connectionIndex = new AtomicLong(0);
    092
    093    private Integer defaultQueryTimeoutSeconds;
    094
    095    /**
    096     * Creates a new {@code PoolableConnectionFactory}.
    097     *
    098     * @param connFactory
    099     *            the {@link ConnectionFactory} from which to obtain base {@link Connection}s
    100     * @param dataSourceJmxObjectName
    101     *            The JMX object name, may be null.
    102     */
    103    public PoolableConnectionFactory(final ConnectionFactory connFactory, final ObjectName dataSourceJmxObjectName) {
    104        this.connectionFactory = connFactory;
    105        this.dataSourceJmxObjectName = dataSourceJmxObjectName;
    106    }
    107
    108    @Override
    109    public void activateObject(final PooledObject<PoolableConnection> p) throws Exception {
    110
    111        validateLifetime(p);
    112
    113        final PoolableConnection conn = p.getObject();
    114        conn.activate();
    115
    116        if (defaultAutoCommit != null && conn.getAutoCommit() != defaultAutoCommit.booleanValue()) {
    117            conn.setAutoCommit(defaultAutoCommit.booleanValue());
    118        }
    119        if (defaultTransactionIsolation != UNKNOWN_TRANSACTION_ISOLATION
    120                && conn.getTransactionIsolation() != defaultTransactionIsolation) {
    121            conn.setTransactionIsolation(defaultTransactionIsolation);
    122        }
    123        if (defaultReadOnly != null && conn.isReadOnly() != defaultReadOnly.booleanValue()) {
    124            conn.setReadOnly(defaultReadOnly.booleanValue());
    125        }
    126        if (defaultCatalog != null && !defaultCatalog.equals(conn.getCatalog())) {
    127            conn.setCatalog(defaultCatalog);
    128        }
    129        if (defaultSchema != null && !defaultSchema.equals(Jdbc41Bridge.getSchema(conn))) {
    130            Jdbc41Bridge.setSchema(conn, defaultSchema);
    131        }
    132        conn.setDefaultQueryTimeout(defaultQueryTimeoutSeconds);
    133    }
    134
    135    @Override
    136    public void destroyObject(final PooledObject<PoolableConnection> p) throws Exception {
    137        p.getObject().reallyClose();
    138    }
    139
    140    /**
    141     * @return The cache state.
    142     * @since Made public in 2.6.0.
    143     */
    144    public boolean getCacheState() {
    145        return cacheState;
    146    }
    147
    148    /**
    149     * @return The connection factory.
    150     * @since Made public in 2.6.0.
    151     */
    152    public ConnectionFactory getConnectionFactory() {
    153        return connectionFactory;
    154    }
    155
    156    protected AtomicLong getConnectionIndex() {
    157        return connectionIndex;
    158    }
    159
    160    /**
    161     * @return The collection of initialization SQL statements.
    162     * @since 2.6.0
    163     */
    164    public Collection<String> getConnectionInitSqls() {
    165        return connectionInitSqls;
    166    }
    167
    168    /**
    169     * @return The data source JMX ObjectName
    170     * @since Made public in 2.6.0.
    171     */
    172    public ObjectName getDataSourceJmxName() {
    173        return dataSourceJmxObjectName;
    174    }
    175
    176    /**
    177     * @return The data source JMS ObjectName.
    178     * @since 2.6.0
    179     */
    180    public ObjectName getDataSourceJmxObjectName() {
    181        return dataSourceJmxObjectName;
    182    }
    183
    184    /**
    185     * @return Default auto-commit value.
    186     * @since 2.6.0
    187     */
    188    public Boolean getDefaultAutoCommit() {
    189        return defaultAutoCommit;
    190    }
    191
    192    /**
    193     * @return Default catalog.
    194     * @since 2.6.0
    195     */
    196    public String getDefaultCatalog() {
    197        return defaultCatalog;
    198    }
    199
    200    /**
    201     * @return Default query timeout in seconds.
    202     */
    203    public Integer getDefaultQueryTimeout() {
    204        return defaultQueryTimeoutSeconds;
    205    }
    206
    207    /**
    208     * @return Default query timeout in seconds.
    209     * @since 2.6.0
    210     */
    211    public Integer getDefaultQueryTimeoutSeconds() {
    212        return defaultQueryTimeoutSeconds;
    213    }
    214
    215    /**
    216     * @return Default read-only-value.
    217     * @since 2.6.0
    218     */
    219    public Boolean getDefaultReadOnly() {
    220        return defaultReadOnly;
    221    }
    222
    223    /**
    224     * @return Default schema.
    225     * @since 2.6.0
    226     */
    227    public String getDefaultSchema() {
    228        return defaultSchema;
    229    }
    230
    231    /**
    232     * @return Default transaction isolation.
    233     * @since 2.6.0
    234     */
    235    public int getDefaultTransactionIsolation() {
    236        return defaultTransactionIsolation;
    237    }
    238
    239    /**
    240     * SQL_STATE codes considered to signal fatal conditions.
    241     * <p>
    242     * Overrides the defaults in {@link Utils#DISCONNECTION_SQL_CODES} (plus anything starting with
    243     * {@link Utils#DISCONNECTION_SQL_CODE_PREFIX}). If this property is non-null and {@link #isFastFailValidation()} is
    244     * {@code true}, whenever connections created by this factory generate exceptions with SQL_STATE codes in this list,
    245     * they will be marked as "fatally disconnected" and subsequent validations will fail fast (no attempt at isValid or
    246     * validation query).
    247     * </p>
    248     * <p>
    249     * If {@link #isFastFailValidation()} is {@code false} setting this property has no effect.
    250     * </p>
    251     *
    252     * @return SQL_STATE codes overriding defaults
    253     * @since 2.1
    254     */
    255    public Collection<String> getDisconnectionSqlCodes() {
    256        return disconnectionSqlCodes;
    257    }
    258
    259    /**
    260     * @return Maximum connection lifetime in milliseconds.
    261     * @since 2.6.0
    262     */
    263    public long getMaxConnLifetimeMillis() {
    264        return maxConnLifetimeMillis;
    265    }
    266
    267    protected int getMaxOpenPreparedStatements() {
    268        return maxOpenPreparedStatements;
    269    }
    270
    271    /**
    272     * Returns the {@link ObjectPool} in which {@link Connection}s are pooled.
    273     *
    274     * @return the connection pool
    275     */
    276    public synchronized ObjectPool<PoolableConnection> getPool() {
    277        return pool;
    278    }
    279
    280    /**
    281     * @return Whether to pool statements.
    282     * @since Made public in 2.6.0.
    283     */
    284    public boolean getPoolStatements() {
    285        return poolStatements;
    286    }
    287    /**
    288     * @return Validation query.
    289     * @since 2.6.0
    290     */
    291    public String getValidationQuery() {
    292        return validationQuery;
    293    }
    294    /**
    295     * @return Validation query timeout in seconds.
    296     * @since 2.6.0
    297     */
    298    public int getValidationQueryTimeoutSeconds() {
    299        return validationQueryTimeoutSeconds;
    300    }
    301    protected void initializeConnection(final Connection conn) throws SQLException {
    302        final Collection<String> sqls = connectionInitSqls;
    303        if (conn.isClosed()) {
    304            throw new SQLException("initializeConnection: connection closed");
    305        }
    306        if (null != sqls) {
    307            try (Statement stmt = conn.createStatement()) {
    308                for (final String sql : sqls) {
    309                    Objects.requireNonNull(sql, "null connectionInitSqls element");
    310                    stmt.execute(sql);
    311                }
    312            }
    313        }
    314    }
    315
    316    /**
    317     * @return Whether to auto-commit on return.
    318     * @since 2.6.0
    319     */
    320    public boolean isAutoCommitOnReturn() {
    321        return autoCommitOnReturn;
    322    }
    323
    324    /**
    325     * @return Whether to auto-commit on return.
    326     * @deprecated Use {@link #isAutoCommitOnReturn()}.
    327     */
    328    @Deprecated
    329    public boolean isEnableAutoCommitOnReturn() {
    330        return autoCommitOnReturn;
    331    }
    332
    333    /**
    334     * True means that validation will fail immediately for connections that have previously thrown SQLExceptions with
    335     * SQL_STATE indicating fatal disconnection errors.
    336     *
    337     * @return true if connections created by this factory will fast fail validation.
    338     * @see #setDisconnectionSqlCodes(Collection)
    339     * @since 2.1
    340     * @since 2.5.0 Defaults to true, previous versions defaulted to false.
    341     */
    342    public boolean isFastFailValidation() {
    343        return fastFailValidation;
    344    }
    345
    346    /**
    347     * @return Whether to rollback on return.
    348     */
    349    public boolean isRollbackOnReturn() {
    350        return rollbackOnReturn;
    351    }
    352
    353    @Override
    354    public PooledObject<PoolableConnection> makeObject() throws Exception {
    355        Connection conn = connectionFactory.createConnection();
    356        if (conn == null) {
    357            throw new IllegalStateException("Connection factory returned null from createConnection");
    358        }
    359        try {
    360            initializeConnection(conn);
    361        } catch (final SQLException sqle) {
    362            // Make sure the connection is closed
    363            try {
    364                conn.close();
    365            } catch (final SQLException ignore) {
    366                // ignore
    367            }
    368            // Rethrow original exception so it is visible to caller
    369            throw sqle;
    370        }
    371
    372        final long connIndex = connectionIndex.getAndIncrement();
    373
    374        if (poolStatements) {
    375            conn = new PoolingConnection(conn);
    376            final GenericKeyedObjectPoolConfig<DelegatingPreparedStatement> config = new GenericKeyedObjectPoolConfig<>();
    377            config.setMaxTotalPerKey(-1);
    378            config.setBlockWhenExhausted(false);
    379            config.setMaxWaitMillis(0);
    380            config.setMaxIdlePerKey(1);
    381            config.setMaxTotal(maxOpenPreparedStatements);
    382            if (dataSourceJmxObjectName != null) {
    383                final StringBuilder base = new StringBuilder(dataSourceJmxObjectName.toString());
    384                base.append(Constants.JMX_CONNECTION_BASE_EXT);
    385                base.append(Long.toString(connIndex));
    386                config.setJmxNameBase(base.toString());
    387                config.setJmxNamePrefix(Constants.JMX_STATEMENT_POOL_PREFIX);
    388            } else {
    389                config.setJmxEnabled(false);
    390            }
    391            final PoolingConnection poolingConn = (PoolingConnection) conn;
    392            final KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> stmtPool = new GenericKeyedObjectPool<>(
    393                    poolingConn, config);
    394            poolingConn.setStatementPool(stmtPool);
    395            poolingConn.setCacheState(cacheState);
    396        }
    397
    398        // Register this connection with JMX
    399        ObjectName connJmxName;
    400        if (dataSourceJmxObjectName == null) {
    401            connJmxName = null;
    402        } else {
    403            connJmxName = new ObjectName(
    404                    dataSourceJmxObjectName.toString() + Constants.JMX_CONNECTION_BASE_EXT + connIndex);
    405        }
    406
    407        final PoolableConnection pc = new PoolableConnection(conn, pool, connJmxName, disconnectionSqlCodes,
    408                fastFailValidation);
    409        pc.setCacheState(cacheState);
    410
    411        return new DefaultPooledObject<>(pc);
    412    }
    413
    414    @Override
    415    public void passivateObject(final PooledObject<PoolableConnection> p) throws Exception {
    416
    417        validateLifetime(p);
    418
    419        final PoolableConnection conn = p.getObject();
    420        Boolean connAutoCommit = null;
    421        if (rollbackOnReturn) {
    422            connAutoCommit = Boolean.valueOf(conn.getAutoCommit());
    423            if (!connAutoCommit.booleanValue() && !conn.isReadOnly()) {
    424                conn.rollback();
    425            }
    426        }
    427
    428        conn.clearWarnings();
    429
    430        // DBCP-97 / DBCP-399 / DBCP-351 Idle connections in the pool should
    431        // have autoCommit enabled
    432        if (autoCommitOnReturn) {
    433            if (connAutoCommit == null) {
    434                connAutoCommit = Boolean.valueOf(conn.getAutoCommit());
    435            }
    436            if (!connAutoCommit.booleanValue()) {
    437                conn.setAutoCommit(true);
    438            }
    439        }
    440
    441        conn.passivate();
    442    }
    443
    444    public void setAutoCommitOnReturn(final boolean autoCommitOnReturn) {
    445        this.autoCommitOnReturn = autoCommitOnReturn;
    446    }
    447
    448    public void setCacheState(final boolean cacheState) {
    449        this.cacheState = cacheState;
    450    }
    451
    452    /**
    453     * Sets the SQL statements I use to initialize newly created {@link Connection}s. Using {@code null} turns off
    454     * connection initialization.
    455     *
    456     * @param connectionInitSqls
    457     *            SQL statement to initialize {@link Connection}s.
    458     */
    459    public void setConnectionInitSql(final Collection<String> connectionInitSqls) {
    460        this.connectionInitSqls = connectionInitSqls;
    461    }
    462
    463    /**
    464     * Sets the default "auto commit" setting for borrowed {@link Connection}s
    465     *
    466     * @param defaultAutoCommit
    467     *            the default "auto commit" setting for borrowed {@link Connection}s
    468     */
    469    public void setDefaultAutoCommit(final Boolean defaultAutoCommit) {
    470        this.defaultAutoCommit = defaultAutoCommit;
    471    }
    472
    473    /**
    474     * Sets the default "catalog" setting for borrowed {@link Connection}s
    475     *
    476     * @param defaultCatalog
    477     *            the default "catalog" setting for borrowed {@link Connection}s
    478     */
    479    public void setDefaultCatalog(final String defaultCatalog) {
    480        this.defaultCatalog = defaultCatalog;
    481    }
    482
    483    public void setDefaultQueryTimeout(final Integer defaultQueryTimeoutSeconds) {
    484        this.defaultQueryTimeoutSeconds = defaultQueryTimeoutSeconds;
    485    }
    486    /**
    487     * Sets the default "read only" setting for borrowed {@link Connection}s
    488     *
    489     * @param defaultReadOnly
    490     *            the default "read only" setting for borrowed {@link Connection}s
    491     */
    492    public void setDefaultReadOnly(final Boolean defaultReadOnly) {
    493        this.defaultReadOnly = defaultReadOnly;
    494    }
    495
    496    /**
    497     * Sets the default "schema" setting for borrowed {@link Connection}s
    498     *
    499     * @param defaultSchema
    500     *            the default "schema" setting for borrowed {@link Connection}s
    501     * @since 2.5.0
    502     */
    503    public void setDefaultSchema(final String defaultSchema) {
    504        this.defaultSchema = defaultSchema;
    505    }
    506
    507    /**
    508     * Sets the default "Transaction Isolation" setting for borrowed {@link Connection}s
    509     *
    510     * @param defaultTransactionIsolation
    511     *            the default "Transaction Isolation" setting for returned {@link Connection}s
    512     */
    513    public void setDefaultTransactionIsolation(final int defaultTransactionIsolation) {
    514        this.defaultTransactionIsolation = defaultTransactionIsolation;
    515    }
    516
    517    /**
    518     * @param disconnectionSqlCodes
    519     *            The disconnection SQL codes.
    520     * @see #getDisconnectionSqlCodes()
    521     * @since 2.1
    522     */
    523    public void setDisconnectionSqlCodes(final Collection<String> disconnectionSqlCodes) {
    524        this.disconnectionSqlCodes = disconnectionSqlCodes;
    525    }
    526
    527    /**
    528     * @param autoCommitOnReturn Whether to auto-commit on return.
    529     * @deprecated Use {@link #setAutoCommitOnReturn(boolean)}.
    530     */
    531    @Deprecated
    532    public void setEnableAutoCommitOnReturn(final boolean autoCommitOnReturn) {
    533        this.autoCommitOnReturn = autoCommitOnReturn;
    534    }
    535
    536    /**
    537     * @see #isFastFailValidation()
    538     * @param fastFailValidation
    539     *            true means connections created by this factory will fast fail validation
    540     * @since 2.1
    541     */
    542    public void setFastFailValidation(final boolean fastFailValidation) {
    543        this.fastFailValidation = fastFailValidation;
    544    }
    545
    546    /**
    547     * Sets the maximum lifetime in milliseconds of a connection after which the connection will always fail activation,
    548     * passivation and validation. A value of zero or less indicates an infinite lifetime. The default value is -1.
    549     *
    550     * @param maxConnLifetimeMillis
    551     *            The maximum lifetime in milliseconds.
    552     */
    553    public void setMaxConnLifetimeMillis(final long maxConnLifetimeMillis) {
    554        this.maxConnLifetimeMillis = maxConnLifetimeMillis;
    555    }
    556
    557    /**
    558     * Sets the maximum number of open prepared statements.
    559     *
    560     * @param maxOpenPreparedStatements
    561     *            The maximum number of open prepared statements.
    562     */
    563    public void setMaxOpenPreparedStatements(final int maxOpenPreparedStatements) {
    564        this.maxOpenPreparedStatements = maxOpenPreparedStatements;
    565    }
    566
    567    /**
    568     * Deprecated due to typo in method name.
    569     *
    570     * @param maxOpenPreparedStatements
    571     *            The maximum number of open prepared statements.
    572     * @deprecated Use {@link #setMaxOpenPreparedStatements(int)}.
    573     */
    574    @Deprecated // Due to typo in method name.
    575    public void setMaxOpenPrepatedStatements(final int maxOpenPreparedStatements) {
    576        setMaxOpenPreparedStatements(maxOpenPreparedStatements);
    577    }
    578
    579    /**
    580     * Sets the {@link ObjectPool} in which to pool {@link Connection}s.
    581     *
    582     * @param pool
    583     *            the {@link ObjectPool} in which to pool those {@link Connection}s
    584     */
    585    public synchronized void setPool(final ObjectPool<PoolableConnection> pool) {
    586        if (null != this.pool && pool != this.pool) {
    587            try {
    588                this.pool.close();
    589            } catch (final Exception e) {
    590                // ignored !?!
    591            }
    592        }
    593        this.pool = pool;
    594    }
    595
    596    public void setPoolStatements(final boolean poolStatements) {
    597        this.poolStatements = poolStatements;
    598    }
    599
    600    public void setRollbackOnReturn(final boolean rollbackOnReturn) {
    601        this.rollbackOnReturn = rollbackOnReturn;
    602    }
    603
    604    /**
    605     * Sets the query I use to {@link #validateObject validate} {@link Connection}s. Should return at least one row. If
    606     * not specified, {@link Connection#isValid(int)} will be used to validate connections.
    607     *
    608     * @param validationQuery
    609     *            a query to use to {@link #validateObject validate} {@link Connection}s.
    610     */
    611    public void setValidationQuery(final String validationQuery) {
    612        this.validationQuery = validationQuery;
    613    }
    614
    615    /**
    616     * Sets the validation query timeout, the amount of time, in seconds, that connection validation will wait for a
    617     * response from the database when executing a validation query. Use a value less than or equal to 0 for no timeout.
    618     *
    619     * @param validationQueryTimeoutSeconds
    620     *            new validation query timeout value in seconds
    621     */
    622    public void setValidationQueryTimeout(final int validationQueryTimeoutSeconds) {
    623        this.validationQueryTimeoutSeconds = validationQueryTimeoutSeconds;
    624    }
    625
    626    public void validateConnection(final PoolableConnection conn) throws SQLException {
    627        if (conn.isClosed()) {
    628            throw new SQLException("validateConnection: connection closed");
    629        }
    630        conn.validate(validationQuery, validationQueryTimeoutSeconds);
    631    }
    632
    633    private void validateLifetime(final PooledObject<PoolableConnection> p) throws Exception {
    634        if (maxConnLifetimeMillis > 0) {
    635            final long lifetime = System.currentTimeMillis() - p.getCreateTime();
    636            if (lifetime > maxConnLifetimeMillis) {
    637                throw new LifetimeExceededException(Utils.getMessage("connectionFactory.lifetimeExceeded",
    638                        Long.valueOf(lifetime), Long.valueOf(maxConnLifetimeMillis)));
    639            }
    640        }
    641    }
    642
    643    @Override
    644    public boolean validateObject(final PooledObject<PoolableConnection> p) {
    645        try {
    646            validateLifetime(p);
    647
    648            validateConnection(p.getObject());
    649            return true;
    650        } catch (final Exception e) {
    651            if (log.isDebugEnabled()) {
    652                log.debug(Utils.getMessage("poolableConnectionFactory.validateObject.fail"), e);
    653            }
    654            return false;
    655        }
    656    }
    657}