    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 *      https://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 */
    017package org.apache.commons.dbcp2;
    018
    019import java.io.ByteArrayInputStream;
    020import java.io.IOException;
    021import java.nio.charset.StandardCharsets;
    022import java.sql.Connection;
    023import java.sql.SQLException;
    024import java.time.Duration;
    025import java.util.ArrayList;
    026import java.util.Arrays;
    027import java.util.Enumeration;
    028import java.util.Hashtable;
    029import java.util.LinkedHashMap;
    030import java.util.List;
    031import java.util.Locale;
    032import java.util.Map;
    033import java.util.Objects;
    034import java.util.Optional;
    035import java.util.Properties;
    036import java.util.StringTokenizer;
    037import java.util.function.Consumer;
    038import java.util.function.Function;
    039
    040import javax.naming.Context;
    041import javax.naming.Name;
    042import javax.naming.RefAddr;
    043import javax.naming.Reference;
    044import javax.naming.spi.ObjectFactory;
    045
    046import org.apache.commons.logging.Log;
    047import org.apache.commons.logging.LogFactory;
    048import org.apache.commons.pool2.impl.BaseObjectPoolConfig;
    049import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
    050
    051/**
    052 * JNDI object factory that creates an instance of {@link BasicDataSource} that has been configured based on the
    053 * {@link RefAddr} values of the specified {@code Reference}, which must match the names and data types of the
    054 * {@link BasicDataSource} bean properties with the following exceptions:
    055 * <ul>
    056 * <li>{@code connectionInitSqls} must be passed to this factory as a single String using semicolon to delimit the
    057 * statements whereas {@link BasicDataSource} requires a collection of Strings.</li>
    058 * </ul>
    059 *
    060 * @since 2.0
    061 */
    062public class BasicDataSourceFactory implements ObjectFactory {
    063
    064    private static final Log log = LogFactory.getLog(BasicDataSourceFactory.class);
    065
    066    private static final String PROP_DEFAULT_AUTO_COMMIT = "defaultAutoCommit";
    067    private static final String PROP_DEFAULT_READ_ONLY = "defaultReadOnly";
    068    private static final String PROP_DEFAULT_TRANSACTION_ISOLATION = "defaultTransactionIsolation";
    069    private static final String PROP_DEFAULT_CATALOG = "defaultCatalog";
    070    private static final String PROP_DEFAULT_SCHEMA = "defaultSchema";
    071    private static final String PROP_CACHE_STATE = "cacheState";
    072    private static final String PROP_DRIVER_CLASS_NAME = "driverClassName";
    073    private static final String PROP_LIFO = "lifo";
    074    private static final String PROP_MAX_TOTAL = "maxTotal";
    075    private static final String PROP_MAX_IDLE = "maxIdle";
    076    private static final String PROP_MIN_IDLE = "minIdle";
    077    private static final String PROP_INITIAL_SIZE = "initialSize";
    078    private static final String PROP_MAX_WAIT_MILLIS = "maxWaitMillis";
    079    private static final String PROP_TEST_ON_CREATE = "testOnCreate";
    080    private static final String PROP_TEST_ON_BORROW = "testOnBorrow";
    081    private static final String PROP_TEST_ON_RETURN = "testOnReturn";
    082    private static final String PROP_TIME_BETWEEN_EVICTION_RUNS_MILLIS = "timeBetweenEvictionRunsMillis";
    083    private static final String PROP_NUM_TESTS_PER_EVICTION_RUN = "numTestsPerEvictionRun";
    084    private static final String PROP_MIN_EVICTABLE_IDLE_TIME_MILLIS = "minEvictableIdleTimeMillis";
    085    private static final String PROP_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS = "softMinEvictableIdleTimeMillis";
    086    private static final String PROP_EVICTION_POLICY_CLASS_NAME = "evictionPolicyClassName";
    087    private static final String PROP_TEST_WHILE_IDLE = "testWhileIdle";
    088    private static final String PROP_PASSWORD = Constants.KEY_PASSWORD;
    089    private static final String PROP_URL = "url";
    090    private static final String PROP_USER_NAME = "username";
    091    private static final String PROP_VALIDATION_QUERY = "validationQuery";
    092    private static final String PROP_VALIDATION_QUERY_TIMEOUT = "validationQueryTimeout";
    093    private static final String PROP_JMX_NAME = "jmxName";
    094    private static final String PROP_REGISTER_CONNECTION_MBEAN = "registerConnectionMBean";
    095    private static final String PROP_CONNECTION_FACTORY_CLASS_NAME = "connectionFactoryClassName";
    096
    097    /**
    098     * The property name for connectionInitSqls. The associated value String must be of the form [query;]*
    099     */
    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}