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