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