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