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  
25  import javax.naming.NamingException;
26  import javax.naming.Reference;
27  import javax.naming.StringRefAddr;
28  import javax.sql.ConnectionPoolDataSource;
29  
30  import org.apache.commons.pool.KeyedObjectPool;
31  import org.apache.commons.pool.impl.GenericKeyedObjectPool;
32  import org.apache.commons.pool.impl.GenericObjectPool;
33  import org.apache.commons.dbcp.SQLNestedException;
34  
35  /**
36   * <p>A pooling <code>DataSource</code> appropriate for deployment within
37   * J2EE environment.  There are many configuration options, most of which are
38   * defined in the parent class. All users (based on username) share a single 
39   * maximum number of Connections in this datasource.</p>
40   * 
41   * <p>User passwords can be changed without re-initializing the datasource.
42   * When a <code>getConnection(username, password)</code> request is processed 
43   * with a password that is different from those used to create connections in the
44   * pool associated with <code>username</code>, an attempt is made to create a
45   * new connection using the supplied password and if this succeeds, idle connections
46   * created using the old password are destroyed and new connections are created
47   * using the new password.</p>
48   *
49   * @author John D. McNally
50   * @version $Revision: 1023401 $ $Date: 2010-10-16 21:54:24 -0400 (Sat, 16 Oct 2010) $
51   */
52  public class SharedPoolDataSource
53      extends InstanceKeyDataSource {
54  
55      private static final long serialVersionUID = -8132305535403690372L;
56  
57      private int maxActive = GenericObjectPool.DEFAULT_MAX_ACTIVE;
58      private int maxIdle = GenericObjectPool.DEFAULT_MAX_IDLE;
59      private int maxWait = (int)Math.min(Integer.MAX_VALUE,
60          GenericObjectPool.DEFAULT_MAX_WAIT);
61      private transient KeyedObjectPool pool = null;
62      private transient KeyedCPDSConnectionFactory factory = null;
63  
64      /**
65       * Default no-arg constructor for Serialization
66       */
67      public SharedPoolDataSource() {
68      }
69  
70      /**
71       * Close pool being maintained by this datasource.
72       */
73      public void close() throws Exception {
74          if (pool != null) {
75              pool.close();
76          }
77          InstanceKeyObjectFactory.removeInstance(instanceKey);
78      }
79  
80      // -------------------------------------------------------------------
81      // Properties
82  
83      /**
84       * The maximum number of active connections that can be allocated from
85       * this pool at the same time, or non-positive for no limit.
86       */
87      public int getMaxActive() {
88          return (this.maxActive);
89      }
90  
91      /**
92       * The maximum number of active connections that can be allocated from
93       * this pool at the same time, or non-positive for no limit.
94       * The default is 8.
95       */
96      public void setMaxActive(int maxActive) {
97          assertInitializationAllowed();
98          this.maxActive = maxActive;
99      }
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