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