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