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    
025    import javax.naming.NamingException;
026    import javax.naming.Reference;
027    import javax.naming.StringRefAddr;
028    import javax.sql.ConnectionPoolDataSource;
029    
030    import org.apache.commons.pool.KeyedObjectPool;
031    import org.apache.commons.pool.impl.GenericKeyedObjectPool;
032    import org.apache.commons.pool.impl.GenericObjectPool;
033    import org.apache.commons.dbcp.SQLNestedException;
034    
035    /**
036     * <p>A pooling <code>DataSource</code> appropriate for deployment within
037     * J2EE environment.  There are many configuration options, most of which are
038     * defined in the parent class. All users (based on username) share a single 
039     * maximum number of Connections in this datasource.</p>
040     * 
041     * <p>User passwords can be changed without re-initializing the datasource.
042     * When a <code>getConnection(username, password)</code> request is processed 
043     * with a password that is different from those used to create connections in the
044     * pool associated with <code>username</code>, an attempt is made to create a
045     * new connection using the supplied password and if this succeeds, idle connections
046     * created using the old password are destroyed and new connections are created
047     * using the new password.</p>
048     *
049     * @author John D. McNally
050     * @version $Revision: 907288 $ $Date: 2010-02-06 14:42:58 -0500 (Sat, 06 Feb 2010) $
051     */
052    public class SharedPoolDataSource
053        extends InstanceKeyDataSource {
054    
055        private static final long serialVersionUID = -8132305535403690372L;
056    
057        private int maxActive = GenericObjectPool.DEFAULT_MAX_ACTIVE;
058        private int maxIdle = GenericObjectPool.DEFAULT_MAX_IDLE;
059        private int maxWait = (int)Math.min(Integer.MAX_VALUE,
060            GenericObjectPool.DEFAULT_MAX_WAIT);
061        private transient KeyedObjectPool pool = null;
062        private transient KeyedCPDSConnectionFactory factory = null;
063    
064        /**
065         * Default no-arg constructor for Serialization
066         */
067        public SharedPoolDataSource() {
068        }
069    
070        /**
071         * Close pool being maintained by this datasource.
072         */
073        public void close() throws Exception {
074            if (pool != null) {
075                pool.close();
076            }
077            InstanceKeyObjectFactory.removeInstance(instanceKey);
078        }
079    
080        // -------------------------------------------------------------------
081        // Properties
082    
083        /**
084         * The maximum number of active connections that can be allocated from
085         * this pool at the same time, or non-positive for no limit.
086         */
087        public int getMaxActive() {
088            return (this.maxActive);
089        }
090    
091        /**
092         * The maximum number of active connections that can be allocated from
093         * this pool at the same time, or non-positive for no limit.
094         * The default is 8.
095         */
096        public void setMaxActive(int maxActive) {
097            assertInitializationAllowed();
098            this.maxActive = maxActive;
099        }
100    
101        /**
102         * The maximum number of active connections that can remain idle in the
103         * pool, without extra ones being released, or negative for no limit.
104         */
105        public int getMaxIdle() {
106            return (this.maxIdle);
107        }
108    
109        /**
110         * The maximum number of active connections that can remain idle in the
111         * pool, without extra ones being released, or negative for no limit.
112         * The default is 8.
113         */
114        public void setMaxIdle(int maxIdle) {
115            assertInitializationAllowed();
116            this.maxIdle = maxIdle;
117        }
118    
119        /**
120         * The maximum number of milliseconds that the pool will wait (when there
121         * are no available connections) for a connection to be returned before
122         * throwing an exception, or -1 to wait indefinitely.  Will fail 
123         * immediately if value is 0.
124         * The default is -1.
125         */
126        public int getMaxWait() {
127            return (this.maxWait);
128        }
129    
130        /**
131         * The maximum number of milliseconds that the pool will wait (when there
132         * are no available connections) for a connection to be returned before
133         * throwing an exception, or -1 to wait indefinitely.  Will fail 
134         * immediately if value is 0.
135         * The default is -1.
136         */
137        public void setMaxWait(int maxWait) {
138            assertInitializationAllowed();
139            this.maxWait = maxWait;
140        }
141    
142        // ----------------------------------------------------------------------
143        // Instrumentation Methods
144    
145        /**
146         * Get the number of active connections in the pool.
147         */
148        public int getNumActive() {
149            return (pool == null) ? 0 : pool.getNumActive();
150        }
151    
152        /**
153         * Get the number of idle connections in the pool.
154         */
155        public int getNumIdle() {
156            return (pool == null) ? 0 : pool.getNumIdle();
157        }
158    
159        // ----------------------------------------------------------------------
160        // Inherited abstract methods
161    
162        protected PooledConnectionAndInfo 
163            getPooledConnectionAndInfo(String username, String password)
164            throws SQLException {
165            
166            synchronized(this) {
167                if (pool == null) {
168                    try {
169                        registerPool(username, password);
170                    } catch (NamingException e) {
171                        throw new SQLNestedException("RegisterPool failed", e);
172                    }
173                }
174            }
175    
176            PooledConnectionAndInfo info = null;
177            
178            UserPassKey key = new UserPassKey(username, password);
179            
180            try {
181                info = (PooledConnectionAndInfo) pool.borrowObject(key);
182            }
183            catch (Exception e) {
184                throw new SQLNestedException(
185                        "Could not retrieve connection info from pool", e);
186            }
187            return info;
188        }
189        
190        protected PooledConnectionManager getConnectionManager(UserPassKey upkey)  {
191            return factory;
192        }
193    
194        /**
195         * Returns a <code>SharedPoolDataSource</code> {@link Reference}.
196         * 
197         * @since 1.2.2
198         */
199        public Reference getReference() throws NamingException {
200            Reference ref = new Reference(getClass().getName(),
201                SharedPoolDataSourceFactory.class.getName(), null);
202            ref.add(new StringRefAddr("instanceKey", instanceKey));
203            return ref;
204        }
205        
206        private void registerPool(
207            String username, String password) 
208            throws javax.naming.NamingException, SQLException {
209    
210            ConnectionPoolDataSource cpds = testCPDS(username, password);
211    
212            // Create an object pool to contain our PooledConnections
213            GenericKeyedObjectPool tmpPool = new GenericKeyedObjectPool(null);
214            tmpPool.setMaxActive(getMaxActive());
215            tmpPool.setMaxIdle(getMaxIdle());
216            tmpPool.setMaxWait(getMaxWait());
217            tmpPool.setWhenExhaustedAction(whenExhaustedAction(maxActive, maxWait));
218            tmpPool.setTestOnBorrow(getTestOnBorrow());
219            tmpPool.setTestOnReturn(getTestOnReturn());
220            tmpPool.setTimeBetweenEvictionRunsMillis(
221                getTimeBetweenEvictionRunsMillis());
222            tmpPool.setNumTestsPerEvictionRun(getNumTestsPerEvictionRun());
223            tmpPool.setMinEvictableIdleTimeMillis(getMinEvictableIdleTimeMillis());
224            tmpPool.setTestWhileIdle(getTestWhileIdle());
225            pool = tmpPool;
226            // Set up the factory we will use (passing the pool associates
227            // the factory with the pool, so we do not have to do so
228            // explicitly)
229            factory = new KeyedCPDSConnectionFactory(cpds, pool, getValidationQuery(),
230                                           isRollbackAfterValidation());
231        }
232    
233        protected void setupDefaults(Connection con, String username) throws SQLException {
234            boolean defaultAutoCommit = isDefaultAutoCommit();
235            if (con.getAutoCommit() != defaultAutoCommit) {
236                con.setAutoCommit(defaultAutoCommit);
237            }
238    
239            int defaultTransactionIsolation = getDefaultTransactionIsolation();
240            if (defaultTransactionIsolation != UNKNOWN_TRANSACTIONISOLATION) {
241                con.setTransactionIsolation(defaultTransactionIsolation);
242            }
243    
244            boolean defaultReadOnly = isDefaultReadOnly();
245            if (con.isReadOnly() != defaultReadOnly) {
246                con.setReadOnly(defaultReadOnly);
247            }
248        }
249    
250        /**
251         * Supports Serialization interface.
252         *
253         * @param in a <code>java.io.ObjectInputStream</code> value
254         * @exception IOException if an error occurs
255         * @exception ClassNotFoundException if an error occurs
256         */
257        private void readObject(ObjectInputStream in)
258            throws IOException, ClassNotFoundException {
259            try 
260            {
261                in.defaultReadObject();
262                SharedPoolDataSource oldDS = (SharedPoolDataSource)
263                    new SharedPoolDataSourceFactory()
264                        .getObjectInstance(getReference(), null, null, null);
265                this.pool = oldDS.pool;
266            }
267            catch (NamingException e)
268            {
269                throw new IOException("NamingException: " + e);
270            }
271        }
272    }
273