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 018 package org.apache.commons.dbcp.datasources; 019 020 import java.io.IOException; 021 import java.io.ObjectInputStream; 022 import java.sql.Connection; 023 import java.sql.SQLException; 024 import java.util.HashMap; 025 import java.util.Iterator; 026 import java.util.Map; 027 import java.util.NoSuchElementException; 028 029 import javax.naming.NamingException; 030 import javax.naming.Reference; 031 import javax.naming.StringRefAddr; 032 import javax.sql.ConnectionPoolDataSource; 033 034 import org.apache.commons.dbcp.SQLNestedException; 035 036 import org.apache.commons.pool.ObjectPool; 037 import org.apache.commons.pool.impl.GenericObjectPool; 038 039 /** 040 * <p>A pooling <code>DataSource</code> appropriate for deployment within 041 * J2EE environment. There are many configuration options, most of which are 042 * defined in the parent class. This datasource uses individual pools per 043 * user, and some properties can be set specifically for a given user, if the 044 * deployment environment can support initialization of mapped properties. 045 * So for example, a pool of admin or write-access Connections can be 046 * guaranteed a certain number of connections, separate from a maximum 047 * set for users with read-only connections.</p> 048 * 049 * <p>User passwords can be changed without re-initializing the datasource. 050 * When a <code>getConnection(username, password)</code> request is processed 051 * with a password that is different from those used to create connections in the 052 * pool associated with <code>username</code>, an attempt is made to create a 053 * new connection using the supplied password and if this succeeds, the existing 054 * pool is cleared and a new pool is created for connections using the new password.</p> 055 * 056 * 057 * @author John D. McNally 058 * @version $Revision: 907288 $ $Date: 2010-02-06 14:42:58 -0500 (Sat, 06 Feb 2010) $ 059 */ 060 public class PerUserPoolDataSource 061 extends InstanceKeyDataSource { 062 063 private static final long serialVersionUID = -3104731034410444060L; 064 065 private int defaultMaxActive = GenericObjectPool.DEFAULT_MAX_ACTIVE; 066 private int defaultMaxIdle = GenericObjectPool.DEFAULT_MAX_IDLE; 067 private int defaultMaxWait = (int)Math.min(Integer.MAX_VALUE, 068 GenericObjectPool.DEFAULT_MAX_WAIT); 069 Map perUserDefaultAutoCommit = null; 070 Map perUserDefaultTransactionIsolation = null; 071 Map perUserMaxActive = null; 072 Map perUserMaxIdle = null; 073 Map perUserMaxWait = null; 074 Map perUserDefaultReadOnly = null; 075 076 /** 077 * Map to keep track of Pools for a given user 078 */ 079 private transient Map /* <PoolKey, PooledConnectionManager> */ managers = new HashMap(); 080 081 /** 082 * Default no-arg constructor for Serialization 083 */ 084 public PerUserPoolDataSource() { 085 } 086 087 /** 088 * Close pool(s) being maintained by this datasource. 089 */ 090 public void close() { 091 for (Iterator poolIter = managers.values().iterator(); 092 poolIter.hasNext();) { 093 try { 094 ((CPDSConnectionFactory) poolIter.next()).getPool().close(); 095 } catch (Exception closePoolException) { 096 //ignore and try to close others. 097 } 098 } 099 InstanceKeyObjectFactory.removeInstance(instanceKey); 100 } 101 102 // ------------------------------------------------------------------- 103 // Properties 104 105 /** 106 * The maximum number of active connections that can be allocated from 107 * this pool at the same time, or non-positive for no limit. 108 * This value is used for any username which is not specified 109 * in perUserMaxConnections. 110 */ 111 public int getDefaultMaxActive() { 112 return (this.defaultMaxActive); 113 } 114 115 /** 116 * The maximum number of active connections that can be allocated from 117 * this pool at the same time, or non-positive for no limit. 118 * This value is used for any username which is not specified 119 * in perUserMaxConnections. The default is 8. 120 */ 121 public void setDefaultMaxActive(int maxActive) { 122 assertInitializationAllowed(); 123 this.defaultMaxActive = maxActive; 124 } 125 126 /** 127 * The maximum number of active connections that can remain idle in the 128 * pool, without extra ones being released, or negative for no limit. 129 * This value is used for any username which is not specified 130 * in perUserMaxIdle. 131 */ 132 public int getDefaultMaxIdle() { 133 return (this.defaultMaxIdle); 134 } 135 136 /** 137 * The maximum number of active connections that can remain idle in the 138 * pool, without extra ones being released, or negative for no limit. 139 * This value is used for any username which is not specified 140 * in perUserMaxIdle. The default is 8. 141 */ 142 public void setDefaultMaxIdle(int defaultMaxIdle) { 143 assertInitializationAllowed(); 144 this.defaultMaxIdle = defaultMaxIdle; 145 } 146 147 /** 148 * The maximum number of milliseconds that the pool will wait (when there 149 * are no available connections) for a connection to be returned before 150 * throwing an exception, or -1 to wait indefinitely. Will fail 151 * immediately if value is 0. 152 * This value is used for any username which is not specified 153 * in perUserMaxWait. The default is -1. 154 */ 155 public int getDefaultMaxWait() { 156 return (this.defaultMaxWait); 157 } 158 159 /** 160 * The maximum number of milliseconds that the pool will wait (when there 161 * are no available connections) for a connection to be returned before 162 * throwing an exception, or -1 to wait indefinitely. Will fail 163 * immediately if value is 0. 164 * This value is used for any username which is not specified 165 * in perUserMaxWait. The default is -1. 166 */ 167 public void setDefaultMaxWait(int defaultMaxWait) { 168 assertInitializationAllowed(); 169 this.defaultMaxWait = defaultMaxWait; 170 } 171 172 /** 173 * The keys are usernames and the value is the --. Any 174 * username specified here will override the value of defaultAutoCommit. 175 */ 176 public Boolean getPerUserDefaultAutoCommit(String key) { 177 Boolean value = null; 178 if (perUserDefaultAutoCommit != null) { 179 value = (Boolean) perUserDefaultAutoCommit.get(key); 180 } 181 return value; 182 } 183 184 /** 185 * The keys are usernames and the value is the --. Any 186 * username specified here will override the value of defaultAutoCommit. 187 */ 188 public void setPerUserDefaultAutoCommit(String username, Boolean value) { 189 assertInitializationAllowed(); 190 if (perUserDefaultAutoCommit == null) { 191 perUserDefaultAutoCommit = new HashMap(); 192 } 193 perUserDefaultAutoCommit.put(username, value); 194 } 195 196 /** 197 * The isolation level of connections when returned from getConnection. 198 * If null, the username will use the value of defaultTransactionIsolation. 199 */ 200 public Integer getPerUserDefaultTransactionIsolation(String username) { 201 Integer value = null; 202 if (perUserDefaultTransactionIsolation != null) { 203 value = (Integer) perUserDefaultTransactionIsolation.get(username); 204 } 205 return value; 206 } 207 208 /** 209 * The isolation level of connections when returned from getConnection. 210 * Valid values are the constants defined in Connection. 211 */ 212 public void setPerUserDefaultTransactionIsolation(String username, 213 Integer value) { 214 assertInitializationAllowed(); 215 if (perUserDefaultTransactionIsolation == null) { 216 perUserDefaultTransactionIsolation = new HashMap(); 217 } 218 perUserDefaultTransactionIsolation.put(username, value); 219 } 220 221 /** 222 * The maximum number of active connections that can be allocated from 223 * this pool at the same time, or non-positive for no limit. 224 * The keys are usernames and the value is the maximum connections. Any 225 * username specified here will override the value of defaultMaxActive. 226 */ 227 public Integer getPerUserMaxActive(String username) { 228 Integer value = null; 229 if (perUserMaxActive != null) { 230 value = (Integer) perUserMaxActive.get(username); 231 } 232 return value; 233 } 234 235 /** 236 * The maximum number of active connections that can be allocated from 237 * this pool at the same time, or non-positive for no limit. 238 * The keys are usernames and the value is the maximum connections. Any 239 * username specified here will override the value of defaultMaxActive. 240 */ 241 public void setPerUserMaxActive(String username, Integer value) { 242 assertInitializationAllowed(); 243 if (perUserMaxActive == null) { 244 perUserMaxActive = new HashMap(); 245 } 246 perUserMaxActive.put(username, value); 247 } 248 249 250 /** 251 * The maximum number of active connections that can remain idle in the 252 * pool, without extra ones being released, or negative for no limit. 253 * The keys are usernames and the value is the maximum connections. Any 254 * username specified here will override the value of defaultMaxIdle. 255 */ 256 public Integer getPerUserMaxIdle(String username) { 257 Integer value = null; 258 if (perUserMaxIdle != null) { 259 value = (Integer) perUserMaxIdle.get(username); 260 } 261 return value; 262 } 263 264 /** 265 * The maximum number of active connections that can remain idle in the 266 * pool, without extra ones being released, or negative for no limit. 267 * The keys are usernames and the value is the maximum connections. Any 268 * username specified here will override the value of defaultMaxIdle. 269 */ 270 public void setPerUserMaxIdle(String username, Integer value) { 271 assertInitializationAllowed(); 272 if (perUserMaxIdle == null) { 273 perUserMaxIdle = new HashMap(); 274 } 275 perUserMaxIdle.put(username, value); 276 } 277 278 /** 279 * The maximum number of milliseconds that the pool will wait (when there 280 * are no available connections) for a connection to be returned before 281 * throwing an exception, or -1 to wait indefinitely. Will fail 282 * immediately if value is 0. 283 * The keys are usernames and the value is the maximum connections. Any 284 * username specified here will override the value of defaultMaxWait. 285 */ 286 public Integer getPerUserMaxWait(String username) { 287 Integer value = null; 288 if (perUserMaxWait != null) { 289 value = (Integer) perUserMaxWait.get(username); 290 } 291 return value; 292 } 293 294 /** 295 * The maximum number of milliseconds that the pool will wait (when there 296 * are no available connections) for a connection to be returned before 297 * throwing an exception, or -1 to wait indefinitely. Will fail 298 * immediately if value is 0. 299 * The keys are usernames and the value is the maximum connections. Any 300 * username specified here will override the value of defaultMaxWait. 301 */ 302 public void setPerUserMaxWait(String username, Integer value) { 303 assertInitializationAllowed(); 304 if (perUserMaxWait == null) { 305 perUserMaxWait = new HashMap(); 306 } 307 perUserMaxWait.put(username, value); 308 } 309 310 /** 311 * The keys are usernames and the value is the --. Any 312 * username specified here will override the value of defaultReadOnly. 313 */ 314 public Boolean getPerUserDefaultReadOnly(String username) { 315 Boolean value = null; 316 if (perUserDefaultReadOnly != null) { 317 value = (Boolean) perUserDefaultReadOnly.get(username); 318 } 319 return value; 320 } 321 322 /** 323 * The keys are usernames and the value is the --. Any 324 * username specified here will override the value of defaultReadOnly. 325 */ 326 public void setPerUserDefaultReadOnly(String username, Boolean value) { 327 assertInitializationAllowed(); 328 if (perUserDefaultReadOnly == null) { 329 perUserDefaultReadOnly = new HashMap(); 330 } 331 perUserDefaultReadOnly.put(username, value); 332 } 333 334 // ---------------------------------------------------------------------- 335 // Instrumentation Methods 336 337 /** 338 * Get the number of active connections in the default pool. 339 */ 340 public int getNumActive() { 341 return getNumActive(null, null); 342 } 343 344 /** 345 * Get the number of active connections in the pool for a given user. 346 */ 347 public int getNumActive(String username, String password) { 348 ObjectPool pool = getPool(getPoolKey(username,password)); 349 return (pool == null) ? 0 : pool.getNumActive(); 350 } 351 352 /** 353 * Get the number of idle connections in the default pool. 354 */ 355 public int getNumIdle() { 356 return getNumIdle(null, null); 357 } 358 359 /** 360 * Get the number of idle connections in the pool for a given user. 361 */ 362 public int getNumIdle(String username, String password) { 363 ObjectPool pool = getPool(getPoolKey(username,password)); 364 return (pool == null) ? 0 : pool.getNumIdle(); 365 } 366 367 368 // ---------------------------------------------------------------------- 369 // Inherited abstract methods 370 371 protected PooledConnectionAndInfo 372 getPooledConnectionAndInfo(String username, String password) 373 throws SQLException { 374 375 final PoolKey key = getPoolKey(username,password); 376 ObjectPool pool; 377 PooledConnectionManager manager; 378 synchronized(this) { 379 manager = (PooledConnectionManager) managers.get(key); 380 if (manager == null) { 381 try { 382 registerPool(username, password); 383 manager = (PooledConnectionManager) managers.get(key); 384 } catch (NamingException e) { 385 throw new SQLNestedException("RegisterPool failed", e); 386 } 387 } 388 pool = ((CPDSConnectionFactory) manager).getPool(); 389 } 390 391 PooledConnectionAndInfo info = null; 392 try { 393 info = (PooledConnectionAndInfo) pool.borrowObject(); 394 } 395 catch (NoSuchElementException ex) { 396 throw new SQLNestedException( 397 "Could not retrieve connection info from pool", ex); 398 } 399 catch (Exception e) { 400 // See if failure is due to CPDSConnectionFactory authentication failure 401 try { 402 testCPDS(username, password); 403 } catch (Exception ex) { 404 throw (SQLException) new SQLException( 405 "Could not retrieve connection info from pool").initCause(ex); 406 } 407 // New password works, so kill the old pool, create a new one, and borrow 408 manager.closePool(username); 409 synchronized (this) { 410 managers.remove(key); 411 } 412 try { 413 registerPool(username, password); 414 pool = getPool(key); 415 } catch (NamingException ne) { 416 throw new SQLNestedException("RegisterPool failed", ne); 417 } 418 try { 419 info = (PooledConnectionAndInfo)((ObjectPool) pool).borrowObject(); 420 } catch (Exception ex) { 421 throw (SQLException) new SQLException( 422 "Could not retrieve connection info from pool").initCause(ex); 423 } 424 } 425 return info; 426 } 427 428 protected void setupDefaults(Connection con, String username) 429 throws SQLException { 430 boolean defaultAutoCommit = isDefaultAutoCommit(); 431 if (username != null) { 432 Boolean userMax = getPerUserDefaultAutoCommit(username); 433 if (userMax != null) { 434 defaultAutoCommit = userMax.booleanValue(); 435 } 436 } 437 438 boolean defaultReadOnly = isDefaultReadOnly(); 439 if (username != null) { 440 Boolean userMax = getPerUserDefaultReadOnly(username); 441 if (userMax != null) { 442 defaultReadOnly = userMax.booleanValue(); 443 } 444 } 445 446 int defaultTransactionIsolation = getDefaultTransactionIsolation(); 447 if (username != null) { 448 Integer userMax = getPerUserDefaultTransactionIsolation(username); 449 if (userMax != null) { 450 defaultTransactionIsolation = userMax.intValue(); 451 } 452 } 453 454 if (con.getAutoCommit() != defaultAutoCommit) { 455 con.setAutoCommit(defaultAutoCommit); 456 } 457 458 if (defaultTransactionIsolation != UNKNOWN_TRANSACTIONISOLATION) { 459 con.setTransactionIsolation(defaultTransactionIsolation); 460 } 461 462 if (con.isReadOnly() != defaultReadOnly) { 463 con.setReadOnly(defaultReadOnly); 464 } 465 } 466 467 protected PooledConnectionManager getConnectionManager(UserPassKey upkey) { 468 return (PooledConnectionManager) managers.get(getPoolKey( 469 upkey.getUsername(), upkey.getPassword())); 470 } 471 472 /** 473 * Returns a <code>PerUserPoolDataSource</code> {@link Reference}. 474 * 475 * @since 1.2.2 476 */ 477 public Reference getReference() throws NamingException { 478 Reference ref = new Reference(getClass().getName(), 479 PerUserPoolDataSourceFactory.class.getName(), null); 480 ref.add(new StringRefAddr("instanceKey", instanceKey)); 481 return ref; 482 } 483 484 private PoolKey getPoolKey(String username, String password) { 485 return new PoolKey(getDataSourceName(), username); 486 } 487 488 private synchronized void registerPool( 489 String username, String password) 490 throws javax.naming.NamingException, SQLException { 491 492 ConnectionPoolDataSource cpds = testCPDS(username, password); 493 494 Integer userMax = getPerUserMaxActive(username); 495 int maxActive = (userMax == null) ? 496 getDefaultMaxActive() : userMax.intValue(); 497 userMax = getPerUserMaxIdle(username); 498 int maxIdle = (userMax == null) ? 499 getDefaultMaxIdle() : userMax.intValue(); 500 userMax = getPerUserMaxWait(username); 501 int maxWait = (userMax == null) ? 502 getDefaultMaxWait() : userMax.intValue(); 503 504 // Create an object pool to contain our PooledConnections 505 GenericObjectPool pool = new GenericObjectPool(null); 506 pool.setMaxActive(maxActive); 507 pool.setMaxIdle(maxIdle); 508 pool.setMaxWait(maxWait); 509 pool.setWhenExhaustedAction(whenExhaustedAction(maxActive, maxWait)); 510 pool.setTestOnBorrow(getTestOnBorrow()); 511 pool.setTestOnReturn(getTestOnReturn()); 512 pool.setTimeBetweenEvictionRunsMillis( 513 getTimeBetweenEvictionRunsMillis()); 514 pool.setNumTestsPerEvictionRun(getNumTestsPerEvictionRun()); 515 pool.setMinEvictableIdleTimeMillis(getMinEvictableIdleTimeMillis()); 516 pool.setTestWhileIdle(getTestWhileIdle()); 517 518 // Set up the factory we will use (passing the pool associates 519 // the factory with the pool, so we do not have to do so 520 // explicitly) 521 CPDSConnectionFactory factory = new CPDSConnectionFactory(cpds, pool, getValidationQuery(), 522 isRollbackAfterValidation(), username, password); 523 524 Object old = managers.put(getPoolKey(username,password), factory); 525 if (old != null) { 526 throw new IllegalStateException("Pool already contains an entry for this user/password: "+username); 527 } 528 } 529 530 /** 531 * Supports Serialization interface. 532 * 533 * @param in a <code>java.io.ObjectInputStream</code> value 534 * @exception IOException if an error occurs 535 * @exception ClassNotFoundException if an error occurs 536 */ 537 private void readObject(ObjectInputStream in) 538 throws IOException, ClassNotFoundException { 539 try 540 { 541 in.defaultReadObject(); 542 PerUserPoolDataSource oldDS = (PerUserPoolDataSource) 543 new PerUserPoolDataSourceFactory() 544 .getObjectInstance(getReference(), null, null, null); 545 this.managers = oldDS.managers; 546 } 547 catch (NamingException e) 548 { 549 throw new IOException("NamingException: " + e); 550 } 551 } 552 553 /** 554 * Returns the object pool associated with the given PoolKey. 555 * 556 * @param key PoolKey identifying the pool 557 * @return the GenericObjectPool pooling connections for the username and datasource 558 * specified by the PoolKey 559 */ 560 private GenericObjectPool getPool(PoolKey key) { 561 CPDSConnectionFactory mgr = (CPDSConnectionFactory) managers.get(key); 562 return mgr == null ? null : (GenericObjectPool) mgr.getPool(); 563 } 564 }