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  package org.apache.commons.dbcp2.datasources;
18  
19  import java.io.IOException;
20  import java.io.ObjectInputStream;
21  import java.sql.Connection;
22  import java.sql.SQLException;
23  
24  import javax.naming.NamingException;
25  import javax.naming.Reference;
26  import javax.naming.StringRefAddr;
27  import javax.sql.ConnectionPoolDataSource;
28  
29  import org.apache.commons.pool2.KeyedObjectPool;
30  import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
31  import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
32  
33  /**
34   * <p>
35   * A pooling {@code DataSource} appropriate for deployment within J2EE environment. There are many configuration
36   * options, most of which are defined in the parent class. All users (based on user name) share a single maximum number
37   * of Connections in this data source.
38   * </p>
39   *
40   * <p>
41   * User passwords can be changed without re-initializing the data source. When a
42   * {@code getConnection(user name, password)} request is processed with a password that is different from those
43   * used to create connections in the pool associated with {@code user name}, an attempt is made to create a new
44   * connection using the supplied password and if this succeeds, idle connections created using the old password are
45   * destroyed and new connections are created using the new password.
46   * </p>
47   *
48   * @since 2.0
49   */
50  public class SharedPoolDataSource extends InstanceKeyDataSource {
51  
52      private static final long serialVersionUID = -1458539734480586454L;
53  
54      // Pool properties
55      private int maxTotal = GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL;
56  
57      private transient KeyedObjectPool<UserPassKey, PooledConnectionAndInfo> pool;
58      private transient KeyedCPDSConnectionFactory factory;
59  
60      /**
61       * Default no-argument constructor for Serialization
62       */
63      public SharedPoolDataSource() {
64          // empty.
65      }
66  
67      /**
68       * Closes pool being maintained by this data source.
69       */
70      @Override
71      public void close() throws SQLException {
72          if (pool != null) {
73              pool.close();
74          }
75          InstanceKeyDataSourceFactory.removeInstance(getInstanceKey());
76      }
77  
78      @Override
79      protected PooledConnectionManager getConnectionManager(final UserPassKey userPassKey) {
80          return factory;
81      }
82  
83      /**
84       * Gets {@link GenericKeyedObjectPool#getMaxTotal()} for this pool.
85       *
86       * @return {@link GenericKeyedObjectPool#getMaxTotal()} for this pool.
87       */
88      public int getMaxTotal() {
89          return this.maxTotal;
90      }
91  
92      // ----------------------------------------------------------------------
93      // Instrumentation Methods
94  
95      /**
96       * Gets the number of active connections in the pool.
97       *
98       * @return The number of active connections in the pool.
99       */
100     public int getNumActive() {
101         return pool == null ? 0 : pool.getNumActive();
102     }
103 
104     /**
105      * Gets the number of idle connections in the pool.
106      *
107      * @return The number of idle connections in the pool.
108      */
109     public int getNumIdle() {
110         return pool == null ? 0 : pool.getNumIdle();
111     }
112 
113     @Override
114     protected PooledConnectionAndInfo getPooledConnectionAndInfo(final String userName, final String userPassword)
115             throws SQLException {
116 
117         synchronized (this) {
118             if (pool == null) {
119                 try {
120                     registerPool(userName, userPassword);
121                 } catch (final NamingException e) {
122                     throw new SQLException("registerPool failed", e);
123                 }
124             }
125         }
126 
127         try {
128             return pool.borrowObject(new UserPassKey(userName, userPassword));
129         } catch (final Exception e) {
130             throw new SQLException("Could not retrieve connection info from pool", e);
131         }
132     }
133 
134     /**
135      * Creates a new {@link Reference} to a {@link SharedPoolDataSource}.
136      */
137     @Override
138     public Reference getReference() throws NamingException {
139         final Reference ref = new Reference(getClass().getName(), SharedPoolDataSourceFactory.class.getName(), null);
140         ref.add(new StringRefAddr("instanceKey", getInstanceKey()));
141         return ref;
142     }
143 
144     /**
145      * Supports Serialization interface.
146      *
147      * @param in
148      *            a {@link java.io.ObjectInputStream} value
149      * @throws IOException
150      *             if an error occurs
151      * @throws ClassNotFoundException
152      *             if an error occurs
153      */
154     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
155         try {
156             in.defaultReadObject();
157             final SharedPoolDataSource oldDS = (SharedPoolDataSource) new SharedPoolDataSourceFactory().getObjectInstance(getReference(), null, null, null);
158             this.pool = oldDS.pool;
159         } catch (final NamingException e) {
160             throw new IOException("NamingException: " + e);
161         }
162     }
163 
164     private void registerPool(final String userName, final String password) throws NamingException, SQLException {
165 
166         final ConnectionPoolDataSource cpds = testCPDS(userName, password);
167 
168         // Create an object pool to contain our PooledConnections
169         factory = new KeyedCPDSConnectionFactory(cpds, getValidationQuery(), getValidationQueryTimeoutDuration(), isRollbackAfterValidation());
170         factory.setMaxConn(getMaxConnDuration());
171 
172         final GenericKeyedObjectPoolConfig<PooledConnectionAndInfo> config = new GenericKeyedObjectPoolConfig<>();
173         config.setBlockWhenExhausted(getDefaultBlockWhenExhausted());
174         config.setEvictionPolicyClassName(getDefaultEvictionPolicyClassName());
175         config.setLifo(getDefaultLifo());
176         config.setMaxIdlePerKey(getDefaultMaxIdle());
177         config.setMaxTotal(getMaxTotal());
178         config.setMaxTotalPerKey(getDefaultMaxTotal());
179         config.setMaxWait(getDefaultMaxWait());
180         config.setMinEvictableIdleDuration(getDefaultMinEvictableIdleDuration());
181         config.setMinIdlePerKey(getDefaultMinIdle());
182         config.setNumTestsPerEvictionRun(getDefaultNumTestsPerEvictionRun());
183         config.setSoftMinEvictableIdleDuration(getDefaultSoftMinEvictableIdleDuration());
184         config.setTestOnCreate(getDefaultTestOnCreate());
185         config.setTestOnBorrow(getDefaultTestOnBorrow());
186         config.setTestOnReturn(getDefaultTestOnReturn());
187         config.setTestWhileIdle(getDefaultTestWhileIdle());
188         config.setTimeBetweenEvictionRuns(getDefaultDurationBetweenEvictionRuns());
189 
190         final KeyedObjectPool<UserPassKey, PooledConnectionAndInfo> tmpPool = new GenericKeyedObjectPool<>(factory, config);
191         factory.setPool(tmpPool);
192         pool = tmpPool;
193     }
194 
195     /**
196      * Sets {@link GenericKeyedObjectPool#getMaxTotal()} for this pool.
197      *
198      * @param maxTotal
199      *            {@link GenericKeyedObjectPool#getMaxTotal()} for this pool.
200      */
201     public void setMaxTotal(final int maxTotal) {
202         assertInitializationAllowed();
203         this.maxTotal = maxTotal;
204     }
205 
206     @Override
207     protected void setupDefaults(final Connection connection, final String userName) throws SQLException {
208         final Boolean defaultAutoCommit = isDefaultAutoCommit();
209         if (defaultAutoCommit != null && connection.getAutoCommit() != defaultAutoCommit) {
210             connection.setAutoCommit(defaultAutoCommit);
211         }
212 
213         final int defaultTransactionIsolation = getDefaultTransactionIsolation();
214         if (defaultTransactionIsolation != UNKNOWN_TRANSACTIONISOLATION) {
215             connection.setTransactionIsolation(defaultTransactionIsolation);
216         }
217 
218         final Boolean defaultReadOnly = isDefaultReadOnly();
219         if (defaultReadOnly != null && connection.isReadOnly() != defaultReadOnly) {
220             connection.setReadOnly(defaultReadOnly);
221         }
222     }
223 
224     @Override
225     protected void toStringFields(final StringBuilder builder) {
226         super.toStringFields(builder);
227         builder.append(", maxTotal=");
228         builder.append(maxTotal);
229     }
230 }