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: 892307 $ $Date: 2013-12-31 23:27:28 +0000 (Tue, 31 Dec 2013) $
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 }