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