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 public void setCacheState(final boolean cacheState) { 161 this.cacheState = cacheState; 162 } 163 164 public void setPoolStatements(final boolean poolStatements) { 165 this.poolStatements = poolStatements; 166 } 167 168 /** 169 * Deprecated due to typo in method name. 170 * 171 * @param maxOpenPreparedStatements 172 * The maximum number of open prepared statements. 173 * @deprecated Use {@link #setMaxOpenPreparedStatements(int)}. 174 */ 175 @Deprecated // Due to typo in method name. 176 public void setMaxOpenPrepatedStatements(final int maxOpenPreparedStatements) { 177 setMaxOpenPreparedStatements(maxOpenPreparedStatements); 178 } 179 180 /** 181 * Sets the maximum number of open prepared statements. 182 * 183 * @param maxOpenPreparedStatements 184 * The maximum number of open prepared statements. 185 */ 186 public void setMaxOpenPreparedStatements(final int maxOpenPreparedStatements) { 187 this.maxOpenPreparedStatements = maxOpenPreparedStatements; 188 } 189 190 /** 191 * Sets the maximum lifetime in milliseconds of a connection after which the connection will always fail activation, 192 * passivation and validation. A value of zero or less indicates an infinite lifetime. The default value is -1. 193 * 194 * @param maxConnLifetimeMillis 195 * The maximum lifetime in milliseconds. 196 */ 197 public void setMaxConnLifetimeMillis(final long maxConnLifetimeMillis) { 198 this.maxConnLifetimeMillis = maxConnLifetimeMillis; 199 } 200 201 public boolean isEnableAutoCommitOnReturn() { 202 return enableAutoCommitOnReturn; 203 } 204 205 public void setEnableAutoCommitOnReturn(final boolean enableAutoCommitOnReturn) { 206 this.enableAutoCommitOnReturn = enableAutoCommitOnReturn; 207 } 208 209 public boolean isRollbackOnReturn() { 210 return rollbackOnReturn; 211 } 212 213 public void setRollbackOnReturn(final boolean rollbackOnReturn) { 214 this.rollbackOnReturn = rollbackOnReturn; 215 } 216 217 public Integer getDefaultQueryTimeout() { 218 return defaultQueryTimeoutSeconds; 219 } 220 221 public void setDefaultQueryTimeout(final Integer defaultQueryTimeoutSeconds) { 222 this.defaultQueryTimeoutSeconds = defaultQueryTimeoutSeconds; 223 } 224 225 /** 226 * SQL_STATE codes considered to signal fatal conditions. 227 * <p> 228 * Overrides the defaults in {@link Utils#DISCONNECTION_SQL_CODES} (plus anything starting with 229 * {@link Utils#DISCONNECTION_SQL_CODE_PREFIX}). If this property is non-null and {@link #isFastFailValidation()} is 230 * {@code true}, whenever connections created by this factory generate exceptions with SQL_STATE codes in this list, 231 * they will be marked as "fatally disconnected" and subsequent validations will fail fast (no attempt at isValid or 232 * validation query). 233 * </p> 234 * <p> 235 * If {@link #isFastFailValidation()} is {@code false} setting this property has no effect. 236 * </p> 237 * 238 * @return SQL_STATE codes overriding defaults 239 * @since 2.1 240 */ 241 public Collection<String> getDisconnectionSqlCodes() { 242 return disconnectionSqlCodes; 243 } 244 245 /** 246 * @param disconnectionSqlCodes 247 * The disconnection SQL codes. 248 * @see #getDisconnectionSqlCodes() 249 * @since 2.1 250 */ 251 public void setDisconnectionSqlCodes(final Collection<String> disconnectionSqlCodes) { 252 this.disconnectionSqlCodes = disconnectionSqlCodes; 253 } 254 255 /** 256 * True means that validation will fail immediately for connections that have previously thrown SQLExceptions with 257 * SQL_STATE indicating fatal disconnection errors. 258 * 259 * @return true if connections created by this factory will fast fail validation. 260 * @see #setDisconnectionSqlCodes(Collection) 261 * @since 2.1 262 */ 263 public boolean isFastFailValidation() { 264 return fastFailValidation; 265 } 266 267 /** 268 * @see #isFastFailValidation() 269 * @param fastFailValidation 270 * true means connections created by this factory will fast fail validation 271 * @since 2.1 272 */ 273 public void setFastFailValidation(final boolean fastFailValidation) { 274 this.fastFailValidation = fastFailValidation; 275 } 276 277 @Override 278 public PooledObject<PoolableConnection> makeObject() throws Exception { 279 Connection conn = connectionFactory.createConnection(); 280 if (conn == null) { 281 throw new IllegalStateException("Connection factory returned null from createConnection"); 282 } 283 try { 284 initializeConnection(conn); 285 } catch (final SQLException sqle) { 286 // Make sure the connection is closed 287 try { 288 conn.close(); 289 } catch (final SQLException ignore) { 290 // ignore 291 } 292 // Rethrow original exception so it is visible to caller 293 throw sqle; 294 } 295 296 final long connIndex = connectionIndex.getAndIncrement(); 297 298 if (poolStatements) { 299 conn = new PoolingConnection(conn); 300 final GenericKeyedObjectPoolConfig config = new GenericKeyedObjectPoolConfig(); 301 config.setMaxTotalPerKey(-1); 302 config.setBlockWhenExhausted(false); 303 config.setMaxWaitMillis(0); 304 config.setMaxIdlePerKey(1); 305 config.setMaxTotal(maxOpenPreparedStatements); 306 if (dataSourceJmxObjectName != null) { 307 final StringBuilder base = new StringBuilder(dataSourceJmxObjectName.toString()); 308 base.append(Constants.JMX_CONNECTION_BASE_EXT); 309 base.append(Long.toString(connIndex)); 310 config.setJmxNameBase(base.toString()); 311 config.setJmxNamePrefix(Constants.JMX_STATEMENT_POOL_PREFIX); 312 } else { 313 config.setJmxEnabled(false); 314 } 315 final KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> stmtPool = new GenericKeyedObjectPool<>( 316 (PoolingConnection) conn, config); 317 ((PoolingConnection) conn).setStatementPool(stmtPool); 318 ((PoolingConnection) conn).setCacheState(cacheState); 319 } 320 321 // Register this connection with JMX 322 ObjectName connJmxName; 323 if (dataSourceJmxObjectName == null) { 324 connJmxName = null; 325 } else { 326 connJmxName = new ObjectName( 327 dataSourceJmxObjectName.toString() + Constants.JMX_CONNECTION_BASE_EXT + connIndex); 328 } 329 330 final PoolableConnection pc = new PoolableConnection(conn, pool, connJmxName, disconnectionSqlCodes, 331 fastFailValidation); 332 pc.setCacheState(cacheState); 333 334 return new DefaultPooledObject<>(pc); 335 } 336 337 protected void initializeConnection(final Connection conn) throws SQLException { 338 final Collection<String> sqls = connectionInitSqls; 339 if (conn.isClosed()) { 340 throw new SQLException("initializeConnection: connection closed"); 341 } 342 if (null != sqls) { 343 try (Statement stmt = conn.createStatement()) { 344 for (final String sql : sqls) { 345 Objects.requireNonNull(sql, "null connectionInitSqls element"); 346 stmt.execute(sql); 347 } 348 } 349 } 350 } 351 352 @Override 353 public void destroyObject(final PooledObject<PoolableConnection> p) throws Exception { 354 p.getObject().reallyClose(); 355 } 356 357 @Override 358 public boolean validateObject(final PooledObject<PoolableConnection> p) { 359 try { 360 validateLifetime(p); 361 362 validateConnection(p.getObject()); 363 return true; 364 } catch (final Exception e) { 365 if (log.isDebugEnabled()) { 366 log.debug(Utils.getMessage("poolableConnectionFactory.validateObject.fail"), e); 367 } 368 return false; 369 } 370 } 371 372 public void validateConnection(final PoolableConnection conn) throws SQLException { 373 if (conn.isClosed()) { 374 throw new SQLException("validateConnection: connection closed"); 375 } 376 conn.validate(validationQuery, validationQueryTimeoutSeconds); 377 } 378 379 @Override 380 public void passivateObject(final PooledObject<PoolableConnection> p) throws Exception { 381 382 validateLifetime(p); 383 384 final PoolableConnection conn = p.getObject(); 385 Boolean connAutoCommit = null; 386 if (rollbackOnReturn) { 387 connAutoCommit = Boolean.valueOf(conn.getAutoCommit()); 388 if (!connAutoCommit.booleanValue() && !conn.isReadOnly()) { 389 conn.rollback(); 390 } 391 } 392 393 conn.clearWarnings(); 394 395 // DBCP-97 / DBCP-399 / DBCP-351 Idle connections in the pool should 396 // have autoCommit enabled 397 if (enableAutoCommitOnReturn) { 398 if (connAutoCommit == null) { 399 connAutoCommit = Boolean.valueOf(conn.getAutoCommit()); 400 } 401 if (!connAutoCommit.booleanValue()) { 402 conn.setAutoCommit(true); 403 } 404 } 405 406 conn.passivate(); 407 } 408 409 @Override 410 public void activateObject(final PooledObject<PoolableConnection> p) throws Exception { 411 412 validateLifetime(p); 413 414 final PoolableConnection conn = p.getObject(); 415 conn.activate(); 416 417 if (defaultAutoCommit != null && conn.getAutoCommit() != defaultAutoCommit.booleanValue()) { 418 conn.setAutoCommit(defaultAutoCommit.booleanValue()); 419 } 420 if (defaultTransactionIsolation != UNKNOWN_TRANSACTIONISOLATION 421 && conn.getTransactionIsolation() != defaultTransactionIsolation) { 422 conn.setTransactionIsolation(defaultTransactionIsolation); 423 } 424 if (defaultReadOnly != null && conn.isReadOnly() != defaultReadOnly.booleanValue()) { 425 conn.setReadOnly(defaultReadOnly.booleanValue()); 426 } 427 if (defaultCatalog != null && !defaultCatalog.equals(conn.getCatalog())) { 428 conn.setCatalog(defaultCatalog); 429 } 430 conn.setDefaultQueryTimeout(defaultQueryTimeoutSeconds); 431 } 432 433 private void validateLifetime(final PooledObject<PoolableConnection> p) throws Exception { 434 if (maxConnLifetimeMillis > 0) { 435 final long lifetime = System.currentTimeMillis() - p.getCreateTime(); 436 if (lifetime > maxConnLifetimeMillis) { 437 throw new LifetimeExceededException(Utils.getMessage("connectionFactory.lifetimeExceeded", 438 Long.valueOf(lifetime), Long.valueOf(maxConnLifetimeMillis))); 439 } 440 } 441 } 442 443 protected ConnectionFactory getConnectionFactory() { 444 return connectionFactory; 445 } 446 447 protected boolean getPoolStatements() { 448 return poolStatements; 449 } 450 451 protected int getMaxOpenPreparedStatements() { 452 return maxOpenPreparedStatements; 453 } 454 455 protected boolean getCacheState() { 456 return cacheState; 457 } 458 459 protected ObjectName getDataSourceJmxName() { 460 return dataSourceJmxObjectName; 461 } 462 463 protected AtomicLong getConnectionIndex() { 464 return connectionIndex; 465 } 466 467 private final ConnectionFactory connectionFactory; 468 private final ObjectName dataSourceJmxObjectName; 469 private volatile String validationQuery; 470 private volatile int validationQueryTimeoutSeconds = -1; 471 private Collection<String> connectionInitSqls; 472 private Collection<String> disconnectionSqlCodes; 473 private boolean fastFailValidation; 474 private volatile ObjectPool<PoolableConnection> pool; 475 private Boolean defaultReadOnly; 476 private Boolean defaultAutoCommit; 477 private boolean enableAutoCommitOnReturn = true; 478 private boolean rollbackOnReturn = true; 479 private int defaultTransactionIsolation = UNKNOWN_TRANSACTIONISOLATION; 480 private String defaultCatalog; 481 private boolean cacheState; 482 private boolean poolStatements; 483 private int maxOpenPreparedStatements = GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL_PER_KEY; 484 private long maxConnLifetimeMillis = -1; 485 private final AtomicLong connectionIndex = new AtomicLong(0); 486 private Integer defaultQueryTimeoutSeconds; 487 488 /** 489 * Internal constant to indicate the level is not set. 490 */ 491 static final int UNKNOWN_TRANSACTIONISOLATION = -1; 492}