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