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;
18  
19  import java.io.ByteArrayInputStream;
20  import java.io.IOException;
21  import java.nio.charset.StandardCharsets;
22  import java.sql.Connection;
23  import java.sql.SQLException;
24  import java.time.Duration;
25  import java.util.ArrayList;
26  import java.util.Arrays;
27  import java.util.Enumeration;
28  import java.util.Hashtable;
29  import java.util.LinkedHashMap;
30  import java.util.List;
31  import java.util.Locale;
32  import java.util.Map;
33  import java.util.Objects;
34  import java.util.Optional;
35  import java.util.Properties;
36  import java.util.StringTokenizer;
37  import java.util.function.Consumer;
38  import java.util.function.Function;
39  
40  import javax.naming.Context;
41  import javax.naming.Name;
42  import javax.naming.RefAddr;
43  import javax.naming.Reference;
44  import javax.naming.spi.ObjectFactory;
45  
46  import org.apache.commons.logging.Log;
47  import org.apache.commons.logging.LogFactory;
48  import org.apache.commons.pool2.impl.BaseObjectPoolConfig;
49  import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
50  
51  /**
52   * JNDI object factory that creates an instance of {@link BasicDataSource} that has been configured based on the
53   * {@link RefAddr} values of the specified {@code Reference}, which must match the names and data types of the
54   * {@link BasicDataSource} bean properties with the following exceptions:
55   * <ul>
56   * <li>{@code connectionInitSqls} must be passed to this factory as a single String using semicolon to delimit the
57   * statements whereas {@link BasicDataSource} requires a collection of Strings.</li>
58   * </ul>
59   *
60   * @since 2.0
61   */
62  public class BasicDataSourceFactory implements ObjectFactory {
63  
64      private static final Log log = LogFactory.getLog(BasicDataSourceFactory.class);
65  
66      private static final String PROP_DEFAULT_AUTO_COMMIT = "defaultAutoCommit";
67      private static final String PROP_DEFAULT_READ_ONLY = "defaultReadOnly";
68      private static final String PROP_DEFAULT_TRANSACTION_ISOLATION = "defaultTransactionIsolation";
69      private static final String PROP_DEFAULT_CATALOG = "defaultCatalog";
70      private static final String PROP_DEFAULT_SCHEMA = "defaultSchema";
71      private static final String PROP_CACHE_STATE = "cacheState";
72      private static final String PROP_DRIVER_CLASS_NAME = "driverClassName";
73      private static final String PROP_LIFO = "lifo";
74      private static final String PROP_MAX_TOTAL = "maxTotal";
75      private static final String PROP_MAX_IDLE = "maxIdle";
76      private static final String PROP_MIN_IDLE = "minIdle";
77      private static final String PROP_INITIAL_SIZE = "initialSize";
78      private static final String PROP_MAX_WAIT_MILLIS = "maxWaitMillis";
79      private static final String PROP_TEST_ON_CREATE = "testOnCreate";
80      private static final String PROP_TEST_ON_BORROW = "testOnBorrow";
81      private static final String PROP_TEST_ON_RETURN = "testOnReturn";
82      private static final String PROP_TIME_BETWEEN_EVICTION_RUNS_MILLIS = "timeBetweenEvictionRunsMillis";
83      private static final String PROP_NUM_TESTS_PER_EVICTION_RUN = "numTestsPerEvictionRun";
84      private static final String PROP_MIN_EVICTABLE_IDLE_TIME_MILLIS = "minEvictableIdleTimeMillis";
85      private static final String PROP_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS = "softMinEvictableIdleTimeMillis";
86      private static final String PROP_EVICTION_POLICY_CLASS_NAME = "evictionPolicyClassName";
87      private static final String PROP_TEST_WHILE_IDLE = "testWhileIdle";
88      private static final String PROP_PASSWORD = Constants.KEY_PASSWORD;
89      private static final String PROP_URL = "url";
90      private static final String PROP_USER_NAME = "username";
91      private static final String PROP_VALIDATION_QUERY = "validationQuery";
92      private static final String PROP_VALIDATION_QUERY_TIMEOUT = "validationQueryTimeout";
93      private static final String PROP_JMX_NAME = "jmxName";
94      private static final String PROP_REGISTER_CONNECTION_MBEAN = "registerConnectionMBean";
95      private static final String PROP_CONNECTION_FACTORY_CLASS_NAME = "connectionFactoryClassName";
96  
97      /**
98       * The property name for connectionInitSqls. The associated value String must be of the form [query;]*
99       */
100     private static final String PROP_CONNECTION_INIT_SQLS = "connectionInitSqls";
101     private static final String PROP_ACCESS_TO_UNDERLYING_CONNECTION_ALLOWED = "accessToUnderlyingConnectionAllowed";
102     private static final String PROP_REMOVE_ABANDONED_ON_BORROW = "removeAbandonedOnBorrow";
103     private static final String PROP_REMOVE_ABANDONED_ON_MAINTENANCE = "removeAbandonedOnMaintenance";
104     private static final String PROP_REMOVE_ABANDONED_TIMEOUT = "removeAbandonedTimeout";
105     private static final String PROP_LOG_ABANDONED = "logAbandoned";
106     private static final String PROP_ABANDONED_USAGE_TRACKING = "abandonedUsageTracking";
107     private static final String PROP_POOL_PREPARED_STATEMENTS = "poolPreparedStatements";
108     private static final String PROP_CLEAR_STATEMENT_POOL_ON_RETURN = "clearStatementPoolOnReturn";
109     private static final String PROP_MAX_OPEN_PREPARED_STATEMENTS = "maxOpenPreparedStatements";
110     private static final String PROP_CONNECTION_PROPERTIES = "connectionProperties";
111     private static final String PROP_MAX_CONN_LIFETIME_MILLIS = "maxConnLifetimeMillis";
112     private static final String PROP_LOG_EXPIRED_CONNECTIONS = "logExpiredConnections";
113     private static final String PROP_ROLLBACK_ON_RETURN = "rollbackOnReturn";
114     private static final String PROP_ENABLE_AUTO_COMMIT_ON_RETURN = "enableAutoCommitOnReturn";
115     private static final String PROP_DEFAULT_QUERY_TIMEOUT = "defaultQueryTimeout";
116     private static final String PROP_FAST_FAIL_VALIDATION = "fastFailValidation";
117 
118     /**
119      * Value string must be of the form [STATE_CODE,]*
120      */
121     private static final String PROP_DISCONNECTION_SQL_CODES = "disconnectionSqlCodes";
122 
123     /**
124      * Property key for specifying the SQL State codes that should be ignored during disconnection checks.
125      * <p>
126      * The value for this property must be a comma-separated string of SQL State codes, where each code represents
127      * a state that will be excluded from being treated as a fatal disconnection. The expected format is a series
128      * of SQL State codes separated by commas, with no spaces between them (e.g., "08003,08004").
129      * </p>
130      * @since 2.13.0
131      */
132     private static final String PROP_DISCONNECTION_IGNORE_SQL_CODES = "disconnectionIgnoreSqlCodes";
133 
134     /*
135      * Block with obsolete properties from DBCP 1.x. Warn users that these are ignored and they should use the 2.x
136      * properties.
137      */
138     private static final String NUPROP_MAX_ACTIVE = "maxActive";
139     private static final String NUPROP_REMOVE_ABANDONED = "removeAbandoned";
140     private static final String NUPROP_MAXWAIT = "maxWait";
141 
142     /*
143      * Block with properties expected in a DataSource This props will not be listed as ignored - we know that they may
144      * appear in Resource, and not listing them as ignored.
145      */
146     private static final String SILENT_PROP_FACTORY = "factory";
147     private static final String SILENT_PROP_SCOPE = "scope";
148     private static final String SILENT_PROP_SINGLETON = "singleton";
149     private static final String SILENT_PROP_AUTH = "auth";
150 
151     private static final List<String> ALL_PROPERTY_NAMES = Arrays.asList(PROP_DEFAULT_AUTO_COMMIT, PROP_DEFAULT_READ_ONLY,
152             PROP_DEFAULT_TRANSACTION_ISOLATION, PROP_DEFAULT_CATALOG, PROP_DEFAULT_SCHEMA, PROP_CACHE_STATE,
153             PROP_DRIVER_CLASS_NAME, PROP_LIFO, PROP_MAX_TOTAL, PROP_MAX_IDLE, PROP_MIN_IDLE, PROP_INITIAL_SIZE,
154             PROP_MAX_WAIT_MILLIS, PROP_TEST_ON_CREATE, PROP_TEST_ON_BORROW, PROP_TEST_ON_RETURN,
155             PROP_TIME_BETWEEN_EVICTION_RUNS_MILLIS, PROP_NUM_TESTS_PER_EVICTION_RUN, PROP_MIN_EVICTABLE_IDLE_TIME_MILLIS,
156             PROP_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS, PROP_EVICTION_POLICY_CLASS_NAME, PROP_TEST_WHILE_IDLE, PROP_PASSWORD,
157             PROP_URL, PROP_USER_NAME, PROP_VALIDATION_QUERY, PROP_VALIDATION_QUERY_TIMEOUT, PROP_CONNECTION_INIT_SQLS,
158             PROP_ACCESS_TO_UNDERLYING_CONNECTION_ALLOWED, PROP_REMOVE_ABANDONED_ON_BORROW, PROP_REMOVE_ABANDONED_ON_MAINTENANCE,
159             PROP_REMOVE_ABANDONED_TIMEOUT, PROP_LOG_ABANDONED, PROP_ABANDONED_USAGE_TRACKING, PROP_POOL_PREPARED_STATEMENTS,
160             PROP_CLEAR_STATEMENT_POOL_ON_RETURN,
161             PROP_MAX_OPEN_PREPARED_STATEMENTS, PROP_CONNECTION_PROPERTIES, PROP_MAX_CONN_LIFETIME_MILLIS,
162             PROP_LOG_EXPIRED_CONNECTIONS, PROP_ROLLBACK_ON_RETURN, PROP_ENABLE_AUTO_COMMIT_ON_RETURN,
163             PROP_DEFAULT_QUERY_TIMEOUT, PROP_FAST_FAIL_VALIDATION, PROP_DISCONNECTION_SQL_CODES, PROP_DISCONNECTION_IGNORE_SQL_CODES,
164             PROP_JMX_NAME, PROP_REGISTER_CONNECTION_MBEAN, PROP_CONNECTION_FACTORY_CLASS_NAME);
165 
166     /**
167      * Obsolete properties from DBCP 1.x. with warning strings suggesting new properties. LinkedHashMap will guarantee
168      * that properties will be listed to output in order of insertion into map.
169      */
170     private static final Map<String, String> NUPROP_WARNTEXT = new LinkedHashMap<>();
171 
172     static {
173         NUPROP_WARNTEXT.put(NUPROP_MAX_ACTIVE,
174                 "Property " + NUPROP_MAX_ACTIVE + " is not used in DBCP2, use " + PROP_MAX_TOTAL + " instead. "
175                         + PROP_MAX_TOTAL + " default value is " + GenericObjectPoolConfig.DEFAULT_MAX_TOTAL + ".");
176         NUPROP_WARNTEXT.put(NUPROP_REMOVE_ABANDONED,
177                 "Property " + NUPROP_REMOVE_ABANDONED + " is not used in DBCP2, use one or both of "
178                         + PROP_REMOVE_ABANDONED_ON_BORROW + " or " + PROP_REMOVE_ABANDONED_ON_MAINTENANCE + " instead. "
179                         + "Both have default value set to false.");
180         NUPROP_WARNTEXT.put(NUPROP_MAXWAIT,
181                 "Property " + NUPROP_MAXWAIT + " is not used in DBCP2 , use " + PROP_MAX_WAIT_MILLIS + " instead. "
182                         + PROP_MAX_WAIT_MILLIS + " default value is " + BaseObjectPoolConfig.DEFAULT_MAX_WAIT
183                         + ".");
184     }
185 
186     /**
187      * Silent Properties. These properties will not be listed as ignored - we know that they may appear in JDBC Resource
188      * references, and we will not list them as ignored.
189      */
190     private static final List<String> SILENT_PROPERTIES = new ArrayList<>();
191 
192     static {
193         SILENT_PROPERTIES.add(SILENT_PROP_FACTORY);
194         SILENT_PROPERTIES.add(SILENT_PROP_SCOPE);
195         SILENT_PROPERTIES.add(SILENT_PROP_SINGLETON);
196         SILENT_PROPERTIES.add(SILENT_PROP_AUTH);
197 
198     }
199 
200     private static <V> void accept(final Properties properties, final String name, final Function<String, V> parser, final Consumer<V> consumer) {
201         getOptional(properties, name).ifPresent(v -> consumer.accept(parser.apply(v)));
202     }
203 
204     private static void acceptBoolean(final Properties properties, final String name, final Consumer<Boolean> consumer) {
205         accept(properties, name, Boolean::parseBoolean, consumer);
206     }
207 
208     private static void acceptDurationOfMillis(final Properties properties, final String name, final Consumer<Duration> consumer) {
209         accept(properties, name, s -> Duration.ofMillis(Long.parseLong(s)), consumer);
210     }
211 
212     private static void acceptDurationOfSeconds(final Properties properties, final String name, final Consumer<Duration> consumer) {
213         accept(properties, name, s -> Duration.ofSeconds(Long.parseLong(s)), consumer);
214     }
215 
216     private static void acceptInt(final Properties properties, final String name, final Consumer<Integer> consumer) {
217         accept(properties, name, Integer::parseInt, consumer);
218     }
219 
220     private static void acceptString(final Properties properties, final String name, final Consumer<String> consumer) {
221         accept(properties, name, Function.identity(), consumer);
222     }
223 
224     /**
225      * Creates and configures a {@link BasicDataSource} instance based on the given properties.
226      *
227      * @param properties
228      *            The data source configuration properties.
229      * @return A new a {@link BasicDataSource} instance based on the given properties.
230      * @throws SQLException
231      *             Thrown when an error occurs creating the data source.
232      */
233     public static BasicDataSource createDataSource(final Properties properties) throws SQLException {
234         final BasicDataSource dataSource = new BasicDataSource();
235         acceptBoolean(properties, PROP_DEFAULT_AUTO_COMMIT, dataSource::setDefaultAutoCommit);
236         acceptBoolean(properties, PROP_DEFAULT_READ_ONLY, dataSource::setDefaultReadOnly);
237 
238         getOptional(properties, PROP_DEFAULT_TRANSACTION_ISOLATION).ifPresent(value -> {
239             value = value.toUpperCase(Locale.ROOT);
240             int level = PoolableConnectionFactory.UNKNOWN_TRANSACTION_ISOLATION;
241             switch (value) {
242             case "NONE":
243                 level = Connection.TRANSACTION_NONE;
244                 break;
245             case "READ_COMMITTED":
246                 level = Connection.TRANSACTION_READ_COMMITTED;
247                 break;
248             case "READ_UNCOMMITTED":
249                 level = Connection.TRANSACTION_READ_UNCOMMITTED;
250                 break;
251             case "REPEATABLE_READ":
252                 level = Connection.TRANSACTION_REPEATABLE_READ;
253                 break;
254             case "SERIALIZABLE":
255                 level = Connection.TRANSACTION_SERIALIZABLE;
256                 break;
257             default:
258                 try {
259                     level = Integer.parseInt(value);
260                 } catch (final NumberFormatException e) {
261                     System.err.println("Could not parse defaultTransactionIsolation: " + value);
262                     System.err.println("WARNING: defaultTransactionIsolation not set");
263                     System.err.println("using default value of database driver");
264                     level = PoolableConnectionFactory.UNKNOWN_TRANSACTION_ISOLATION;
265                 }
266                 break;
267             }
268             dataSource.setDefaultTransactionIsolation(level);
269         });
270 
271         acceptString(properties, PROP_DEFAULT_SCHEMA, dataSource::setDefaultSchema);
272         acceptString(properties, PROP_DEFAULT_CATALOG, dataSource::setDefaultCatalog);
273         acceptBoolean(properties, PROP_CACHE_STATE, dataSource::setCacheState);
274         acceptString(properties, PROP_DRIVER_CLASS_NAME, dataSource::setDriverClassName);
275         acceptBoolean(properties, PROP_LIFO, dataSource::setLifo);
276         acceptInt(properties, PROP_MAX_TOTAL, dataSource::setMaxTotal);
277         acceptInt(properties, PROP_MAX_IDLE, dataSource::setMaxIdle);
278         acceptInt(properties, PROP_MIN_IDLE, dataSource::setMinIdle);
279         acceptInt(properties, PROP_INITIAL_SIZE, dataSource::setInitialSize);
280         acceptDurationOfMillis(properties, PROP_MAX_WAIT_MILLIS, dataSource::setMaxWait);
281         acceptBoolean(properties, PROP_TEST_ON_CREATE, dataSource::setTestOnCreate);
282         acceptBoolean(properties, PROP_TEST_ON_BORROW, dataSource::setTestOnBorrow);
283         acceptBoolean(properties, PROP_TEST_ON_RETURN, dataSource::setTestOnReturn);
284         acceptDurationOfMillis(properties, PROP_TIME_BETWEEN_EVICTION_RUNS_MILLIS, dataSource::setDurationBetweenEvictionRuns);
285         acceptInt(properties, PROP_NUM_TESTS_PER_EVICTION_RUN, dataSource::setNumTestsPerEvictionRun);
286         acceptDurationOfMillis(properties, PROP_MIN_EVICTABLE_IDLE_TIME_MILLIS, dataSource::setMinEvictableIdle);
287         acceptDurationOfMillis(properties, PROP_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS, dataSource::setSoftMinEvictableIdle);
288         acceptString(properties, PROP_EVICTION_POLICY_CLASS_NAME, dataSource::setEvictionPolicyClassName);
289         acceptBoolean(properties, PROP_TEST_WHILE_IDLE, dataSource::setTestWhileIdle);
290         acceptString(properties, PROP_PASSWORD, dataSource::setPassword);
291         acceptString(properties, PROP_URL, dataSource::setUrl);
292         acceptString(properties, PROP_USER_NAME, dataSource::setUsername);
293         acceptString(properties, PROP_VALIDATION_QUERY, dataSource::setValidationQuery);
294         acceptDurationOfSeconds(properties, PROP_VALIDATION_QUERY_TIMEOUT, dataSource::setValidationQueryTimeout);
295         acceptBoolean(properties, PROP_ACCESS_TO_UNDERLYING_CONNECTION_ALLOWED, dataSource::setAccessToUnderlyingConnectionAllowed);
296         acceptBoolean(properties, PROP_REMOVE_ABANDONED_ON_BORROW, dataSource::setRemoveAbandonedOnBorrow);
297         acceptBoolean(properties, PROP_REMOVE_ABANDONED_ON_MAINTENANCE, dataSource::setRemoveAbandonedOnMaintenance);
298         acceptDurationOfSeconds(properties, PROP_REMOVE_ABANDONED_TIMEOUT, dataSource::setRemoveAbandonedTimeout);
299         acceptBoolean(properties, PROP_LOG_ABANDONED, dataSource::setLogAbandoned);
300         acceptBoolean(properties, PROP_ABANDONED_USAGE_TRACKING, dataSource::setAbandonedUsageTracking);
301         acceptBoolean(properties, PROP_POOL_PREPARED_STATEMENTS, dataSource::setPoolPreparedStatements);
302         acceptBoolean(properties, PROP_CLEAR_STATEMENT_POOL_ON_RETURN, dataSource::setClearStatementPoolOnReturn);
303         acceptInt(properties, PROP_MAX_OPEN_PREPARED_STATEMENTS, dataSource::setMaxOpenPreparedStatements);
304         getOptional(properties, PROP_CONNECTION_INIT_SQLS).ifPresent(v -> dataSource.setConnectionInitSqls(parseList(v, ';')));
305 
306         final String value = properties.getProperty(PROP_CONNECTION_PROPERTIES);
307         if (value != null) {
308             final Properties connectionProperties = getProperties(value);
309             for (final Object key : connectionProperties.keySet()) {
310                 final String propertyName = Objects.toString(key);
311                 dataSource.addConnectionProperty(propertyName, connectionProperties.getProperty(propertyName));
312             }
313         }
314 
315         acceptDurationOfMillis(properties, PROP_MAX_CONN_LIFETIME_MILLIS, dataSource::setMaxConn);
316         acceptBoolean(properties, PROP_LOG_EXPIRED_CONNECTIONS, dataSource::setLogExpiredConnections);
317         acceptString(properties, PROP_JMX_NAME, dataSource::setJmxName);
318         acceptBoolean(properties, PROP_REGISTER_CONNECTION_MBEAN, dataSource::setRegisterConnectionMBean);
319         acceptBoolean(properties, PROP_ENABLE_AUTO_COMMIT_ON_RETURN, dataSource::setAutoCommitOnReturn);
320         acceptBoolean(properties, PROP_ROLLBACK_ON_RETURN, dataSource::setRollbackOnReturn);
321         acceptDurationOfSeconds(properties, PROP_DEFAULT_QUERY_TIMEOUT, dataSource::setDefaultQueryTimeout);
322         acceptBoolean(properties, PROP_FAST_FAIL_VALIDATION, dataSource::setFastFailValidation);
323         getOptional(properties, PROP_DISCONNECTION_SQL_CODES).ifPresent(v -> dataSource.setDisconnectionSqlCodes(parseList(v, ',')));
324         getOptional(properties, PROP_DISCONNECTION_IGNORE_SQL_CODES).ifPresent(v -> dataSource.setDisconnectionIgnoreSqlCodes(parseList(v, ',')));
325         acceptString(properties, PROP_CONNECTION_FACTORY_CLASS_NAME, dataSource::setConnectionFactoryClassName);
326 
327         // DBCP-215
328         // Trick to make sure that initialSize connections are created
329         if (dataSource.getInitialSize() > 0) {
330             dataSource.getLogWriter();
331         }
332 
333         // Return the configured DataSource instance
334         return dataSource;
335     }
336 
337     private static Optional<String> getOptional(final Properties properties, final String name) {
338         return Optional.ofNullable(properties.getProperty(name));
339     }
340 
341     /**
342      * Parse properties from the string. Format of the string must be [propertyName=property;]*
343      *
344      * @param propText The source text
345      * @return Properties A new Properties instance
346      * @throws SQLException When a paring exception occurs
347      */
348     private static Properties getProperties(final String propText) throws SQLException {
349         final Properties p = new Properties();
350         if (propText != null) {
351             try {
352                 p.load(new ByteArrayInputStream(propText.replace(';', '\n').getBytes(StandardCharsets.ISO_8859_1)));
353             } catch (final IOException e) {
354                 throw new SQLException(propText, e);
355             }
356         }
357         return p;
358     }
359 
360     /**
361      * Parses list of property values from a delimited string
362      *
363      * @param value
364      *            delimited list of values
365      * @param delimiter
366      *            character used to separate values in the list
367      * @return String Collection of values
368      */
369     private static List<String> parseList(final String value, final char delimiter) {
370         final StringTokenizer tokenizer = new StringTokenizer(value, Character.toString(delimiter));
371         final List<String> tokens = new ArrayList<>(tokenizer.countTokens());
372         while (tokenizer.hasMoreTokens()) {
373             tokens.add(tokenizer.nextToken());
374         }
375         return tokens;
376     }
377 
378     /**
379      * Constructs a new instance.
380      */
381     public BasicDataSourceFactory() {
382         // empty
383     }
384 
385     /**
386      * Creates and return a new {@link BasicDataSource} instance. If no instance can be created, return
387      * {@code null} instead.
388      *
389      * @param obj
390      *            The possibly null object containing location or reference information that can be used in creating an
391      *            object
392      * @param name
393      *            The name of this object relative to {@code nameCtx}
394      * @param nameCtx
395      *            The context relative to which the {@code name} parameter is specified, or {@code null} if
396      *            {@code name} is relative to the default initial context
397      * @param environment
398      *            The possibly null environment that is used in creating this object
399      *
400      * @throws SQLException
401      *             if an exception occurs creating the instance
402      */
403     @Override
404     public Object getObjectInstance(final Object obj, final Name name, final Context nameCtx,
405             final Hashtable<?, ?> environment) throws SQLException {
406 
407         // We only know how to deal with {@code javax.naming.Reference}s
408         // that specify a class name of "javax.sql.DataSource"
409         if (obj == null || !(obj instanceof Reference)) {
410             return null;
411         }
412         final Reference ref = (Reference) obj;
413         if (!"javax.sql.DataSource".equals(ref.getClassName())) {
414             return null;
415         }
416 
417         // Check property names and log warnings about obsolete and / or unknown properties
418         final List<String> warnMessages = new ArrayList<>();
419         final List<String> infoMessages = new ArrayList<>();
420         validatePropertyNames(ref, name, warnMessages, infoMessages);
421         warnMessages.forEach(log::warn);
422         infoMessages.forEach(log::info);
423 
424         final Properties properties = new Properties();
425         ALL_PROPERTY_NAMES.forEach(propertyName -> {
426             final RefAddr ra = ref.get(propertyName);
427             if (ra != null) {
428                 properties.setProperty(propertyName, Objects.toString(ra.getContent(), null));
429             }
430         });
431 
432         return createDataSource(properties);
433     }
434 
435     /**
436      * Collects warnings and info messages. Warnings are generated when an obsolete property is set. Unknown properties
437      * generate info messages.
438      *
439      * @param ref
440      *            Reference to check properties of
441      * @param name
442      *            Name provided to getObject
443      * @param warnMessages
444      *            container for warning messages
445      * @param infoMessages
446      *            container for info messages
447      */
448     private void validatePropertyNames(final Reference ref, final Name name, final List<String> warnMessages,
449             final List<String> infoMessages) {
450         final String nameString = name != null ? "Name = " + name.toString() + " " : "";
451         NUPROP_WARNTEXT.forEach((propertyName, value) -> {
452             final RefAddr ra = ref.get(propertyName);
453             if (ra != null && !ALL_PROPERTY_NAMES.contains(ra.getType())) {
454                 final StringBuilder stringBuilder = new StringBuilder(nameString);
455                 final String propertyValue = Objects.toString(ra.getContent(), null);
456                 stringBuilder.append(value).append(" You have set value of \"").append(propertyValue).append("\" for \"").append(propertyName)
457                         .append("\" property, which is being ignored.");
458                 warnMessages.add(stringBuilder.toString());
459             }
460         });
461 
462         final Enumeration<RefAddr> allRefAddrs = ref.getAll();
463         while (allRefAddrs.hasMoreElements()) {
464             final RefAddr ra = allRefAddrs.nextElement();
465             final String propertyName = ra.getType();
466             // If property name is not in the properties list, we haven't warned on it
467             // and it is not in the "silent" list, tell user we are ignoring it.
468             if (!(ALL_PROPERTY_NAMES.contains(propertyName) || NUPROP_WARNTEXT.containsKey(propertyName) || SILENT_PROPERTIES.contains(propertyName))) {
469                 final String propertyValue = Objects.toString(ra.getContent(), null);
470                 final StringBuilder stringBuilder = new StringBuilder(nameString);
471                 stringBuilder.append("Ignoring unknown property: ").append("value of \"").append(propertyValue).append("\" for \"").append(propertyName)
472                         .append("\" property");
473                 infoMessages.add(stringBuilder.toString());
474             }
475         }
476     }
477 }