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.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 {@link SharedPoolDataSource}s or {@link 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       * @throws ClassNotFoundException
82       *            If a class cannot be found during the deserialization of a configuration parameter.
83       * @throws IOException
84       *            If an I/O error occurs during the deserialization of a configuration parameter.
85       */
86      protected static final Object deserialize(final byte[] data) throws IOException, ClassNotFoundException {
87          ObjectInputStream in = null;
88          try {
89              in = new ObjectInputStream(new ByteArrayInputStream(data));
90              return in.readObject();
91          } finally {
92              Utils.closeQuietly(in);
93          }
94      }
95  
96      static synchronized String registerNewInstance(final InstanceKeyDataSource ds) {
97          int max = 0;
98          for (final String s : INSTANCE_MAP.keySet()) {
99              if (s != null) {
100                 try {
101                     max = Math.max(max, Integer.parseInt(s));
102                 } catch (final NumberFormatException ignored) {
103                     // no sweat, ignore those keys
104                 }
105             }
106         }
107         final String instanceKey = String.valueOf(max + 1);
108         // Put a placeholder here for now, so other instances will not
109         // take our key. We will replace with a pool when ready.
110         INSTANCE_MAP.put(instanceKey, ds);
111         return instanceKey;
112     }
113 
114     static void removeInstance(final String key) {
115         if (key != null) {
116             INSTANCE_MAP.remove(key);
117         }
118     }
119 
120     private Boolean booleanValueOf(final RefAddr refAddr) {
121         return Boolean.valueOf(toString(refAddr));
122     }
123 
124     /**
125      * Creates an instance of the subclass and sets any properties contained in the Reference.
126      *
127      * @param ref
128      *            The properties to be set on the created DataSource
129      *
130      * @return A configured DataSource of the appropriate type.
131      * @throws ClassNotFoundException
132      *            If a class cannot be found during the deserialization of a configuration parameter.
133      * @throws IOException
134      *            If an I/O error occurs during the deserialization of a configuration parameter.
135      */
136     protected abstract InstanceKeyDataSource getNewInstance(Reference ref) throws IOException, ClassNotFoundException;
137 
138     /**
139      * Implements ObjectFactory to create an instance of SharedPoolDataSource or PerUserPoolDataSource
140      */
141     @Override
142     public Object getObjectInstance(final Object refObj, final Name name, final Context context,
143             final Hashtable<?, ?> env) throws IOException, ClassNotFoundException {
144         // The spec says to return null if we can't create an instance
145         // of the reference
146         Object obj = null;
147         if (refObj instanceof Reference) {
148             final Reference ref = (Reference) refObj;
149             if (isCorrectClass(ref.getClassName())) {
150                 final RefAddr refAddr = ref.get("instanceKey");
151                 if (hasContent(refAddr)) {
152                     // object was bound to JNDI via Referenceable API.
153                     obj = INSTANCE_MAP.get(refAddr.getContent());
154                 } else {
155                     // Tomcat JNDI creates a Reference out of server.xml
156                     // <ResourceParam> configuration and passes it to an
157                     // instance of the factory given in server.xml.
158                     String key = null;
159                     if (name != null) {
160                         key = name.toString();
161                         obj = INSTANCE_MAP.get(key);
162                     }
163                     if (obj == null) {
164                         final InstanceKeyDataSource ds = getNewInstance(ref);
165                         setCommonProperties(ref, ds);
166                         obj = ds;
167                         if (key != null) {
168                             INSTANCE_MAP.put(key, ds);
169                         }
170                     }
171                 }
172             }
173         }
174         return obj;
175     }
176 
177     private boolean hasContent(final RefAddr refAddr) {
178         return refAddr != null && refAddr.getContent() != null;
179     }
180 
181     /**
182      * Tests if className is the value returned from getClass().getName().toString().
183      *
184      * @param className
185      *            The class name to test.
186      *
187      * @return true if and only if className is the value returned from getClass().getName().toString()
188      */
189     protected abstract boolean isCorrectClass(String className);
190 
191     boolean parseBoolean(final RefAddr refAddr) {
192         return Boolean.parseBoolean(toString(refAddr));
193     }
194 
195     int parseInt(final RefAddr refAddr) {
196         return Integer.parseInt(toString(refAddr));
197     }
198 
199     long parseLong(final RefAddr refAddr) {
200         return Long.parseLong(toString(refAddr));
201     }
202 
203     private void setCommonProperties(final Reference ref, final InstanceKeyDataSource ikds)
204             throws IOException, ClassNotFoundException {
205 
206         RefAddr refAddr = ref.get("dataSourceName");
207         if (hasContent(refAddr)) {
208             ikds.setDataSourceName(toString(refAddr));
209         }
210 
211         refAddr = ref.get("description");
212         if (hasContent(refAddr)) {
213             ikds.setDescription(toString(refAddr));
214         }
215 
216         refAddr = ref.get("jndiEnvironment");
217         if (hasContent(refAddr)) {
218             final byte[] serialized = (byte[]) refAddr.getContent();
219             ikds.setJndiEnvironment((Properties) deserialize(serialized));
220         }
221 
222         refAddr = ref.get("loginTimeout");
223         if (hasContent(refAddr)) {
224             ikds.setLoginTimeout(toDurationFromSeconds(refAddr));
225         }
226 
227         // Pool properties
228         refAddr = ref.get("blockWhenExhausted");
229         if (hasContent(refAddr)) {
230             ikds.setDefaultBlockWhenExhausted(parseBoolean(refAddr));
231         }
232 
233         refAddr = ref.get("evictionPolicyClassName");
234         if (hasContent(refAddr)) {
235             ikds.setDefaultEvictionPolicyClassName(toString(refAddr));
236         }
237 
238         // Pool properties
239         refAddr = ref.get("lifo");
240         if (hasContent(refAddr)) {
241             ikds.setDefaultLifo(parseBoolean(refAddr));
242         }
243 
244         refAddr = ref.get("maxIdlePerKey");
245         if (hasContent(refAddr)) {
246             ikds.setDefaultMaxIdle(parseInt(refAddr));
247         }
248 
249         refAddr = ref.get("maxTotalPerKey");
250         if (hasContent(refAddr)) {
251             ikds.setDefaultMaxTotal(parseInt(refAddr));
252         }
253 
254         refAddr = ref.get("maxWaitMillis");
255         if (hasContent(refAddr)) {
256             ikds.setDefaultMaxWait(toDurationFromMillis(refAddr));
257         }
258 
259         refAddr = ref.get("minEvictableIdleTimeMillis");
260         if (hasContent(refAddr)) {
261             ikds.setDefaultMinEvictableIdle(toDurationFromMillis(refAddr));
262         }
263 
264         refAddr = ref.get("minIdlePerKey");
265         if (hasContent(refAddr)) {
266             ikds.setDefaultMinIdle(parseInt(refAddr));
267         }
268 
269         refAddr = ref.get("numTestsPerEvictionRun");
270         if (hasContent(refAddr)) {
271             ikds.setDefaultNumTestsPerEvictionRun(parseInt(refAddr));
272         }
273 
274         refAddr = ref.get("softMinEvictableIdleTimeMillis");
275         if (hasContent(refAddr)) {
276             ikds.setDefaultSoftMinEvictableIdle(toDurationFromMillis(refAddr));
277         }
278 
279         refAddr = ref.get("testOnCreate");
280         if (hasContent(refAddr)) {
281             ikds.setDefaultTestOnCreate(parseBoolean(refAddr));
282         }
283 
284         refAddr = ref.get("testOnBorrow");
285         if (hasContent(refAddr)) {
286             ikds.setDefaultTestOnBorrow(parseBoolean(refAddr));
287         }
288 
289         refAddr = ref.get("testOnReturn");
290         if (hasContent(refAddr)) {
291             ikds.setDefaultTestOnReturn(parseBoolean(refAddr));
292         }
293 
294         refAddr = ref.get("testWhileIdle");
295         if (hasContent(refAddr)) {
296             ikds.setDefaultTestWhileIdle(parseBoolean(refAddr));
297         }
298 
299         refAddr = ref.get("timeBetweenEvictionRunsMillis");
300         if (hasContent(refAddr)) {
301             ikds.setDefaultDurationBetweenEvictionRuns(toDurationFromMillis(refAddr));
302         }
303 
304         // Connection factory properties
305 
306         refAddr = ref.get("validationQuery");
307         if (hasContent(refAddr)) {
308             ikds.setValidationQuery(toString(refAddr));
309         }
310 
311         refAddr = ref.get("validationQueryTimeout");
312         if (hasContent(refAddr)) {
313             ikds.setValidationQueryTimeout(toDurationFromSeconds(refAddr));
314         }
315 
316         refAddr = ref.get("rollbackAfterValidation");
317         if (hasContent(refAddr)) {
318             ikds.setRollbackAfterValidation(parseBoolean(refAddr));
319         }
320 
321         refAddr = ref.get("maxConnLifetimeMillis");
322         if (hasContent(refAddr)) {
323             ikds.setMaxConnLifetime(toDurationFromMillis(refAddr));
324         }
325 
326         // Connection properties
327 
328         refAddr = ref.get("defaultAutoCommit");
329         if (hasContent(refAddr)) {
330             ikds.setDefaultAutoCommit(booleanValueOf(refAddr));
331         }
332 
333         refAddr = ref.get("defaultTransactionIsolation");
334         if (hasContent(refAddr)) {
335             ikds.setDefaultTransactionIsolation(parseInt(refAddr));
336         }
337 
338         refAddr = ref.get("defaultReadOnly");
339         if (hasContent(refAddr)) {
340             ikds.setDefaultReadOnly(booleanValueOf(refAddr));
341         }
342     }
343 
344     private Duration toDurationFromMillis(final RefAddr refAddr) {
345         return Duration.ofMillis(parseLong(refAddr));
346     }
347 
348     private Duration toDurationFromSeconds(final RefAddr refAddr) {
349         return Duration.ofSeconds(parseInt(refAddr));
350     }
351 
352     String toString(final RefAddr refAddr) {
353         return refAddr.getContent().toString();
354     }
355 }