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.ByteArrayInputStream;
20  import java.io.IOException;
21  import java.io.ObjectInputStream;
22  import java.time.Duration;
23  import java.util.ArrayList;
24  import java.util.Hashtable;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Properties;
28  import java.util.concurrent.ConcurrentHashMap;
29  
30  import javax.naming.Context;
31  import javax.naming.Name;
32  import javax.naming.RefAddr;
33  import javax.naming.Reference;
34  import javax.naming.spi.ObjectFactory;
35  
36  import org.apache.commons.dbcp2.ListException;
37  import org.apache.commons.dbcp2.Utils;
38  
39  /**
40   * A JNDI ObjectFactory which creates {@code SharedPoolDataSource}s or {@code PerUserPoolDataSource}s
41   *
42   * @since 2.0
43   */
44  abstract class InstanceKeyDataSourceFactory implements ObjectFactory {
45  
46      private static final Map<String, InstanceKeyDataSource> INSTANCE_MAP = new ConcurrentHashMap<>();
47  
48      /**
49       * Closes all pools associated with this class.
50       *
51       * @throws ListException
52       *             a {@link ListException} containing all exceptions thrown by {@link InstanceKeyDataSource#close()}
53       * @see InstanceKeyDataSource#close()
54       * @since 2.4.0 throws a {@link ListException} instead of, in 2.3.0 and before, the first exception thrown by
55       *        {@link InstanceKeyDataSource#close()}.
56       */
57      public static void closeAll() throws ListException {
58          // Get iterator to loop over all instances of this data source.
59          final List<Throwable> exceptionList = new ArrayList<>(INSTANCE_MAP.size());
60          INSTANCE_MAP.entrySet().forEach(entry -> {
61              // Bullet-proof to avoid anything else but problems from InstanceKeyDataSource#close().
62              if (entry != null) {
63                  @SuppressWarnings("resource")
64                  final InstanceKeyDataSource value = entry.getValue();
65                  Utils.close(value, exceptionList::add);
66              }
67          });
68          INSTANCE_MAP.clear();
69          if (!exceptionList.isEmpty()) {
70              throw new ListException("Could not close all InstanceKeyDataSource instances.", exceptionList);
71          }
72      }
73  
74      /**
75       * Deserializes the provided byte array to create an object.
76       *
77       * @param data
78       *            Data to deserialize to create the configuration parameter.
79       *
80       * @return The Object created by deserializing the data.
81       *
82       * @throws ClassNotFoundException
83       *            If a class cannot be found during the deserialization of a configuration parameter.
84       * @throws IOException
85       *            If an I/O error occurs during the deserialization of a configuration parameter.
86       */
87      protected static final Object deserialize(final byte[] data) throws IOException, ClassNotFoundException {
88          ObjectInputStream in = null;
89          try {
90              in = new ObjectInputStream(new ByteArrayInputStream(data));
91              return in.readObject();
92          } finally {
93              Utils.closeQuietly(in);
94          }
95      }
96  
97      static synchronized String registerNewInstance(final InstanceKeyDataSource ds) {
98          int max = 0;
99          for (final String s : INSTANCE_MAP.keySet()) {
100             if (s != null) {
101                 try {
102                     max = Math.max(max, Integer.parseInt(s));
103                 } catch (final NumberFormatException ignored) {
104                     // no sweat, ignore those keys
105                 }
106             }
107         }
108         final String instanceKey = String.valueOf(max + 1);
109         // Put a placeholder here for now, so other instances will not
110         // take our key. We will replace with a pool when ready.
111         INSTANCE_MAP.put(instanceKey, ds);
112         return instanceKey;
113     }
114 
115     static void removeInstance(final String key) {
116         if (key != null) {
117             INSTANCE_MAP.remove(key);
118         }
119     }
120 
121     private Boolean booleanValueOf(RefAddr refAddr) {
122         return Boolean.valueOf(toString(refAddr));
123     }
124 
125     /**
126      * Creates an instance of the subclass and sets any properties contained in the Reference.
127      *
128      * @param ref
129      *            The properties to be set on the created DataSource
130      *
131      * @return A configured DataSource of the appropriate type.
132      *
133      * @throws ClassNotFoundException
134      *            If a class cannot be found during the deserialization of a configuration parameter.
135      * @throws IOException
136      *            If an I/O error occurs during the deserialization of a configuration parameter.
137      */
138     protected abstract InstanceKeyDataSource getNewInstance(Reference ref) throws IOException, ClassNotFoundException;
139 
140     /**
141      * Implements ObjectFactory to create an instance of SharedPoolDataSource or PerUserPoolDataSource
142      */
143     @Override
144     public Object getObjectInstance(final Object refObj, final Name name, final Context context,
145             final Hashtable<?, ?> env) throws IOException, ClassNotFoundException {
146         // The spec says to return null if we can't create an instance
147         // of the reference
148         Object obj = null;
149         if (refObj instanceof Reference) {
150             final Reference ref = (Reference) refObj;
151             if (isCorrectClass(ref.getClassName())) {
152                 final RefAddr refAddr = ref.get("instanceKey");
153                 if (hasContent(refAddr)) {
154                     // object was bound to JNDI via Referenceable API.
155                     obj = INSTANCE_MAP.get(refAddr.getContent());
156                 } else {
157                     // Tomcat JNDI creates a Reference out of server.xml
158                     // <ResourceParam> configuration and passes it to an
159                     // instance of the factory given in server.xml.
160                     String key = null;
161                     if (name != null) {
162                         key = name.toString();
163                         obj = INSTANCE_MAP.get(key);
164                     }
165                     if (obj == null) {
166                         final InstanceKeyDataSource ds = getNewInstance(ref);
167                         setCommonProperties(ref, ds);
168                         obj = ds;
169                         if (key != null) {
170                             INSTANCE_MAP.put(key, ds);
171                         }
172                     }
173                 }
174             }
175         }
176         return obj;
177     }
178 
179     private boolean hasContent(final RefAddr refAddr) {
180         return refAddr != null && refAddr.getContent() != null;
181     }
182 
183     /**
184      * Tests if className is the value returned from getClass().getName().toString().
185      *
186      * @param className
187      *            The class name to test.
188      *
189      * @return true if and only if className is the value returned from getClass().getName().toString()
190      */
191     protected abstract boolean isCorrectClass(String className);
192 
193     boolean parseBoolean(final RefAddr refAddr) {
194         return Boolean.parseBoolean(toString(refAddr));
195     }
196 
197     int parseInt(final RefAddr refAddr) {
198         return Integer.parseInt(toString(refAddr));
199     }
200 
201     long parseLong(final RefAddr refAddr) {
202         return Long.parseLong(toString(refAddr));
203     }
204 
205     private void setCommonProperties(final Reference ref, final InstanceKeyDataSource ikds)
206             throws IOException, ClassNotFoundException {
207 
208         RefAddr refAddr = ref.get("dataSourceName");
209         if (hasContent(refAddr)) {
210             ikds.setDataSourceName(toString(refAddr));
211         }
212 
213         refAddr = ref.get("description");
214         if (hasContent(refAddr)) {
215             ikds.setDescription(toString(refAddr));
216         }
217 
218         refAddr = ref.get("jndiEnvironment");
219         if (hasContent(refAddr)) {
220             final byte[] serialized = (byte[]) refAddr.getContent();
221             ikds.setJndiEnvironment((Properties) deserialize(serialized));
222         }
223 
224         refAddr = ref.get("loginTimeout");
225         if (hasContent(refAddr)) {
226             ikds.setLoginTimeout(toDurationFromSeconds(refAddr));
227         }
228 
229         // Pool properties
230         refAddr = ref.get("blockWhenExhausted");
231         if (hasContent(refAddr)) {
232             ikds.setDefaultBlockWhenExhausted(parseBoolean(refAddr));
233         }
234 
235         refAddr = ref.get("evictionPolicyClassName");
236         if (hasContent(refAddr)) {
237             ikds.setDefaultEvictionPolicyClassName(toString(refAddr));
238         }
239 
240         // Pool properties
241         refAddr = ref.get("lifo");
242         if (hasContent(refAddr)) {
243             ikds.setDefaultLifo(parseBoolean(refAddr));
244         }
245 
246         refAddr = ref.get("maxIdlePerKey");
247         if (hasContent(refAddr)) {
248             ikds.setDefaultMaxIdle(parseInt(refAddr));
249         }
250 
251         refAddr = ref.get("maxTotalPerKey");
252         if (hasContent(refAddr)) {
253             ikds.setDefaultMaxTotal(parseInt(refAddr));
254         }
255 
256         refAddr = ref.get("maxWaitMillis");
257         if (hasContent(refAddr)) {
258             ikds.setDefaultMaxWait(toDurationFromMillis(refAddr));
259         }
260 
261         refAddr = ref.get("minEvictableIdleTimeMillis");
262         if (hasContent(refAddr)) {
263             ikds.setDefaultMinEvictableIdle(toDurationFromMillis(refAddr));
264         }
265 
266         refAddr = ref.get("minIdlePerKey");
267         if (hasContent(refAddr)) {
268             ikds.setDefaultMinIdle(parseInt(refAddr));
269         }
270 
271         refAddr = ref.get("numTestsPerEvictionRun");
272         if (hasContent(refAddr)) {
273             ikds.setDefaultNumTestsPerEvictionRun(parseInt(refAddr));
274         }
275 
276         refAddr = ref.get("softMinEvictableIdleTimeMillis");
277         if (hasContent(refAddr)) {
278             ikds.setDefaultSoftMinEvictableIdle(toDurationFromMillis(refAddr));
279         }
280 
281         refAddr = ref.get("testOnCreate");
282         if (hasContent(refAddr)) {
283             ikds.setDefaultTestOnCreate(parseBoolean(refAddr));
284         }
285 
286         refAddr = ref.get("testOnBorrow");
287         if (hasContent(refAddr)) {
288             ikds.setDefaultTestOnBorrow(parseBoolean(refAddr));
289         }
290 
291         refAddr = ref.get("testOnReturn");
292         if (hasContent(refAddr)) {
293             ikds.setDefaultTestOnReturn(parseBoolean(refAddr));
294         }
295 
296         refAddr = ref.get("testWhileIdle");
297         if (hasContent(refAddr)) {
298             ikds.setDefaultTestWhileIdle(parseBoolean(refAddr));
299         }
300 
301         refAddr = ref.get("timeBetweenEvictionRunsMillis");
302         if (hasContent(refAddr)) {
303             ikds.setDefaultDurationBetweenEvictionRuns(toDurationFromMillis(refAddr));
304         }
305 
306         // Connection factory properties
307 
308         refAddr = ref.get("validationQuery");
309         if (hasContent(refAddr)) {
310             ikds.setValidationQuery(toString(refAddr));
311         }
312 
313         refAddr = ref.get("validationQueryTimeout");
314         if (hasContent(refAddr)) {
315             ikds.setValidationQueryTimeout(toDurationFromSeconds(refAddr));
316         }
317 
318         refAddr = ref.get("rollbackAfterValidation");
319         if (hasContent(refAddr)) {
320             ikds.setRollbackAfterValidation(parseBoolean(refAddr));
321         }
322 
323         refAddr = ref.get("maxConnLifetimeMillis");
324         if (hasContent(refAddr)) {
325             ikds.setMaxConnLifetime(toDurationFromMillis(refAddr));
326         }
327 
328         // Connection properties
329 
330         refAddr = ref.get("defaultAutoCommit");
331         if (hasContent(refAddr)) {
332             ikds.setDefaultAutoCommit(booleanValueOf(refAddr));
333         }
334 
335         refAddr = ref.get("defaultTransactionIsolation");
336         if (hasContent(refAddr)) {
337             ikds.setDefaultTransactionIsolation(parseInt(refAddr));
338         }
339 
340         refAddr = ref.get("defaultReadOnly");
341         if (hasContent(refAddr)) {
342             ikds.setDefaultReadOnly(booleanValueOf(refAddr));
343         }
344     }
345 
346     private Duration toDurationFromMillis(RefAddr refAddr) {
347         return Duration.ofMillis(parseLong(refAddr));
348     }
349 
350     private Duration toDurationFromSeconds(RefAddr refAddr) {
351         return Duration.ofSeconds(parseInt(refAddr));
352     }
353 
354     String toString(final RefAddr refAddr) {
355         return refAddr.getContent().toString();
356     }
357 }