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.DefaultPooledObject; 036import org.apache.commons.pool2.impl.GenericKeyedObjectPool; 037import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig; 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 * Internal constant to indicate the level is not set. 050 */ 051 static final int UNKNOWN_TRANSACTION_ISOLATION = -1; 052 053 private final ConnectionFactory connectionFactory; 054 055 private final ObjectName dataSourceJmxObjectName; 056 057 private volatile String validationQuery; 058 059 private volatile int validationQueryTimeoutSeconds = -1; 060 061 private Collection<String> connectionInitSqls; 062 063 private Collection<String> disconnectionSqlCodes; 064 065 private boolean fastFailValidation = true; 066 067 private volatile ObjectPool<PoolableConnection> pool; 068 069 private Boolean defaultReadOnly; 070 071 private Boolean defaultAutoCommit; 072 073 private boolean autoCommitOnReturn = true; 074 075 private boolean rollbackOnReturn = true; 076 077 private int defaultTransactionIsolation = UNKNOWN_TRANSACTION_ISOLATION; 078 079 private String defaultCatalog; 080 081 private String defaultSchema; 082 083 private boolean cacheState; 084 085 private boolean poolStatements; 086 087 private boolean clearStatementPoolOnReturn; 088 089 private int maxOpenPreparedStatements = GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL_PER_KEY; 090 091 private long maxConnLifetimeMillis = -1; 092 093 private final AtomicLong connectionIndex = new AtomicLong(0); 094 095 private Integer defaultQueryTimeoutSeconds; 096 097 /** 098 * Creates a new {@code PoolableConnectionFactory}. 099 * 100 * @param connFactory 101 * the {@link ConnectionFactory} from which to obtain base {@link Connection}s 102 * @param dataSourceJmxObjectName 103 * The JMX object name, may be null. 104 */ 105 public PoolableConnectionFactory(final ConnectionFactory connFactory, final ObjectName dataSourceJmxObjectName) { 106 this.connectionFactory = connFactory; 107 this.dataSourceJmxObjectName = dataSourceJmxObjectName; 108 } 109 110 @Override 111 public void activateObject(final PooledObject<PoolableConnection> p) throws Exception { 112 113 validateLifetime(p); 114 115 final PoolableConnection conn = p.getObject(); 116 conn.activate(); 117 118 if (defaultAutoCommit != null && conn.getAutoCommit() != defaultAutoCommit.booleanValue()) { 119 conn.setAutoCommit(defaultAutoCommit.booleanValue()); 120 } 121 if (defaultTransactionIsolation != UNKNOWN_TRANSACTION_ISOLATION 122 && conn.getTransactionIsolation() != defaultTransactionIsolation) { 123 conn.setTransactionIsolation(defaultTransactionIsolation); 124 } 125 if (defaultReadOnly != null && conn.isReadOnly() != defaultReadOnly.booleanValue()) { 126 conn.setReadOnly(defaultReadOnly.booleanValue()); 127 } 128 if (defaultCatalog != null && !defaultCatalog.equals(conn.getCatalog())) { 129 conn.setCatalog(defaultCatalog); 130 } 131 if (defaultSchema != null && !defaultSchema.equals(Jdbc41Bridge.getSchema(conn))) { 132 Jdbc41Bridge.setSchema(conn, defaultSchema); 133 } 134 conn.setDefaultQueryTimeout(defaultQueryTimeoutSeconds); 135 } 136 137 @Override 138 public void destroyObject(final PooledObject<PoolableConnection> p) throws Exception { 139 p.getObject().reallyClose(); 140 } 141 142 /** 143 * @return The cache state. 144 * @since Made public in 2.6.0. 145 */ 146 public boolean getCacheState() { 147 return cacheState; 148 } 149 150 /** 151 * @return The connection factory. 152 * @since Made public in 2.6.0. 153 */ 154 public ConnectionFactory getConnectionFactory() { 155 return connectionFactory; 156 } 157 158 protected AtomicLong getConnectionIndex() { 159 return connectionIndex; 160 } 161 162 /** 163 * @return The collection of initialization SQL statements. 164 * @since 2.6.0 165 */ 166 public Collection<String> getConnectionInitSqls() { 167 return connectionInitSqls; 168 } 169 170 /** 171 * @return The data source JMX ObjectName 172 * @since Made public in 2.6.0. 173 */ 174 public ObjectName getDataSourceJmxName() { 175 return dataSourceJmxObjectName; 176 } 177 178 /** 179 * @return The data source JMS ObjectName. 180 * @since 2.6.0 181 */ 182 public ObjectName getDataSourceJmxObjectName() { 183 return dataSourceJmxObjectName; 184 } 185 186 /** 187 * @return Default auto-commit value. 188 * @since 2.6.0 189 */ 190 public Boolean getDefaultAutoCommit() { 191 return defaultAutoCommit; 192 } 193 194 /** 195 * @return Default catalog. 196 * @since 2.6.0 197 */ 198 public String getDefaultCatalog() { 199 return defaultCatalog; 200 } 201 202 /** 203 * @return Default query timeout in seconds. 204 */ 205 public Integer getDefaultQueryTimeout() { 206 return defaultQueryTimeoutSeconds; 207 } 208 209 /** 210 * @return Default query timeout in seconds. 211 * @since 2.6.0 212 */ 213 public Integer getDefaultQueryTimeoutSeconds() { 214 return defaultQueryTimeoutSeconds; 215 } 216 217 /** 218 * @return Default read-only-value. 219 * @since 2.6.0 220 */ 221 public Boolean getDefaultReadOnly() { 222 return defaultReadOnly; 223 } 224 225 /** 226 * @return Default schema. 227 * @since 2.6.0 228 */ 229 public String getDefaultSchema() { 230 return defaultSchema; 231 } 232 233 /** 234 * @return Default transaction isolation. 235 * @since 2.6.0 236 */ 237 public int getDefaultTransactionIsolation() { 238 return defaultTransactionIsolation; 239 } 240 241 /** 242 * SQL_STATE codes considered to signal fatal conditions. 243 * <p> 244 * Overrides the defaults in {@link Utils#DISCONNECTION_SQL_CODES} (plus anything starting with 245 * {@link Utils#DISCONNECTION_SQL_CODE_PREFIX}). If this property is non-null and {@link #isFastFailValidation()} is 246 * {@code true}, whenever connections created by this factory generate exceptions with SQL_STATE codes in this list, 247 * they will be marked as "fatally disconnected" and subsequent validations will fail fast (no attempt at isValid or 248 * validation query). 249 * </p> 250 * <p> 251 * If {@link #isFastFailValidation()} is {@code false} setting this property has no effect. 252 * </p> 253 * 254 * @return SQL_STATE codes overriding defaults 255 * @since 2.1 256 */ 257 public Collection<String> getDisconnectionSqlCodes() { 258 return disconnectionSqlCodes; 259 } 260 261 /** 262 * @return Maximum connection lifetime in milliseconds. 263 * @since 2.6.0 264 */ 265 public long getMaxConnLifetimeMillis() { 266 return maxConnLifetimeMillis; 267 } 268 269 protected int getMaxOpenPreparedStatements() { 270 return maxOpenPreparedStatements; 271 } 272 273 /** 274 * Returns the {@link ObjectPool} in which {@link Connection}s are pooled. 275 * 276 * @return the connection pool 277 */ 278 public synchronized ObjectPool<PoolableConnection> getPool() { 279 return pool; 280 } 281 282 /** 283 * @return Whether to pool statements. 284 * @since Made public in 2.6.0. 285 */ 286 public boolean getPoolStatements() { 287 return poolStatements; 288 } 289 /** 290 * @return Validation query. 291 * @since 2.6.0 292 */ 293 public String getValidationQuery() { 294 return validationQuery; 295 } 296 /** 297 * @return Validation query timeout in seconds. 298 * @since 2.6.0 299 */ 300 public int getValidationQueryTimeoutSeconds() { 301 return validationQueryTimeoutSeconds; 302 } 303 protected void initializeConnection(final Connection conn) throws SQLException { 304 final Collection<String> sqls = connectionInitSqls; 305 if (conn.isClosed()) { 306 throw new SQLException("initializeConnection: connection closed"); 307 } 308 if (null != sqls) { 309 try (Statement stmt = conn.createStatement()) { 310 for (final String sql : sqls) { 311 Objects.requireNonNull(sql, "null connectionInitSqls element"); 312 stmt.execute(sql); 313 } 314 } 315 } 316 } 317 318 /** 319 * @return Whether to auto-commit on return. 320 * @since 2.6.0 321 */ 322 public boolean isAutoCommitOnReturn() { 323 return autoCommitOnReturn; 324 } 325 326 /** 327 * @return Whether to auto-commit on return. 328 * @deprecated Use {@link #isAutoCommitOnReturn()}. 329 */ 330 @Deprecated 331 public boolean isEnableAutoCommitOnReturn() { 332 return autoCommitOnReturn; 333 } 334 335 /** 336 * True means that validation will fail immediately for connections that have previously thrown SQLExceptions with 337 * SQL_STATE indicating fatal disconnection errors. 338 * 339 * @return true if connections created by this factory will fast fail validation. 340 * @see #setDisconnectionSqlCodes(Collection) 341 * @since 2.1 342 * @since 2.5.0 Defaults to true, previous versions defaulted to false. 343 */ 344 public boolean isFastFailValidation() { 345 return fastFailValidation; 346 } 347 348 /** 349 * @return Whether to rollback on return. 350 */ 351 public boolean isRollbackOnReturn() { 352 return rollbackOnReturn; 353 } 354 355 @Override 356 public PooledObject<PoolableConnection> makeObject() throws Exception { 357 Connection conn = connectionFactory.createConnection(); 358 if (conn == null) { 359 throw new IllegalStateException("Connection factory returned null from createConnection"); 360 } 361 try { 362 initializeConnection(conn); 363 } catch (final SQLException sqle) { 364 // Make sure the connection is closed 365 try { 366 conn.close(); 367 } catch (final SQLException ignore) { 368 // ignore 369 } 370 // Rethrow original exception so it is visible to caller 371 throw sqle; 372 } 373 374 final long connIndex = connectionIndex.getAndIncrement(); 375 376 if (poolStatements) { 377 conn = new PoolingConnection(conn); 378 final GenericKeyedObjectPoolConfig<DelegatingPreparedStatement> config = new GenericKeyedObjectPoolConfig<>(); 379 config.setMaxTotalPerKey(-1); 380 config.setBlockWhenExhausted(false); 381 config.setMaxWaitMillis(0); 382 config.setMaxIdlePerKey(1); 383 config.setMaxTotal(maxOpenPreparedStatements); 384 if (dataSourceJmxObjectName != null) { 385 final StringBuilder base = new StringBuilder(dataSourceJmxObjectName.toString()); 386 base.append(Constants.JMX_CONNECTION_BASE_EXT); 387 base.append(Long.toString(connIndex)); 388 config.setJmxNameBase(base.toString()); 389 config.setJmxNamePrefix(Constants.JMX_STATEMENT_POOL_PREFIX); 390 } else { 391 config.setJmxEnabled(false); 392 } 393 final PoolingConnection poolingConn = (PoolingConnection) conn; 394 final KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> stmtPool = new GenericKeyedObjectPool<>( 395 poolingConn, config); 396 poolingConn.setStatementPool(stmtPool); 397 poolingConn.setClearStatementPoolOnReturn(clearStatementPoolOnReturn); 398 poolingConn.setCacheState(cacheState); 399 } 400 401 // Register this connection with JMX 402 ObjectName connJmxName; 403 if (dataSourceJmxObjectName == null) { 404 connJmxName = null; 405 } else { 406 connJmxName = new ObjectName( 407 dataSourceJmxObjectName.toString() + Constants.JMX_CONNECTION_BASE_EXT + connIndex); 408 } 409 410 final PoolableConnection pc = new PoolableConnection(conn, pool, connJmxName, disconnectionSqlCodes, 411 fastFailValidation); 412 pc.setCacheState(cacheState); 413 414 return new DefaultPooledObject<>(pc); 415 } 416 417 @Override 418 public void passivateObject(final PooledObject<PoolableConnection> p) throws Exception { 419 420 validateLifetime(p); 421 422 final PoolableConnection conn = p.getObject(); 423 Boolean connAutoCommit = null; 424 if (rollbackOnReturn) { 425 connAutoCommit = Boolean.valueOf(conn.getAutoCommit()); 426 if (!connAutoCommit.booleanValue() && !conn.isReadOnly()) { 427 conn.rollback(); 428 } 429 } 430 431 conn.clearWarnings(); 432 433 // DBCP-97 / DBCP-399 / DBCP-351 Idle connections in the pool should 434 // have autoCommit enabled 435 if (autoCommitOnReturn) { 436 if (connAutoCommit == null) { 437 connAutoCommit = Boolean.valueOf(conn.getAutoCommit()); 438 } 439 if (!connAutoCommit.booleanValue()) { 440 conn.setAutoCommit(true); 441 } 442 } 443 444 conn.passivate(); 445 } 446 447 public void setAutoCommitOnReturn(final boolean autoCommitOnReturn) { 448 this.autoCommitOnReturn = autoCommitOnReturn; 449 } 450 451 public void setCacheState(final boolean cacheState) { 452 this.cacheState = cacheState; 453 } 454 455 /** 456 * Sets the SQL statements I use to initialize newly created {@link Connection}s. Using {@code null} turns off 457 * connection initialization. 458 * 459 * @param connectionInitSqls 460 * SQL statement to initialize {@link Connection}s. 461 */ 462 public void setConnectionInitSql(final Collection<String> connectionInitSqls) { 463 this.connectionInitSqls = connectionInitSqls; 464 } 465 466 /** 467 * Sets the default "auto commit" setting for borrowed {@link Connection}s 468 * 469 * @param defaultAutoCommit 470 * the default "auto commit" setting for borrowed {@link Connection}s 471 */ 472 public void setDefaultAutoCommit(final Boolean defaultAutoCommit) { 473 this.defaultAutoCommit = defaultAutoCommit; 474 } 475 476 /** 477 * Sets the default "catalog" setting for borrowed {@link Connection}s 478 * 479 * @param defaultCatalog 480 * the default "catalog" setting for borrowed {@link Connection}s 481 */ 482 public void setDefaultCatalog(final String defaultCatalog) { 483 this.defaultCatalog = defaultCatalog; 484 } 485 486 public void setDefaultQueryTimeout(final Integer defaultQueryTimeoutSeconds) { 487 this.defaultQueryTimeoutSeconds = defaultQueryTimeoutSeconds; 488 } 489 /** 490 * Sets the default "read only" setting for borrowed {@link Connection}s 491 * 492 * @param defaultReadOnly 493 * the default "read only" setting for borrowed {@link Connection}s 494 */ 495 public void setDefaultReadOnly(final Boolean defaultReadOnly) { 496 this.defaultReadOnly = defaultReadOnly; 497 } 498 499 /** 500 * Sets the default "schema" setting for borrowed {@link Connection}s 501 * 502 * @param defaultSchema 503 * the default "schema" setting for borrowed {@link Connection}s 504 * @since 2.5.0 505 */ 506 public void setDefaultSchema(final String defaultSchema) { 507 this.defaultSchema = defaultSchema; 508 } 509 510 /** 511 * Sets the default "Transaction Isolation" setting for borrowed {@link Connection}s 512 * 513 * @param defaultTransactionIsolation 514 * the default "Transaction Isolation" setting for returned {@link Connection}s 515 */ 516 public void setDefaultTransactionIsolation(final int defaultTransactionIsolation) { 517 this.defaultTransactionIsolation = defaultTransactionIsolation; 518 } 519 520 /** 521 * @param disconnectionSqlCodes 522 * The disconnection SQL codes. 523 * @see #getDisconnectionSqlCodes() 524 * @since 2.1 525 */ 526 public void setDisconnectionSqlCodes(final Collection<String> disconnectionSqlCodes) { 527 this.disconnectionSqlCodes = disconnectionSqlCodes; 528 } 529 530 /** 531 * @param autoCommitOnReturn Whether to auto-commit on return. 532 * @deprecated Use {@link #setAutoCommitOnReturn(boolean)}. 533 */ 534 @Deprecated 535 public void setEnableAutoCommitOnReturn(final boolean autoCommitOnReturn) { 536 this.autoCommitOnReturn = autoCommitOnReturn; 537 } 538 539 /** 540 * @see #isFastFailValidation() 541 * @param fastFailValidation 542 * true means connections created by this factory will fast fail validation 543 * @since 2.1 544 */ 545 public void setFastFailValidation(final boolean fastFailValidation) { 546 this.fastFailValidation = fastFailValidation; 547 } 548 549 /** 550 * Sets the maximum lifetime in milliseconds of a connection after which the connection will always fail activation, 551 * passivation and validation. A value of zero or less indicates an infinite lifetime. The default value is -1. 552 * 553 * @param maxConnLifetimeMillis 554 * The maximum lifetime in milliseconds. 555 */ 556 public void setMaxConnLifetimeMillis(final long maxConnLifetimeMillis) { 557 this.maxConnLifetimeMillis = maxConnLifetimeMillis; 558 } 559 560 /** 561 * Sets the maximum number of open prepared statements. 562 * 563 * @param maxOpenPreparedStatements 564 * The maximum number of open prepared statements. 565 */ 566 public void setMaxOpenPreparedStatements(final int maxOpenPreparedStatements) { 567 this.maxOpenPreparedStatements = maxOpenPreparedStatements; 568 } 569 570 /** 571 * Deprecated due to typo in method name. 572 * 573 * @param maxOpenPreparedStatements 574 * The maximum number of open prepared statements. 575 * @deprecated Use {@link #setMaxOpenPreparedStatements(int)}. 576 */ 577 @Deprecated // Due to typo in method name. 578 public void setMaxOpenPrepatedStatements(final int maxOpenPreparedStatements) { 579 setMaxOpenPreparedStatements(maxOpenPreparedStatements); 580 } 581 582 /** 583 * Sets the {@link ObjectPool} in which to pool {@link Connection}s. 584 * 585 * @param pool 586 * the {@link ObjectPool} in which to pool those {@link Connection}s 587 */ 588 public synchronized void setPool(final ObjectPool<PoolableConnection> pool) { 589 if (null != this.pool && pool != this.pool) { 590 try { 591 this.pool.close(); 592 } catch (final Exception e) { 593 // ignored !?! 594 } 595 } 596 this.pool = pool; 597 } 598 599 public void setPoolStatements(final boolean poolStatements) { 600 this.poolStatements = poolStatements; 601 } 602 603 /** 604 * Sets whether the pool of statements (which was enabled with {@link #setPoolStatements(boolean)}) should 605 * be cleared when the connection is returned to its pool. Default is false. 606 * 607 * @param clearStatementPoolOnReturn clear or not 608 * @since 2.8.0 609 */ 610 public void setClearStatementPoolOnReturn(final boolean clearStatementPoolOnReturn) { 611 this.clearStatementPoolOnReturn = clearStatementPoolOnReturn; 612 } 613 614 public void setRollbackOnReturn(final boolean rollbackOnReturn) { 615 this.rollbackOnReturn = rollbackOnReturn; 616 } 617 618 /** 619 * Sets the query I use to {@link #validateObject validate} {@link Connection}s. Should return at least one row. If 620 * not specified, {@link Connection#isValid(int)} will be used to validate connections. 621 * 622 * @param validationQuery 623 * a query to use to {@link #validateObject validate} {@link Connection}s. 624 */ 625 public void setValidationQuery(final String validationQuery) { 626 this.validationQuery = validationQuery; 627 } 628 629 /** 630 * Sets the validation query timeout, the amount of time, in seconds, that connection validation will wait for a 631 * response from the database when executing a validation query. Use a value less than or equal to 0 for no timeout. 632 * 633 * @param validationQueryTimeoutSeconds 634 * new validation query timeout value in seconds 635 */ 636 public void setValidationQueryTimeout(final int validationQueryTimeoutSeconds) { 637 this.validationQueryTimeoutSeconds = validationQueryTimeoutSeconds; 638 } 639 640 public void validateConnection(final PoolableConnection conn) throws SQLException { 641 if (conn.isClosed()) { 642 throw new SQLException("validateConnection: connection closed"); 643 } 644 conn.validate(validationQuery, validationQueryTimeoutSeconds); 645 } 646 647 private void validateLifetime(final PooledObject<PoolableConnection> p) throws Exception { 648 if (maxConnLifetimeMillis > 0) { 649 final long lifetime = System.currentTimeMillis() - p.getCreateTime(); 650 if (lifetime > maxConnLifetimeMillis) { 651 throw new LifetimeExceededException(Utils.getMessage("connectionFactory.lifetimeExceeded", 652 Long.valueOf(lifetime), Long.valueOf(maxConnLifetimeMillis))); 653 } 654 } 655 } 656 657 @Override 658 public boolean validateObject(final PooledObject<PoolableConnection> p) { 659 try { 660 validateLifetime(p); 661 662 validateConnection(p.getObject()); 663 return true; 664 } catch (final Exception e) { 665 if (log.isDebugEnabled()) { 666 log.debug(Utils.getMessage("poolableConnectionFactory.validateObject.fail"), e); 667 } 668 return false; 669 } 670 } 671}