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