001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.dbcp2;
019
020import java.io.ByteArrayInputStream;
021import java.nio.charset.StandardCharsets;
022import java.sql.Connection;
023import java.util.ArrayList;
024import java.util.Arrays;
025import java.util.Collection;
026import java.util.Enumeration;
027import java.util.Hashtable;
028import java.util.LinkedHashMap;
029import java.util.List;
030import java.util.Map;
031import java.util.Properties;
032import java.util.StringTokenizer;
033
034import javax.naming.Context;
035import javax.naming.Name;
036import javax.naming.RefAddr;
037import javax.naming.Reference;
038import javax.naming.spi.ObjectFactory;
039
040import org.apache.commons.logging.Log;
041import org.apache.commons.logging.LogFactory;
042import org.apache.commons.pool2.impl.BaseObjectPoolConfig;
043import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
044
045/**
046 * <p>
047 * JNDI object factory that creates an instance of <code>BasicDataSource</code> that has been configured based on the
048 * <code>RefAddr</code> values of the specified <code>Reference</code>, which must match the names and data types of the
049 * <code>BasicDataSource</code> bean properties with the following exceptions:
050 * </p>
051 * <ul>
052 * <li><code>connectionInitSqls</code> must be passed to this factory as a single String using semicolon to delimit the
053 * statements whereas <code>BasicDataSource</code> requires a collection of Strings.</li>
054 * </ul>
055 *
056 * @since 2.0
057 */
058public class BasicDataSourceFactory implements ObjectFactory {
059
060    private static final Log log = LogFactory.getLog(BasicDataSourceFactory.class);
061
062    private static final String PROP_DEFAULT_AUTO_COMMIT = "defaultAutoCommit";
063    private static final String PROP_DEFAULT_READ_ONLY = "defaultReadOnly";
064    private static final String PROP_DEFAULT_TRANSACTION_ISOLATION = "defaultTransactionIsolation";
065    private static final String PROP_DEFAULT_CATALOG = "defaultCatalog";
066    private static final String PROP_DEFAULT_SCHEMA = "defaultSchema";
067    private static final String PROP_CACHE_STATE = "cacheState";
068    private static final String PROP_DRIVER_CLASS_NAME = "driverClassName";
069    private static final String PROP_LIFO = "lifo";
070    private static final String PROP_MAX_TOTAL = "maxTotal";
071    private static final String PROP_MAX_IDLE = "maxIdle";
072    private static final String PROP_MIN_IDLE = "minIdle";
073    private static final String PROP_INITIAL_SIZE = "initialSize";
074    private static final String PROP_MAX_WAIT_MILLIS = "maxWaitMillis";
075    private static final String PROP_TEST_ON_CREATE = "testOnCreate";
076    private static final String PROP_TEST_ON_BORROW = "testOnBorrow";
077    private static final String PROP_TEST_ON_RETURN = "testOnReturn";
078    private static final String PROP_TIME_BETWEEN_EVICTION_RUNS_MILLIS = "timeBetweenEvictionRunsMillis";
079    private static final String PROP_NUM_TESTS_PER_EVICTION_RUN = "numTestsPerEvictionRun";
080    private static final String PROP_MIN_EVICTABLE_IDLE_TIME_MILLIS = "minEvictableIdleTimeMillis";
081    private static final String PROP_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS = "softMinEvictableIdleTimeMillis";
082    private static final String PROP_EVICTION_POLICY_CLASS_NAME = "evictionPolicyClassName";
083    private static final String PROP_TEST_WHILE_IDLE = "testWhileIdle";
084    private static final String PROP_PASSWORD = "password";
085    private static final String PROP_URL = "url";
086    private static final String PROP_USER_NAME = "username";
087    private static final String PROP_VALIDATION_QUERY = "validationQuery";
088    private static final String PROP_VALIDATION_QUERY_TIMEOUT = "validationQueryTimeout";
089    private static final String PROP_JMX_NAME = "jmxName";
090    private static final String PROP_CONNECTION_FACTORY_CLASS_NAME = "connectionFactoryClassName";
091
092    /**
093     * The property name for connectionInitSqls. The associated value String must be of the form [query;]*
094     */
095    private static final String PROP_CONNECTION_INIT_SQLS = "connectionInitSqls";
096    private static final String PROP_ACCESS_TO_UNDERLYING_CONNECTION_ALLOWED = "accessToUnderlyingConnectionAllowed";
097    private static final String PROP_REMOVE_ABANDONED_ON_BORROW = "removeAbandonedOnBorrow";
098    private static final String PROP_REMOVE_ABANDONED_ON_MAINTENANCE = "removeAbandonedOnMaintenance";
099    private static final String PROP_REMOVE_ABANDONED_TIMEOUT = "removeAbandonedTimeout";
100    private static final String PROP_LOG_ABANDONED = "logAbandoned";
101    private static final String PROP_ABANDONED_USAGE_TRACKING = "abandonedUsageTracking";
102    private static final String PROP_POOL_PREPARED_STATEMENTS = "poolPreparedStatements";
103    private static final String PROP_CLEAR_STATEMENT_POOL_ON_RETURN = "clearStatementPoolOnReturn";
104    private static final String PROP_MAX_OPEN_PREPARED_STATEMENTS = "maxOpenPreparedStatements";
105    private static final String PROP_CONNECTION_PROPERTIES = "connectionProperties";
106    private static final String PROP_MAX_CONN_LIFETIME_MILLIS = "maxConnLifetimeMillis";
107    private static final String PROP_LOG_EXPIRED_CONNECTIONS = "logExpiredConnections";
108    private static final String PROP_ROLLBACK_ON_RETURN = "rollbackOnReturn";
109    private static final String PROP_ENABLE_AUTO_COMMIT_ON_RETURN = "enableAutoCommitOnReturn";
110    private static final String PROP_DEFAULT_QUERY_TIMEOUT = "defaultQueryTimeout";
111    private static final String PROP_FAST_FAIL_VALIDATION = "fastFailValidation";
112
113    /**
114     * Value string must be of the form [STATE_CODE,]*
115     */
116    private static final String PROP_DISCONNECTION_SQL_CODES = "disconnectionSqlCodes";
117
118    /*
119     * Block with obsolete properties from DBCP 1.x. Warn users that these are ignored and they should use the 2.x
120     * properties.
121     */
122    private static final String NUPROP_MAX_ACTIVE = "maxActive";
123    private static final String NUPROP_REMOVE_ABANDONED = "removeAbandoned";
124    private static final String NUPROP_MAXWAIT = "maxWait";
125
126    /*
127     * Block with properties expected in a DataSource This props will not be listed as ignored - we know that they may
128     * appear in Resource, and not listing them as ignored.
129     */
130    private static final String SILENT_PROP_FACTORY = "factory";
131    private static final String SILENT_PROP_SCOPE = "scope";
132    private static final String SILENT_PROP_SINGLETON = "singleton";
133    private static final String SILENT_PROP_AUTH = "auth";
134
135    private static final String[] ALL_PROPERTIES = {PROP_DEFAULT_AUTO_COMMIT, PROP_DEFAULT_READ_ONLY,
136            PROP_DEFAULT_TRANSACTION_ISOLATION, PROP_DEFAULT_CATALOG, PROP_DEFAULT_SCHEMA, PROP_CACHE_STATE,
137            PROP_DRIVER_CLASS_NAME, PROP_LIFO, PROP_MAX_TOTAL, PROP_MAX_IDLE, PROP_MIN_IDLE, PROP_INITIAL_SIZE,
138            PROP_MAX_WAIT_MILLIS, PROP_TEST_ON_CREATE, PROP_TEST_ON_BORROW, PROP_TEST_ON_RETURN,
139            PROP_TIME_BETWEEN_EVICTION_RUNS_MILLIS, PROP_NUM_TESTS_PER_EVICTION_RUN, PROP_MIN_EVICTABLE_IDLE_TIME_MILLIS,
140            PROP_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS, PROP_EVICTION_POLICY_CLASS_NAME, PROP_TEST_WHILE_IDLE, PROP_PASSWORD,
141            PROP_URL, PROP_USER_NAME, PROP_VALIDATION_QUERY, PROP_VALIDATION_QUERY_TIMEOUT, PROP_CONNECTION_INIT_SQLS,
142            PROP_ACCESS_TO_UNDERLYING_CONNECTION_ALLOWED, PROP_REMOVE_ABANDONED_ON_BORROW, PROP_REMOVE_ABANDONED_ON_MAINTENANCE,
143            PROP_REMOVE_ABANDONED_TIMEOUT, PROP_LOG_ABANDONED, PROP_ABANDONED_USAGE_TRACKING, PROP_POOL_PREPARED_STATEMENTS,
144            PROP_CLEAR_STATEMENT_POOL_ON_RETURN,
145            PROP_MAX_OPEN_PREPARED_STATEMENTS, PROP_CONNECTION_PROPERTIES, PROP_MAX_CONN_LIFETIME_MILLIS,
146            PROP_LOG_EXPIRED_CONNECTIONS, PROP_ROLLBACK_ON_RETURN, PROP_ENABLE_AUTO_COMMIT_ON_RETURN,
147            PROP_DEFAULT_QUERY_TIMEOUT, PROP_FAST_FAIL_VALIDATION, PROP_DISCONNECTION_SQL_CODES, PROP_JMX_NAME,
148            PROP_CONNECTION_FACTORY_CLASS_NAME };
149
150    /**
151     * Obsolete properties from DBCP 1.x. with warning strings suggesting new properties. LinkedHashMap will guarantee
152     * that properties will be listed to output in order of insertion into map.
153     */
154    private static final Map<String, String> NUPROP_WARNTEXT = new LinkedHashMap<>();
155
156    static {
157        NUPROP_WARNTEXT.put(NUPROP_MAX_ACTIVE,
158                "Property " + NUPROP_MAX_ACTIVE + " is not used in DBCP2, use " + PROP_MAX_TOTAL + " instead. "
159                        + PROP_MAX_TOTAL + " default value is " + GenericObjectPoolConfig.DEFAULT_MAX_TOTAL + ".");
160        NUPROP_WARNTEXT.put(NUPROP_REMOVE_ABANDONED,
161                "Property " + NUPROP_REMOVE_ABANDONED + " is not used in DBCP2," + " use one or both of "
162                        + PROP_REMOVE_ABANDONED_ON_BORROW + " or " + PROP_REMOVE_ABANDONED_ON_MAINTENANCE + " instead. "
163                        + "Both have default value set to false.");
164        NUPROP_WARNTEXT.put(NUPROP_MAXWAIT,
165                "Property " + NUPROP_MAXWAIT + " is not used in DBCP2" + " , use " + PROP_MAX_WAIT_MILLIS + " instead. "
166                        + PROP_MAX_WAIT_MILLIS + " default value is " + BaseObjectPoolConfig.DEFAULT_MAX_WAIT_MILLIS
167                        + ".");
168    }
169
170    /**
171     * Silent Properties. These properties will not be listed as ignored - we know that they may appear in JDBC Resource
172     * references, and we will not list them as ignored.
173     */
174    private static final List<String> SILENT_PROPERTIES = new ArrayList<>();
175
176    static {
177        SILENT_PROPERTIES.add(SILENT_PROP_FACTORY);
178        SILENT_PROPERTIES.add(SILENT_PROP_SCOPE);
179        SILENT_PROPERTIES.add(SILENT_PROP_SINGLETON);
180        SILENT_PROPERTIES.add(SILENT_PROP_AUTH);
181
182    }
183
184    // -------------------------------------------------- ObjectFactory Methods
185
186    /**
187     * Creates and configures a {@link BasicDataSource} instance based on the given properties.
188     *
189     * @param properties
190     *            The data source configuration properties.
191     * @return A new a {@link BasicDataSource} instance based on the given properties.
192     * @throws Exception
193     *             Thrown when an error occurs creating the data source.
194     */
195    public static BasicDataSource createDataSource(final Properties properties) throws Exception {
196        final BasicDataSource dataSource = new BasicDataSource();
197        String value = properties.getProperty(PROP_DEFAULT_AUTO_COMMIT);
198        if (value != null) {
199            dataSource.setDefaultAutoCommit(Boolean.valueOf(value));
200        }
201
202        value = properties.getProperty(PROP_DEFAULT_READ_ONLY);
203        if (value != null) {
204            dataSource.setDefaultReadOnly(Boolean.valueOf(value));
205        }
206
207        value = properties.getProperty(PROP_DEFAULT_TRANSACTION_ISOLATION);
208        if (value != null) {
209            int level = PoolableConnectionFactory.UNKNOWN_TRANSACTION_ISOLATION;
210            if ("NONE".equalsIgnoreCase(value)) {
211                level = Connection.TRANSACTION_NONE;
212            } else if ("READ_COMMITTED".equalsIgnoreCase(value)) {
213                level = Connection.TRANSACTION_READ_COMMITTED;
214            } else if ("READ_UNCOMMITTED".equalsIgnoreCase(value)) {
215                level = Connection.TRANSACTION_READ_UNCOMMITTED;
216            } else if ("REPEATABLE_READ".equalsIgnoreCase(value)) {
217                level = Connection.TRANSACTION_REPEATABLE_READ;
218            } else if ("SERIALIZABLE".equalsIgnoreCase(value)) {
219                level = Connection.TRANSACTION_SERIALIZABLE;
220            } else {
221                try {
222                    level = Integer.parseInt(value);
223                } catch (final NumberFormatException e) {
224                    System.err.println("Could not parse defaultTransactionIsolation: " + value);
225                    System.err.println("WARNING: defaultTransactionIsolation not set");
226                    System.err.println("using default value of database driver");
227                    level = PoolableConnectionFactory.UNKNOWN_TRANSACTION_ISOLATION;
228                }
229            }
230            dataSource.setDefaultTransactionIsolation(level);
231        }
232
233        value = properties.getProperty(PROP_DEFAULT_CATALOG);
234        if (value != null) {
235            dataSource.setDefaultCatalog(value);
236        }
237
238        value = properties.getProperty(PROP_DEFAULT_SCHEMA);
239        if (value != null) {
240            dataSource.setDefaultSchema(value);
241        }
242
243        value = properties.getProperty(PROP_CACHE_STATE);
244        if (value != null) {
245            dataSource.setCacheState(Boolean.parseBoolean(value));
246        }
247
248        value = properties.getProperty(PROP_DRIVER_CLASS_NAME);
249        if (value != null) {
250            dataSource.setDriverClassName(value);
251        }
252
253        value = properties.getProperty(PROP_LIFO);
254        if (value != null) {
255            dataSource.setLifo(Boolean.parseBoolean(value));
256        }
257
258        value = properties.getProperty(PROP_MAX_TOTAL);
259        if (value != null) {
260            dataSource.setMaxTotal(Integer.parseInt(value));
261        }
262
263        value = properties.getProperty(PROP_MAX_IDLE);
264        if (value != null) {
265            dataSource.setMaxIdle(Integer.parseInt(value));
266        }
267
268        value = properties.getProperty(PROP_MIN_IDLE);
269        if (value != null) {
270            dataSource.setMinIdle(Integer.parseInt(value));
271        }
272
273        value = properties.getProperty(PROP_INITIAL_SIZE);
274        if (value != null) {
275            dataSource.setInitialSize(Integer.parseInt(value));
276        }
277
278        value = properties.getProperty(PROP_MAX_WAIT_MILLIS);
279        if (value != null) {
280            dataSource.setMaxWaitMillis(Long.parseLong(value));
281        }
282
283        value = properties.getProperty(PROP_TEST_ON_CREATE);
284        if (value != null) {
285            dataSource.setTestOnCreate(Boolean.parseBoolean(value));
286        }
287
288        value = properties.getProperty(PROP_TEST_ON_BORROW);
289        if (value != null) {
290            dataSource.setTestOnBorrow(Boolean.parseBoolean(value));
291        }
292
293        value = properties.getProperty(PROP_TEST_ON_RETURN);
294        if (value != null) {
295            dataSource.setTestOnReturn(Boolean.parseBoolean(value));
296        }
297
298        value = properties.getProperty(PROP_TIME_BETWEEN_EVICTION_RUNS_MILLIS);
299        if (value != null) {
300            dataSource.setTimeBetweenEvictionRunsMillis(Long.parseLong(value));
301        }
302
303        value = properties.getProperty(PROP_NUM_TESTS_PER_EVICTION_RUN);
304        if (value != null) {
305            dataSource.setNumTestsPerEvictionRun(Integer.parseInt(value));
306        }
307
308        value = properties.getProperty(PROP_MIN_EVICTABLE_IDLE_TIME_MILLIS);
309        if (value != null) {
310            dataSource.setMinEvictableIdleTimeMillis(Long.parseLong(value));
311        }
312
313        value = properties.getProperty(PROP_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS);
314        if (value != null) {
315            dataSource.setSoftMinEvictableIdleTimeMillis(Long.parseLong(value));
316        }
317
318        value = properties.getProperty(PROP_EVICTION_POLICY_CLASS_NAME);
319        if (value != null) {
320            dataSource.setEvictionPolicyClassName(value);
321        }
322
323        value = properties.getProperty(PROP_TEST_WHILE_IDLE);
324        if (value != null) {
325            dataSource.setTestWhileIdle(Boolean.parseBoolean(value));
326        }
327
328        value = properties.getProperty(PROP_PASSWORD);
329        if (value != null) {
330            dataSource.setPassword(value);
331        }
332
333        value = properties.getProperty(PROP_URL);
334        if (value != null) {
335            dataSource.setUrl(value);
336        }
337
338        value = properties.getProperty(PROP_USER_NAME);
339        if (value != null) {
340            dataSource.setUsername(value);
341        }
342
343        value = properties.getProperty(PROP_VALIDATION_QUERY);
344        if (value != null) {
345            dataSource.setValidationQuery(value);
346        }
347
348        value = properties.getProperty(PROP_VALIDATION_QUERY_TIMEOUT);
349        if (value != null) {
350            dataSource.setValidationQueryTimeout(Integer.parseInt(value));
351        }
352
353        value = properties.getProperty(PROP_ACCESS_TO_UNDERLYING_CONNECTION_ALLOWED);
354        if (value != null) {
355            dataSource.setAccessToUnderlyingConnectionAllowed(Boolean.parseBoolean(value));
356        }
357
358        value = properties.getProperty(PROP_REMOVE_ABANDONED_ON_BORROW);
359        if (value != null) {
360            dataSource.setRemoveAbandonedOnBorrow(Boolean.parseBoolean(value));
361        }
362
363        value = properties.getProperty(PROP_REMOVE_ABANDONED_ON_MAINTENANCE);
364        if (value != null) {
365            dataSource.setRemoveAbandonedOnMaintenance(Boolean.parseBoolean(value));
366        }
367
368        value = properties.getProperty(PROP_REMOVE_ABANDONED_TIMEOUT);
369        if (value != null) {
370            dataSource.setRemoveAbandonedTimeout(Integer.parseInt(value));
371        }
372
373        value = properties.getProperty(PROP_LOG_ABANDONED);
374        if (value != null) {
375            dataSource.setLogAbandoned(Boolean.parseBoolean(value));
376        }
377
378        value = properties.getProperty(PROP_ABANDONED_USAGE_TRACKING);
379        if (value != null) {
380            dataSource.setAbandonedUsageTracking(Boolean.parseBoolean(value));
381        }
382
383        value = properties.getProperty(PROP_POOL_PREPARED_STATEMENTS);
384        if (value != null) {
385            dataSource.setPoolPreparedStatements(Boolean.parseBoolean(value));
386        }
387
388        value = properties.getProperty(PROP_CLEAR_STATEMENT_POOL_ON_RETURN);
389        if (value != null) {
390            dataSource.setClearStatementPoolOnReturn(Boolean.parseBoolean(value));
391        }
392
393        value = properties.getProperty(PROP_MAX_OPEN_PREPARED_STATEMENTS);
394        if (value != null) {
395            dataSource.setMaxOpenPreparedStatements(Integer.parseInt(value));
396        }
397
398        value = properties.getProperty(PROP_CONNECTION_INIT_SQLS);
399        if (value != null) {
400            dataSource.setConnectionInitSqls(parseList(value, ';'));
401        }
402
403        value = properties.getProperty(PROP_CONNECTION_PROPERTIES);
404        if (value != null) {
405            final Properties p = getProperties(value);
406            final Enumeration<?> e = p.propertyNames();
407            while (e.hasMoreElements()) {
408                final String propertyName = (String) e.nextElement();
409                dataSource.addConnectionProperty(propertyName, p.getProperty(propertyName));
410            }
411        }
412
413        value = properties.getProperty(PROP_MAX_CONN_LIFETIME_MILLIS);
414        if (value != null) {
415            dataSource.setMaxConnLifetimeMillis(Long.parseLong(value));
416        }
417
418        value = properties.getProperty(PROP_LOG_EXPIRED_CONNECTIONS);
419        if (value != null) {
420            dataSource.setLogExpiredConnections(Boolean.parseBoolean(value));
421        }
422
423        value = properties.getProperty(PROP_JMX_NAME);
424        if (value != null) {
425            dataSource.setJmxName(value);
426        }
427
428        value = properties.getProperty(PROP_ENABLE_AUTO_COMMIT_ON_RETURN);
429        if (value != null) {
430            dataSource.setAutoCommitOnReturn(Boolean.parseBoolean(value));
431        }
432
433        value = properties.getProperty(PROP_ROLLBACK_ON_RETURN);
434        if (value != null) {
435            dataSource.setRollbackOnReturn(Boolean.parseBoolean(value));
436        }
437
438        value = properties.getProperty(PROP_DEFAULT_QUERY_TIMEOUT);
439        if (value != null) {
440            dataSource.setDefaultQueryTimeout(Integer.valueOf(value));
441        }
442
443        value = properties.getProperty(PROP_FAST_FAIL_VALIDATION);
444        if (value != null) {
445            dataSource.setFastFailValidation(Boolean.parseBoolean(value));
446        }
447
448        value = properties.getProperty(PROP_DISCONNECTION_SQL_CODES);
449        if (value != null) {
450            dataSource.setDisconnectionSqlCodes(parseList(value, ','));
451        }
452
453        value = properties.getProperty(PROP_CONNECTION_FACTORY_CLASS_NAME);
454        if (value != null) {
455            dataSource.setConnectionFactoryClassName(value);
456        }
457
458        // DBCP-215
459        // Trick to make sure that initialSize connections are created
460        if (dataSource.getInitialSize() > 0) {
461            dataSource.getLogWriter();
462        }
463
464        // Return the configured DataSource instance
465        return dataSource;
466    }
467
468    /**
469     * <p>
470     * Parse properties from the string. Format of the string must be [propertyName=property;]*
471     * <p>
472     *
473     * @param propText
474     * @return Properties
475     * @throws Exception
476     */
477    private static Properties getProperties(final String propText) throws Exception {
478        final Properties p = new Properties();
479        if (propText != null) {
480            p.load(new ByteArrayInputStream(propText.replace(';', '\n').getBytes(StandardCharsets.ISO_8859_1)));
481        }
482        return p;
483    }
484
485    /**
486     * Parse list of property values from a delimited string
487     *
488     * @param value
489     *            delimited list of values
490     * @param delimiter
491     *            character used to separate values in the list
492     * @return String Collection of values
493     */
494    private static Collection<String> parseList(final String value, final char delimiter) {
495        final StringTokenizer tokenizer = new StringTokenizer(value, Character.toString(delimiter));
496        final Collection<String> tokens = new ArrayList<>(tokenizer.countTokens());
497        while (tokenizer.hasMoreTokens()) {
498            tokens.add(tokenizer.nextToken());
499        }
500        return tokens;
501    }
502
503    /**
504     * <p>
505     * Create and return a new <code>BasicDataSource</code> instance. If no instance can be created, return
506     * <code>null</code> instead.
507     * </p>
508     *
509     * @param obj
510     *            The possibly null object containing location or reference information that can be used in creating an
511     *            object
512     * @param name
513     *            The name of this object relative to <code>nameCtx</code>
514     * @param nameCtx
515     *            The context relative to which the <code>name</code> parameter is specified, or <code>null</code> if
516     *            <code>name</code> is relative to the default initial context
517     * @param environment
518     *            The possibly null environment that is used in creating this object
519     *
520     * @throws Exception
521     *             if an exception occurs creating the instance
522     */
523    @Override
524    public Object getObjectInstance(final Object obj, final Name name, final Context nameCtx,
525            final Hashtable<?, ?> environment) throws Exception {
526
527        // We only know how to deal with <code>javax.naming.Reference</code>s
528        // that specify a class name of "javax.sql.DataSource"
529        if (obj == null || !(obj instanceof Reference)) {
530            return null;
531        }
532        final Reference ref = (Reference) obj;
533        if (!"javax.sql.DataSource".equals(ref.getClassName())) {
534            return null;
535        }
536
537        // Check property names and log warnings about obsolete and / or unknown properties
538        final List<String> warnings = new ArrayList<>();
539        final List<String> infoMessages = new ArrayList<>();
540        validatePropertyNames(ref, name, warnings, infoMessages);
541        for (final String warning : warnings) {
542            log.warn(warning);
543        }
544        for (final String infoMessage : infoMessages) {
545            log.info(infoMessage);
546        }
547
548        final Properties properties = new Properties();
549        for (final String propertyName : ALL_PROPERTIES) {
550            final RefAddr ra = ref.get(propertyName);
551            if (ra != null) {
552                final String propertyValue = ra.getContent().toString();
553                properties.setProperty(propertyName, propertyValue);
554            }
555        }
556
557        return createDataSource(properties);
558    }
559
560    /**
561     * Collects warnings and info messages. Warnings are generated when an obsolete property is set. Unknown properties
562     * generate info messages.
563     *
564     * @param ref
565     *            Reference to check properties of
566     * @param name
567     *            Name provided to getObject
568     * @param warnings
569     *            container for warning messages
570     * @param infoMessages
571     *            container for info messages
572     */
573    private void validatePropertyNames(final Reference ref, final Name name, final List<String> warnings,
574            final List<String> infoMessages) {
575        final List<String> allPropsAsList = Arrays.asList(ALL_PROPERTIES);
576        final String nameString = name != null ? "Name = " + name.toString() + " " : "";
577        if (NUPROP_WARNTEXT != null && !NUPROP_WARNTEXT.isEmpty()) {
578            for (final String propertyName : NUPROP_WARNTEXT.keySet()) {
579                final RefAddr ra = ref.get(propertyName);
580                if (ra != null && !allPropsAsList.contains(ra.getType())) {
581                    final StringBuilder stringBuilder = new StringBuilder(nameString);
582                    final String propertyValue = ra.getContent().toString();
583                    stringBuilder.append(NUPROP_WARNTEXT.get(propertyName)).append(" You have set value of \"")
584                            .append(propertyValue).append("\" for \"").append(propertyName)
585                            .append("\" property, which is being ignored.");
586                    warnings.add(stringBuilder.toString());
587                }
588            }
589        }
590
591        final Enumeration<RefAddr> allRefAddrs = ref.getAll();
592        while (allRefAddrs.hasMoreElements()) {
593            final RefAddr ra = allRefAddrs.nextElement();
594            final String propertyName = ra.getType();
595            // If property name is not in the properties list, we haven't warned on it
596            // and it is not in the "silent" list, tell user we are ignoring it.
597            if (!(allPropsAsList.contains(propertyName) || NUPROP_WARNTEXT.containsKey(propertyName)
598                    || SILENT_PROPERTIES.contains(propertyName))) {
599                final String propertyValue = ra.getContent().toString();
600                final StringBuilder stringBuilder = new StringBuilder(nameString);
601                stringBuilder.append("Ignoring unknown property: ").append("value of \"").append(propertyValue)
602                        .append("\" for \"").append(propertyName).append("\" property");
603                infoMessages.add(stringBuilder.toString());
604            }
605        }
606    }
607}