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