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