BasicDataSourceFactory.java

  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;

  18. import java.io.ByteArrayInputStream;
  19. import java.io.IOException;
  20. import java.nio.charset.StandardCharsets;
  21. import java.sql.Connection;
  22. import java.sql.SQLException;
  23. import java.time.Duration;
  24. import java.util.ArrayList;
  25. import java.util.Arrays;
  26. import java.util.Enumeration;
  27. import java.util.Hashtable;
  28. import java.util.LinkedHashMap;
  29. import java.util.List;
  30. import java.util.Locale;
  31. import java.util.Map;
  32. import java.util.Objects;
  33. import java.util.Optional;
  34. import java.util.Properties;
  35. import java.util.StringTokenizer;
  36. import java.util.function.Consumer;
  37. import java.util.function.Function;

  38. import javax.naming.Context;
  39. import javax.naming.Name;
  40. import javax.naming.RefAddr;
  41. import javax.naming.Reference;
  42. import javax.naming.spi.ObjectFactory;

  43. import org.apache.commons.logging.Log;
  44. import org.apache.commons.logging.LogFactory;
  45. import org.apache.commons.pool2.impl.BaseObjectPoolConfig;
  46. import org.apache.commons.pool2.impl.GenericObjectPoolConfig;

  47. /**
  48.  * JNDI object factory that creates an instance of {@code BasicDataSource} that has been configured based on the
  49.  * {@code RefAddr} values of the specified {@code Reference}, which must match the names and data types of the
  50.  * {@code BasicDataSource} bean properties with the following exceptions:
  51.  * <ul>
  52.  * <li>{@code connectionInitSqls} must be passed to this factory as a single String using semicolon to delimit the
  53.  * statements whereas {@code BasicDataSource} requires a collection of Strings.</li>
  54.  * </ul>
  55.  *
  56.  * @since 2.0
  57.  */
  58. public class BasicDataSourceFactory implements ObjectFactory {

  59.     private static final Log log = LogFactory.getLog(BasicDataSourceFactory.class);

  60.     private static final String PROP_DEFAULT_AUTO_COMMIT = "defaultAutoCommit";
  61.     private static final String PROP_DEFAULT_READ_ONLY = "defaultReadOnly";
  62.     private static final String PROP_DEFAULT_TRANSACTION_ISOLATION = "defaultTransactionIsolation";
  63.     private static final String PROP_DEFAULT_CATALOG = "defaultCatalog";
  64.     private static final String PROP_DEFAULT_SCHEMA = "defaultSchema";
  65.     private static final String PROP_CACHE_STATE = "cacheState";
  66.     private static final String PROP_DRIVER_CLASS_NAME = "driverClassName";
  67.     private static final String PROP_LIFO = "lifo";
  68.     private static final String PROP_MAX_TOTAL = "maxTotal";
  69.     private static final String PROP_MAX_IDLE = "maxIdle";
  70.     private static final String PROP_MIN_IDLE = "minIdle";
  71.     private static final String PROP_INITIAL_SIZE = "initialSize";
  72.     private static final String PROP_MAX_WAIT_MILLIS = "maxWaitMillis";
  73.     private static final String PROP_TEST_ON_CREATE = "testOnCreate";
  74.     private static final String PROP_TEST_ON_BORROW = "testOnBorrow";
  75.     private static final String PROP_TEST_ON_RETURN = "testOnReturn";
  76.     private static final String PROP_TIME_BETWEEN_EVICTION_RUNS_MILLIS = "timeBetweenEvictionRunsMillis";
  77.     private static final String PROP_NUM_TESTS_PER_EVICTION_RUN = "numTestsPerEvictionRun";
  78.     private static final String PROP_MIN_EVICTABLE_IDLE_TIME_MILLIS = "minEvictableIdleTimeMillis";
  79.     private static final String PROP_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS = "softMinEvictableIdleTimeMillis";
  80.     private static final String PROP_EVICTION_POLICY_CLASS_NAME = "evictionPolicyClassName";
  81.     private static final String PROP_TEST_WHILE_IDLE = "testWhileIdle";
  82.     private static final String PROP_PASSWORD = Constants.KEY_PASSWORD;
  83.     private static final String PROP_URL = "url";
  84.     private static final String PROP_USER_NAME = "username";
  85.     private static final String PROP_VALIDATION_QUERY = "validationQuery";
  86.     private static final String PROP_VALIDATION_QUERY_TIMEOUT = "validationQueryTimeout";
  87.     private static final String PROP_JMX_NAME = "jmxName";
  88.     private static final String PROP_REGISTER_CONNECTION_MBEAN = "registerConnectionMBean";
  89.     private static final String PROP_CONNECTION_FACTORY_CLASS_NAME = "connectionFactoryClassName";

  90.     /**
  91.      * The property name for connectionInitSqls. The associated value String must be of the form [query;]*
  92.      */
  93.     private static final String PROP_CONNECTION_INIT_SQLS = "connectionInitSqls";
  94.     private static final String PROP_ACCESS_TO_UNDERLYING_CONNECTION_ALLOWED = "accessToUnderlyingConnectionAllowed";
  95.     private static final String PROP_REMOVE_ABANDONED_ON_BORROW = "removeAbandonedOnBorrow";
  96.     private static final String PROP_REMOVE_ABANDONED_ON_MAINTENANCE = "removeAbandonedOnMaintenance";
  97.     private static final String PROP_REMOVE_ABANDONED_TIMEOUT = "removeAbandonedTimeout";
  98.     private static final String PROP_LOG_ABANDONED = "logAbandoned";
  99.     private static final String PROP_ABANDONED_USAGE_TRACKING = "abandonedUsageTracking";
  100.     private static final String PROP_POOL_PREPARED_STATEMENTS = "poolPreparedStatements";
  101.     private static final String PROP_CLEAR_STATEMENT_POOL_ON_RETURN = "clearStatementPoolOnReturn";
  102.     private static final String PROP_MAX_OPEN_PREPARED_STATEMENTS = "maxOpenPreparedStatements";
  103.     private static final String PROP_CONNECTION_PROPERTIES = "connectionProperties";
  104.     private static final String PROP_MAX_CONN_LIFETIME_MILLIS = "maxConnLifetimeMillis";
  105.     private static final String PROP_LOG_EXPIRED_CONNECTIONS = "logExpiredConnections";
  106.     private static final String PROP_ROLLBACK_ON_RETURN = "rollbackOnReturn";
  107.     private static final String PROP_ENABLE_AUTO_COMMIT_ON_RETURN = "enableAutoCommitOnReturn";
  108.     private static final String PROP_DEFAULT_QUERY_TIMEOUT = "defaultQueryTimeout";
  109.     private static final String PROP_FAST_FAIL_VALIDATION = "fastFailValidation";

  110.     /**
  111.      * Value string must be of the form [STATE_CODE,]*
  112.      */
  113.     private static final String PROP_DISCONNECTION_SQL_CODES = "disconnectionSqlCodes";

  114.     /**
  115.      * Property key for specifying the SQL State codes that should be ignored during disconnection checks.
  116.      * <p>
  117.      * The value for this property must be a comma-separated string of SQL State codes, where each code represents
  118.      * a state that will be excluded from being treated as a fatal disconnection. The expected format is a series
  119.      * of SQL State codes separated by commas, with no spaces between them (e.g., "08003,08004").
  120.      * </p>
  121.      * @since 2.13.0
  122.      */
  123.     private static final String PROP_DISCONNECTION_IGNORE_SQL_CODES = "disconnectionIgnoreSqlCodes";


  124.     /*
  125.      * Block with obsolete properties from DBCP 1.x. Warn users that these are ignored and they should use the 2.x
  126.      * properties.
  127.      */
  128.     private static final String NUPROP_MAX_ACTIVE = "maxActive";
  129.     private static final String NUPROP_REMOVE_ABANDONED = "removeAbandoned";
  130.     private static final String NUPROP_MAXWAIT = "maxWait";

  131.     /*
  132.      * Block with properties expected in a DataSource This props will not be listed as ignored - we know that they may
  133.      * appear in Resource, and not listing them as ignored.
  134.      */
  135.     private static final String SILENT_PROP_FACTORY = "factory";
  136.     private static final String SILENT_PROP_SCOPE = "scope";
  137.     private static final String SILENT_PROP_SINGLETON = "singleton";
  138.     private static final String SILENT_PROP_AUTH = "auth";

  139.     private static final List<String> ALL_PROPERTY_NAMES = Arrays.asList(PROP_DEFAULT_AUTO_COMMIT, PROP_DEFAULT_READ_ONLY,
  140.             PROP_DEFAULT_TRANSACTION_ISOLATION, PROP_DEFAULT_CATALOG, PROP_DEFAULT_SCHEMA, PROP_CACHE_STATE,
  141.             PROP_DRIVER_CLASS_NAME, PROP_LIFO, PROP_MAX_TOTAL, PROP_MAX_IDLE, PROP_MIN_IDLE, PROP_INITIAL_SIZE,
  142.             PROP_MAX_WAIT_MILLIS, PROP_TEST_ON_CREATE, PROP_TEST_ON_BORROW, PROP_TEST_ON_RETURN,
  143.             PROP_TIME_BETWEEN_EVICTION_RUNS_MILLIS, PROP_NUM_TESTS_PER_EVICTION_RUN, PROP_MIN_EVICTABLE_IDLE_TIME_MILLIS,
  144.             PROP_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS, PROP_EVICTION_POLICY_CLASS_NAME, PROP_TEST_WHILE_IDLE, PROP_PASSWORD,
  145.             PROP_URL, PROP_USER_NAME, PROP_VALIDATION_QUERY, PROP_VALIDATION_QUERY_TIMEOUT, PROP_CONNECTION_INIT_SQLS,
  146.             PROP_ACCESS_TO_UNDERLYING_CONNECTION_ALLOWED, PROP_REMOVE_ABANDONED_ON_BORROW, PROP_REMOVE_ABANDONED_ON_MAINTENANCE,
  147.             PROP_REMOVE_ABANDONED_TIMEOUT, PROP_LOG_ABANDONED, PROP_ABANDONED_USAGE_TRACKING, PROP_POOL_PREPARED_STATEMENTS,
  148.             PROP_CLEAR_STATEMENT_POOL_ON_RETURN,
  149.             PROP_MAX_OPEN_PREPARED_STATEMENTS, PROP_CONNECTION_PROPERTIES, PROP_MAX_CONN_LIFETIME_MILLIS,
  150.             PROP_LOG_EXPIRED_CONNECTIONS, PROP_ROLLBACK_ON_RETURN, PROP_ENABLE_AUTO_COMMIT_ON_RETURN,
  151.             PROP_DEFAULT_QUERY_TIMEOUT, PROP_FAST_FAIL_VALIDATION, PROP_DISCONNECTION_SQL_CODES, PROP_DISCONNECTION_IGNORE_SQL_CODES,
  152.             PROP_JMX_NAME, PROP_REGISTER_CONNECTION_MBEAN, PROP_CONNECTION_FACTORY_CLASS_NAME);

  153.     /**
  154.      * Obsolete properties from DBCP 1.x. with warning strings suggesting new properties. LinkedHashMap will guarantee
  155.      * that properties will be listed to output in order of insertion into map.
  156.      */
  157.     private static final Map<String, String> NUPROP_WARNTEXT = new LinkedHashMap<>();

  158.     static {
  159.         NUPROP_WARNTEXT.put(NUPROP_MAX_ACTIVE,
  160.                 "Property " + NUPROP_MAX_ACTIVE + " is not used in DBCP2, use " + PROP_MAX_TOTAL + " instead. "
  161.                         + PROP_MAX_TOTAL + " default value is " + GenericObjectPoolConfig.DEFAULT_MAX_TOTAL + ".");
  162.         NUPROP_WARNTEXT.put(NUPROP_REMOVE_ABANDONED,
  163.                 "Property " + NUPROP_REMOVE_ABANDONED + " is not used in DBCP2," + " use one or both of "
  164.                         + PROP_REMOVE_ABANDONED_ON_BORROW + " or " + PROP_REMOVE_ABANDONED_ON_MAINTENANCE + " instead. "
  165.                         + "Both have default value set to false.");
  166.         NUPROP_WARNTEXT.put(NUPROP_MAXWAIT,
  167.                 "Property " + NUPROP_MAXWAIT + " is not used in DBCP2" + " , use " + PROP_MAX_WAIT_MILLIS + " instead. "
  168.                         + PROP_MAX_WAIT_MILLIS + " default value is " + BaseObjectPoolConfig.DEFAULT_MAX_WAIT
  169.                         + ".");
  170.     }

  171.     /**
  172.      * Silent Properties. These properties will not be listed as ignored - we know that they may appear in JDBC Resource
  173.      * references, and we will not list them as ignored.
  174.      */
  175.     private static final List<String> SILENT_PROPERTIES = new ArrayList<>();

  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.     private static <V> void accept(final Properties properties, final String name, final Function<String, V> parser, final Consumer<V> consumer) {
  183.         getOptional(properties, name).ifPresent(v -> consumer.accept(parser.apply(v)));
  184.     }

  185.     private static void acceptBoolean(final Properties properties, final String name, final Consumer<Boolean> consumer) {
  186.         accept(properties, name, Boolean::parseBoolean, consumer);
  187.     }

  188.     private static void acceptDurationOfMillis(final Properties properties, final String name, final Consumer<Duration> consumer) {
  189.         accept(properties, name, s -> Duration.ofMillis(Long.parseLong(s)), consumer);
  190.     }

  191.     private static void acceptDurationOfSeconds(final Properties properties, final String name, final Consumer<Duration> consumer) {
  192.         accept(properties, name, s -> Duration.ofSeconds(Long.parseLong(s)), consumer);
  193.     }

  194.     private static void acceptInt(final Properties properties, final String name, final Consumer<Integer> consumer) {
  195.         accept(properties, name, Integer::parseInt, consumer);
  196.     }

  197.     private static void acceptString(final Properties properties, final String name, final Consumer<String> consumer) {
  198.         accept(properties, name, Function.identity(), consumer);
  199.     }

  200.     /**
  201.      * Creates and configures a {@link BasicDataSource} instance based on the given properties.
  202.      *
  203.      * @param properties
  204.      *            The data source configuration properties.
  205.      * @return A new a {@link BasicDataSource} instance based on the given properties.
  206.      * @throws SQLException
  207.      *             Thrown when an error occurs creating the data source.
  208.      */
  209.     public static BasicDataSource createDataSource(final Properties properties) throws SQLException {
  210.         final BasicDataSource dataSource = new BasicDataSource();
  211.         acceptBoolean(properties, PROP_DEFAULT_AUTO_COMMIT, dataSource::setDefaultAutoCommit);
  212.         acceptBoolean(properties, PROP_DEFAULT_READ_ONLY, dataSource::setDefaultReadOnly);

  213.         getOptional(properties, PROP_DEFAULT_TRANSACTION_ISOLATION).ifPresent(value -> {
  214.             value = value.toUpperCase(Locale.ROOT);
  215.             int level = PoolableConnectionFactory.UNKNOWN_TRANSACTION_ISOLATION;
  216.             switch (value) {
  217.             case "NONE":
  218.                 level = Connection.TRANSACTION_NONE;
  219.                 break;
  220.             case "READ_COMMITTED":
  221.                 level = Connection.TRANSACTION_READ_COMMITTED;
  222.                 break;
  223.             case "READ_UNCOMMITTED":
  224.                 level = Connection.TRANSACTION_READ_UNCOMMITTED;
  225.                 break;
  226.             case "REPEATABLE_READ":
  227.                 level = Connection.TRANSACTION_REPEATABLE_READ;
  228.                 break;
  229.             case "SERIALIZABLE":
  230.                 level = Connection.TRANSACTION_SERIALIZABLE;
  231.                 break;
  232.             default:
  233.                 try {
  234.                     level = Integer.parseInt(value);
  235.                 } catch (final NumberFormatException e) {
  236.                     System.err.println("Could not parse defaultTransactionIsolation: " + value);
  237.                     System.err.println("WARNING: defaultTransactionIsolation not set");
  238.                     System.err.println("using default value of database driver");
  239.                     level = PoolableConnectionFactory.UNKNOWN_TRANSACTION_ISOLATION;
  240.                 }
  241.                 break;
  242.             }
  243.             dataSource.setDefaultTransactionIsolation(level);
  244.         });

  245.         acceptString(properties, PROP_DEFAULT_SCHEMA, dataSource::setDefaultSchema);
  246.         acceptString(properties, PROP_DEFAULT_CATALOG, dataSource::setDefaultCatalog);
  247.         acceptBoolean(properties, PROP_CACHE_STATE, dataSource::setCacheState);
  248.         acceptString(properties, PROP_DRIVER_CLASS_NAME, dataSource::setDriverClassName);
  249.         acceptBoolean(properties, PROP_LIFO, dataSource::setLifo);
  250.         acceptInt(properties, PROP_MAX_TOTAL, dataSource::setMaxTotal);
  251.         acceptInt(properties, PROP_MAX_IDLE, dataSource::setMaxIdle);
  252.         acceptInt(properties, PROP_MIN_IDLE, dataSource::setMinIdle);
  253.         acceptInt(properties, PROP_INITIAL_SIZE, dataSource::setInitialSize);
  254.         acceptDurationOfMillis(properties, PROP_MAX_WAIT_MILLIS, dataSource::setMaxWait);
  255.         acceptBoolean(properties, PROP_TEST_ON_CREATE, dataSource::setTestOnCreate);
  256.         acceptBoolean(properties, PROP_TEST_ON_BORROW, dataSource::setTestOnBorrow);
  257.         acceptBoolean(properties, PROP_TEST_ON_RETURN, dataSource::setTestOnReturn);
  258.         acceptDurationOfMillis(properties, PROP_TIME_BETWEEN_EVICTION_RUNS_MILLIS, dataSource::setDurationBetweenEvictionRuns);
  259.         acceptInt(properties, PROP_NUM_TESTS_PER_EVICTION_RUN, dataSource::setNumTestsPerEvictionRun);
  260.         acceptDurationOfMillis(properties, PROP_MIN_EVICTABLE_IDLE_TIME_MILLIS, dataSource::setMinEvictableIdle);
  261.         acceptDurationOfMillis(properties, PROP_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS, dataSource::setSoftMinEvictableIdle);
  262.         acceptString(properties, PROP_EVICTION_POLICY_CLASS_NAME, dataSource::setEvictionPolicyClassName);
  263.         acceptBoolean(properties, PROP_TEST_WHILE_IDLE, dataSource::setTestWhileIdle);
  264.         acceptString(properties, PROP_PASSWORD, dataSource::setPassword);
  265.         acceptString(properties, PROP_URL, dataSource::setUrl);
  266.         acceptString(properties, PROP_USER_NAME, dataSource::setUsername);
  267.         acceptString(properties, PROP_VALIDATION_QUERY, dataSource::setValidationQuery);
  268.         acceptDurationOfSeconds(properties, PROP_VALIDATION_QUERY_TIMEOUT, dataSource::setValidationQueryTimeout);
  269.         acceptBoolean(properties, PROP_ACCESS_TO_UNDERLYING_CONNECTION_ALLOWED, dataSource::setAccessToUnderlyingConnectionAllowed);
  270.         acceptBoolean(properties, PROP_REMOVE_ABANDONED_ON_BORROW, dataSource::setRemoveAbandonedOnBorrow);
  271.         acceptBoolean(properties, PROP_REMOVE_ABANDONED_ON_MAINTENANCE, dataSource::setRemoveAbandonedOnMaintenance);
  272.         acceptDurationOfSeconds(properties, PROP_REMOVE_ABANDONED_TIMEOUT, dataSource::setRemoveAbandonedTimeout);
  273.         acceptBoolean(properties, PROP_LOG_ABANDONED, dataSource::setLogAbandoned);
  274.         acceptBoolean(properties, PROP_ABANDONED_USAGE_TRACKING, dataSource::setAbandonedUsageTracking);
  275.         acceptBoolean(properties, PROP_POOL_PREPARED_STATEMENTS, dataSource::setPoolPreparedStatements);
  276.         acceptBoolean(properties, PROP_CLEAR_STATEMENT_POOL_ON_RETURN, dataSource::setClearStatementPoolOnReturn);
  277.         acceptInt(properties, PROP_MAX_OPEN_PREPARED_STATEMENTS, dataSource::setMaxOpenPreparedStatements);
  278.         getOptional(properties, PROP_CONNECTION_INIT_SQLS).ifPresent(v -> dataSource.setConnectionInitSqls(parseList(v, ';')));

  279.         final String value = properties.getProperty(PROP_CONNECTION_PROPERTIES);
  280.         if (value != null) {
  281.             for (final Object key : getProperties(value).keySet()) {
  282.                 final String propertyName = Objects.toString(key, null);
  283.                 dataSource.addConnectionProperty(propertyName, getProperties(value).getProperty(propertyName));
  284.             }
  285.         }

  286.         acceptDurationOfMillis(properties, PROP_MAX_CONN_LIFETIME_MILLIS, dataSource::setMaxConn);
  287.         acceptBoolean(properties, PROP_LOG_EXPIRED_CONNECTIONS, dataSource::setLogExpiredConnections);
  288.         acceptString(properties, PROP_JMX_NAME, dataSource::setJmxName);
  289.         acceptBoolean(properties, PROP_REGISTER_CONNECTION_MBEAN, dataSource::setRegisterConnectionMBean);
  290.         acceptBoolean(properties, PROP_ENABLE_AUTO_COMMIT_ON_RETURN, dataSource::setAutoCommitOnReturn);
  291.         acceptBoolean(properties, PROP_ROLLBACK_ON_RETURN, dataSource::setRollbackOnReturn);
  292.         acceptDurationOfSeconds(properties, PROP_DEFAULT_QUERY_TIMEOUT, dataSource::setDefaultQueryTimeout);
  293.         acceptBoolean(properties, PROP_FAST_FAIL_VALIDATION, dataSource::setFastFailValidation);
  294.         getOptional(properties, PROP_DISCONNECTION_SQL_CODES).ifPresent(v -> dataSource.setDisconnectionSqlCodes(parseList(v, ',')));
  295.         getOptional(properties, PROP_DISCONNECTION_IGNORE_SQL_CODES).ifPresent(v -> dataSource.setDisconnectionIgnoreSqlCodes(parseList(v, ',')));
  296.         acceptString(properties, PROP_CONNECTION_FACTORY_CLASS_NAME, dataSource::setConnectionFactoryClassName);

  297.         // DBCP-215
  298.         // Trick to make sure that initialSize connections are created
  299.         if (dataSource.getInitialSize() > 0) {
  300.             dataSource.getLogWriter();
  301.         }

  302.         // Return the configured DataSource instance
  303.         return dataSource;
  304.     }

  305.     private static Optional<String> getOptional(final Properties properties, final String name) {
  306.         return Optional.ofNullable(properties.getProperty(name));
  307.     }

  308.     /**
  309.      * Parse properties from the string. Format of the string must be [propertyName=property;]*
  310.      *
  311.      * @param propText The source text
  312.      * @return Properties A new Properties instance
  313.      * @throws SQLException When a paring exception occurs
  314.      */
  315.     private static Properties getProperties(final String propText) throws SQLException {
  316.         final Properties p = new Properties();
  317.         if (propText != null) {
  318.             try {
  319.                 p.load(new ByteArrayInputStream(propText.replace(';', '\n').getBytes(StandardCharsets.ISO_8859_1)));
  320.             } catch (final IOException e) {
  321.                 throw new SQLException(propText, e);
  322.             }
  323.         }
  324.         return p;
  325.     }

  326.     /**
  327.      * Parses list of property values from a delimited string
  328.      *
  329.      * @param value
  330.      *            delimited list of values
  331.      * @param delimiter
  332.      *            character used to separate values in the list
  333.      * @return String Collection of values
  334.      */
  335.     private static List<String> parseList(final String value, final char delimiter) {
  336.         final StringTokenizer tokenizer = new StringTokenizer(value, Character.toString(delimiter));
  337.         final List<String> tokens = new ArrayList<>(tokenizer.countTokens());
  338.         while (tokenizer.hasMoreTokens()) {
  339.             tokens.add(tokenizer.nextToken());
  340.         }
  341.         return tokens;
  342.     }

  343.     /**
  344.      * Creates and return a new {@code BasicDataSource} instance. If no instance can be created, return
  345.      * {@code null} instead.
  346.      *
  347.      * @param obj
  348.      *            The possibly null object containing location or reference information that can be used in creating an
  349.      *            object
  350.      * @param name
  351.      *            The name of this object relative to {@code nameCtx}
  352.      * @param nameCtx
  353.      *            The context relative to which the {@code name} parameter is specified, or {@code null} if
  354.      *            {@code name} is relative to the default initial context
  355.      * @param environment
  356.      *            The possibly null environment that is used in creating this object
  357.      *
  358.      * @throws SQLException
  359.      *             if an exception occurs creating the instance
  360.      */
  361.     @Override
  362.     public Object getObjectInstance(final Object obj, final Name name, final Context nameCtx,
  363.             final Hashtable<?, ?> environment) throws SQLException {

  364.         // We only know how to deal with {@code javax.naming.Reference}s
  365.         // that specify a class name of "javax.sql.DataSource"
  366.         if (obj == null || !(obj instanceof Reference)) {
  367.             return null;
  368.         }
  369.         final Reference ref = (Reference) obj;
  370.         if (!"javax.sql.DataSource".equals(ref.getClassName())) {
  371.             return null;
  372.         }

  373.         // Check property names and log warnings about obsolete and / or unknown properties
  374.         final List<String> warnMessages = new ArrayList<>();
  375.         final List<String> infoMessages = new ArrayList<>();
  376.         validatePropertyNames(ref, name, warnMessages, infoMessages);
  377.         warnMessages.forEach(log::warn);
  378.         infoMessages.forEach(log::info);

  379.         final Properties properties = new Properties();
  380.         ALL_PROPERTY_NAMES.forEach(propertyName -> {
  381.             final RefAddr ra = ref.get(propertyName);
  382.             if (ra != null) {
  383.                 properties.setProperty(propertyName, Objects.toString(ra.getContent(), null));
  384.             }
  385.         });

  386.         return createDataSource(properties);
  387.     }

  388.     /**
  389.      * Collects warnings and info messages. Warnings are generated when an obsolete property is set. Unknown properties
  390.      * generate info messages.
  391.      *
  392.      * @param ref
  393.      *            Reference to check properties of
  394.      * @param name
  395.      *            Name provided to getObject
  396.      * @param warnMessages
  397.      *            container for warning messages
  398.      * @param infoMessages
  399.      *            container for info messages
  400.      */
  401.     private void validatePropertyNames(final Reference ref, final Name name, final List<String> warnMessages,
  402.             final List<String> infoMessages) {
  403.         final String nameString = name != null ? "Name = " + name.toString() + " " : "";
  404.         NUPROP_WARNTEXT.forEach((propertyName, value) -> {
  405.             final RefAddr ra = ref.get(propertyName);
  406.             if (ra != null && !ALL_PROPERTY_NAMES.contains(ra.getType())) {
  407.                 final StringBuilder stringBuilder = new StringBuilder(nameString);
  408.                 final String propertyValue = Objects.toString(ra.getContent(), null);
  409.                 stringBuilder.append(value).append(" You have set value of \"").append(propertyValue).append("\" for \"").append(propertyName)
  410.                         .append("\" property, which is being ignored.");
  411.                 warnMessages.add(stringBuilder.toString());
  412.             }
  413.         });

  414.         final Enumeration<RefAddr> allRefAddrs = ref.getAll();
  415.         while (allRefAddrs.hasMoreElements()) {
  416.             final RefAddr ra = allRefAddrs.nextElement();
  417.             final String propertyName = ra.getType();
  418.             // If property name is not in the properties list, we haven't warned on it
  419.             // and it is not in the "silent" list, tell user we are ignoring it.
  420.             if (!(ALL_PROPERTY_NAMES.contains(propertyName) || NUPROP_WARNTEXT.containsKey(propertyName) || SILENT_PROPERTIES.contains(propertyName))) {
  421.                 final String propertyValue = Objects.toString(ra.getContent(), null);
  422.                 final StringBuilder stringBuilder = new StringBuilder(nameString);
  423.                 stringBuilder.append("Ignoring unknown property: ").append("value of \"").append(propertyValue).append("\" for \"").append(propertyName)
  424.                         .append("\" property");
  425.                 infoMessages.add(stringBuilder.toString());
  426.             }
  427.         }
  428.     }
  429. }