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