View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.dbcp.datasources;
19  
20  import java.io.IOException;
21  import java.io.ObjectInputStream;
22  import java.sql.Connection;
23  import java.sql.SQLException;
24  import java.util.HashMap;
25  import java.util.Iterator;
26  import java.util.Map;
27  import java.util.NoSuchElementException;
28  
29  import javax.naming.NamingException;
30  import javax.naming.Reference;
31  import javax.naming.StringRefAddr;
32  import javax.sql.ConnectionPoolDataSource;
33  
34  import org.apache.commons.dbcp.SQLNestedException;
35  
36  import org.apache.commons.pool.ObjectPool;
37  import org.apache.commons.pool.impl.GenericObjectPool;
38  
39  /**
40   * <p>A pooling <code>DataSource</code> appropriate for deployment within
41   * J2EE environment.  There are many configuration options, most of which are
42   * defined in the parent class.  This datasource uses individual pools per 
43   * user, and some properties can be set specifically for a given user, if the 
44   * deployment environment can support initialization of mapped properties.
45   * So for example, a pool of admin or write-access Connections can be
46   * guaranteed a certain number of connections, separate from a maximum
47   * set for users with read-only connections.</p>
48   * 
49   * <p>User passwords can be changed without re-initializing the datasource.
50   * When a <code>getConnection(username, password)</code> request is processed 
51   * with a password that is different from those used to create connections in the
52   * pool associated with <code>username</code>, an attempt is made to create a
53   * new connection using the supplied password and if this succeeds, the existing
54   * pool is cleared and a new pool is created for connections using the new password.</p>
55   * 
56   *
57   * @author John D. McNally
58   * @version $Revision: 1023401 $ $Date: 2010-10-16 21:54:24 -0400 (Sat, 16 Oct 2010) $
59   */
60  public class PerUserPoolDataSource
61      extends InstanceKeyDataSource {
62  
63      private static final long serialVersionUID = -3104731034410444060L;
64  
65      private int defaultMaxActive = GenericObjectPool.DEFAULT_MAX_ACTIVE;
66      private int defaultMaxIdle = GenericObjectPool.DEFAULT_MAX_IDLE;
67      private int defaultMaxWait = (int)Math.min(Integer.MAX_VALUE,
68          GenericObjectPool.DEFAULT_MAX_WAIT);
69      Map perUserDefaultAutoCommit = null;    
70      Map perUserDefaultTransactionIsolation = null;
71      Map perUserMaxActive = null;    
72      Map perUserMaxIdle = null;    
73      Map perUserMaxWait = null;
74      Map perUserDefaultReadOnly = null;    
75  
76      /**
77       * Map to keep track of Pools for a given user
78       */
79      private transient Map /* <PoolKey, PooledConnectionManager> */ managers = new HashMap();
80  
81      /**
82       * Default no-arg constructor for Serialization
83       */
84      public PerUserPoolDataSource() {
85      }
86  
87      /**
88       * Close pool(s) being maintained by this datasource.
89       */
90      public void close() {
91          for (Iterator poolIter = managers.values().iterator();
92               poolIter.hasNext();) {    
93              try {
94                ((CPDSConnectionFactory) poolIter.next()).getPool().close();
95              } catch (Exception closePoolException) {
96                      //ignore and try to close others.
97              }
98          }
99          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 }