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    }