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.Statement;
022import java.sql.SQLException;
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 $Revision: 1572242 $ $Date: 2014-02-26 20:34:39 +0000 (Wed, 26 Feb 2014) $
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 <tt>PoolableConnectionFactory</tt>.
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 <tt>null</tt> 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    synchronized public 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
194    public Integer getDefaultQueryTimeout() {
195        return defaultQueryTimeout;
196    }
197
198    public void setDefaultQueryTimeout(Integer defaultQueryTimeout) {
199        this.defaultQueryTimeout = defaultQueryTimeout;
200    }
201
202
203    @Override
204    public PooledObject<PoolableConnection> makeObject() throws Exception {
205        Connection conn = _connFactory.createConnection();
206        if (conn == null) {
207            throw new IllegalStateException("Connection factory returned null from createConnection");
208        }
209        try {
210            initializeConnection(conn);
211        } catch (SQLException sqle) {
212            // Make sure the connection is closed
213            try {
214                conn.close();
215            } catch (SQLException ignore) {
216                // ignore
217            }
218            // Rethrow original exception so it is visible to caller
219            throw sqle;
220        }
221
222        long connIndex = connectionIndex.getAndIncrement();
223
224        if(poolStatements) {
225            conn = new PoolingConnection(conn);
226            GenericKeyedObjectPoolConfig config = new GenericKeyedObjectPoolConfig();
227            config.setMaxTotalPerKey(-1);
228            config.setBlockWhenExhausted(false);
229            config.setMaxWaitMillis(0);
230            config.setMaxIdlePerKey(1);
231            config.setMaxTotal(maxOpenPreparedStatements);
232            if (dataSourceJmxName != null) {
233                StringBuilder base = new StringBuilder(dataSourceJmxName.toString());
234                base.append(Constants.JMX_CONNECTION_BASE_EXT);
235                base.append(Long.toString(connIndex));
236                config.setJmxNameBase(base.toString());
237                config.setJmxNamePrefix(Constants.JMX_STATEMENT_POOL_PREFIX);
238            }
239            KeyedObjectPool<PStmtKey,DelegatingPreparedStatement> stmtPool =
240                    new GenericKeyedObjectPool<>((PoolingConnection)conn, config);
241            ((PoolingConnection)conn).setStatementPool(stmtPool);
242            ((PoolingConnection) conn).setCacheState(_cacheState);
243        }
244
245        // Register this connection with JMX
246        ObjectName connJmxName;
247        if (dataSourceJmxName == null) {
248            connJmxName = null;
249        } else {
250            connJmxName = new ObjectName(dataSourceJmxName.toString() +
251                    Constants.JMX_CONNECTION_BASE_EXT + connIndex);
252        }
253
254        PoolableConnection pc = new PoolableConnection(conn,_pool, connJmxName);
255
256        return new DefaultPooledObject<>(pc);
257    }
258
259    protected void initializeConnection(Connection conn) throws SQLException {
260        Collection<String> sqls = _connectionInitSqls;
261        if(conn.isClosed()) {
262            throw new SQLException("initializeConnection: connection closed");
263        }
264        if(null != sqls) {
265            try (Statement stmt = conn.createStatement();) {
266                for (String sql : sqls) {
267                    if (sql == null) {
268                        throw new NullPointerException(
269                                "null connectionInitSqls element");
270                    }
271                    stmt.execute(sql);
272                }
273            }
274        }
275    }
276
277    @Override
278    public void destroyObject(PooledObject<PoolableConnection> p)
279            throws Exception {
280        p.getObject().reallyClose();
281    }
282
283    @Override
284    public boolean validateObject(PooledObject<PoolableConnection> p) {
285        try {
286            validateLifetime(p);
287
288            validateConnection(p.getObject());
289            return true;
290        } catch (Exception e) {
291            if (log.isDebugEnabled()) {
292                log.debug(Utils.getMessage(
293                        "poolableConnectionFactory.validateObject.fail"), e);
294            }
295            return false;
296        }
297    }
298
299    public void validateConnection(PoolableConnection conn) throws SQLException {
300        if(conn.isClosed()) {
301            throw new SQLException("validateConnection: connection closed");
302        }
303        conn.validate(_validationQuery, _validationQueryTimeout);
304    }
305
306    @Override
307    public void passivateObject(PooledObject<PoolableConnection> p)
308            throws Exception {
309
310        validateLifetime(p);
311
312        PoolableConnection conn = p.getObject();
313        Boolean connAutoCommit = null;
314        if (rollbackOnReturn) {
315            connAutoCommit = Boolean.valueOf(conn.getAutoCommit());
316            if(!connAutoCommit.booleanValue() && !conn.isReadOnly()) {
317                conn.rollback();
318            }
319        }
320
321        conn.clearWarnings();
322
323        // DBCP-97 / DBCP-399 / DBCP-351 Idle connections in the pool should
324        // have autoCommit enabled
325        if (enableAutoCommitOnReturn) {
326            if (connAutoCommit == null) {
327                connAutoCommit = Boolean.valueOf(conn.getAutoCommit());
328            }
329            if(!connAutoCommit.booleanValue()) {
330                conn.setAutoCommit(true);
331            }
332        }
333
334        conn.passivate();
335    }
336
337    @Override
338    public void activateObject(PooledObject<PoolableConnection> p)
339            throws Exception {
340
341        validateLifetime(p);
342
343        PoolableConnection conn = p.getObject();
344        conn.activate();
345
346        if (_defaultAutoCommit != null &&
347                conn.getAutoCommit() != _defaultAutoCommit.booleanValue()) {
348            conn.setAutoCommit(_defaultAutoCommit.booleanValue());
349        }
350        if (_defaultTransactionIsolation != UNKNOWN_TRANSACTIONISOLATION &&
351                conn.getTransactionIsolation() != _defaultTransactionIsolation) {
352            conn.setTransactionIsolation(_defaultTransactionIsolation);
353        }
354        if (_defaultReadOnly != null &&
355                conn.isReadOnly() != _defaultReadOnly.booleanValue()) {
356            conn.setReadOnly(_defaultReadOnly.booleanValue());
357        }
358        if (_defaultCatalog != null &&
359                !_defaultCatalog.equals(conn.getCatalog())) {
360            conn.setCatalog(_defaultCatalog);
361        }
362        conn.setDefaultQueryTimeout(defaultQueryTimeout);
363    }
364
365    private void validateLifetime(PooledObject<PoolableConnection> p)
366            throws Exception {
367        if (maxConnLifetimeMillis > 0) {
368            long lifetime = System.currentTimeMillis() - p.getCreateTime();
369            if (lifetime > maxConnLifetimeMillis) {
370                throw new Exception(Utils.getMessage(
371                        "connectionFactory.lifetimeExceeded",
372                        Long.valueOf(lifetime),
373                        Long.valueOf(maxConnLifetimeMillis)));
374            }
375        }
376    }
377
378    protected ConnectionFactory getConnectionFactory() {
379        return _connFactory;
380    }
381
382    protected boolean getPoolStatements() {
383        return poolStatements;
384    }
385
386    protected int getMaxOpenPreparedStatements() {
387        return maxOpenPreparedStatements;
388    }
389
390    protected boolean getCacheState() {
391        return _cacheState;
392    }
393
394    private final ConnectionFactory _connFactory;
395    private final ObjectName dataSourceJmxName;
396    private volatile String _validationQuery = null;
397    private volatile int _validationQueryTimeout = -1;
398    private Collection<String> _connectionInitSqls = null;
399    private volatile ObjectPool<PoolableConnection> _pool = null;
400    private Boolean _defaultReadOnly = null;
401    private Boolean _defaultAutoCommit = null;
402    private boolean enableAutoCommitOnReturn = true;
403    private boolean rollbackOnReturn = true;
404    private int _defaultTransactionIsolation = UNKNOWN_TRANSACTIONISOLATION;
405    private String _defaultCatalog;
406    private boolean _cacheState;
407    private boolean poolStatements = false;
408    private int maxOpenPreparedStatements =
409        GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL_PER_KEY;
410    private long maxConnLifetimeMillis = -1;
411    private final AtomicLong connectionIndex = new AtomicLong(0);
412    private Integer defaultQueryTimeout = null;
413
414    /**
415     * Internal constant to indicate the level is not set.
416     */
417    static final int UNKNOWN_TRANSACTIONISOLATION = -1;
418}