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