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 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}