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