1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */

17
18 package org.apache.tomcat.dbcp.dbcp2;
19
20 import java.sql.Connection;
21 import java.sql.SQLException;
22 import java.sql.Statement;
23 import java.util.Collection;
24 import java.util.Objects;
25 import java.util.concurrent.atomic.AtomicLong;
26
27 import javax.management.ObjectName;
28
29 import org.apache.juli.logging.Log;
30 import org.apache.juli.logging.LogFactory;
31 import org.apache.tomcat.dbcp.pool2.KeyedObjectPool;
32 import org.apache.tomcat.dbcp.pool2.ObjectPool;
33 import org.apache.tomcat.dbcp.pool2.PooledObject;
34 import org.apache.tomcat.dbcp.pool2.PooledObjectFactory;
35 import org.apache.tomcat.dbcp.pool2.impl.DefaultPooledObject;
36 import org.apache.tomcat.dbcp.pool2.impl.GenericKeyedObjectPool;
37 import org.apache.tomcat.dbcp.pool2.impl.GenericKeyedObjectPoolConfig;
38
39 /**
40  * A {@link PooledObjectFactory} that creates {@link PoolableConnection}s.
41  *
42  * @since 2.0
43  */

44 public class PoolableConnectionFactory implements PooledObjectFactory<PoolableConnection> {
45
46     private static final Log log = LogFactory.getLog(PoolableConnectionFactory.class);
47
48     /**
49      * Internal constant to indicate the level is not set.
50      */

51     static final int UNKNOWN_TRANSACTION_ISOLATION = -1;
52
53     private final ConnectionFactory connectionFactory;
54
55     private final ObjectName dataSourceJmxObjectName;
56
57     private volatile String validationQuery;
58
59     private volatile int validationQueryTimeoutSeconds = -1;
60
61     private Collection<String> connectionInitSqls;
62
63     private Collection<String> disconnectionSqlCodes;
64
65     private boolean fastFailValidation = true;
66
67     private volatile ObjectPool<PoolableConnection> pool;
68
69     private Boolean defaultReadOnly;
70
71     private Boolean defaultAutoCommit;
72
73     private boolean autoCommitOnReturn = true;
74
75     private boolean rollbackOnReturn = true;
76
77     private int defaultTransactionIsolation = UNKNOWN_TRANSACTION_ISOLATION;
78
79     private String defaultCatalog;
80
81     private String defaultSchema;
82
83     private boolean cacheState;
84
85     private boolean poolStatements;
86
87     private int maxOpenPreparedStatements = GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL_PER_KEY;
88
89     private long maxConnLifetimeMillis = -1;
90
91     private final AtomicLong connectionIndex = new AtomicLong(0);
92
93     private Integer defaultQueryTimeoutSeconds;
94
95     /**
96      * Creates a new {@code PoolableConnectionFactory}.
97      *
98      * @param connFactory
99      *            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 }
658