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