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 */
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.Collection;
028import java.util.Enumeration;
029import java.util.Hashtable;
030import java.util.LinkedHashMap;
031import java.util.List;
032import java.util.Locale;
033import java.util.Map;
034import java.util.Objects;
035import java.util.Optional;
036import java.util.Properties;
037import java.util.StringTokenizer;
038import java.util.function.Consumer;
039import java.util.function.Function;
040
041import javax.naming.Context;
042import javax.naming.Name;
043import javax.naming.RefAddr;
044import javax.naming.Reference;
045import javax.naming.spi.ObjectFactory;
046
047import org.apache.commons.logging.Log;
048import org.apache.commons.logging.LogFactory;
049import org.apache.commons.pool2.impl.BaseObjectPoolConfig;
050import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
051
052/**
053 * JNDI object factory that creates an instance of {@code BasicDataSource} that has been configured based on the
054 * {@code RefAddr} values of the specified {@code Reference}, which must match the names and data types of the
055 * {@code BasicDataSource} bean properties with the following exceptions:
056 * <ul>
057 * <li>{@code connectionInitSqls} must be passed to this factory as a single String using semicolon to delimit the
058 * statements whereas {@code BasicDataSource} requires a collection of Strings.</li>
059 * </ul>
060 *
061 * @since 2.0
062 */
063public class BasicDataSourceFactory implements ObjectFactory {
064
065    private static final Log log = LogFactory.getLog(BasicDataSourceFactory.class);
066
067    private static final String PROP_DEFAULT_AUTO_COMMIT = "defaultAutoCommit";
068    private static final String PROP_DEFAULT_READ_ONLY = "defaultReadOnly";
069    private static final String PROP_DEFAULT_TRANSACTION_ISOLATION = "defaultTransactionIsolation";
070    private static final String PROP_DEFAULT_CATALOG = "defaultCatalog";
071    private static final String PROP_DEFAULT_SCHEMA = "defaultSchema";
072    private static final String PROP_CACHE_STATE = "cacheState";
073    private static final String PROP_DRIVER_CLASS_NAME = "driverClassName";
074    private static final String PROP_LIFO = "lifo";
075    private static final String PROP_MAX_TOTAL = "maxTotal";
076    private static final String PROP_MAX_IDLE = "maxIdle";
077    private static final String PROP_MIN_IDLE = "minIdle";
078    private static final String PROP_INITIAL_SIZE = "initialSize";
079    private static final String PROP_MAX_WAIT_MILLIS = "maxWaitMillis";
080    private static final String PROP_TEST_ON_CREATE = "testOnCreate";
081    private static final String PROP_TEST_ON_BORROW = "testOnBorrow";
082    private static final String PROP_TEST_ON_RETURN = "testOnReturn";
083    private static final String PROP_TIME_BETWEEN_EVICTION_RUNS_MILLIS = "timeBetweenEvictionRunsMillis";
084    private static final String PROP_NUM_TESTS_PER_EVICTION_RUN = "numTestsPerEvictionRun";
085    private static final String PROP_MIN_EVICTABLE_IDLE_TIME_MILLIS = "minEvictableIdleTimeMillis";
086    private static final String PROP_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS = "softMinEvictableIdleTimeMillis";
087    private static final String PROP_EVICTION_POLICY_CLASS_NAME = "evictionPolicyClassName";
088    private static final String PROP_TEST_WHILE_IDLE = "testWhileIdle";
089    private static final String PROP_PASSWORD = Constants.KEY_PASSWORD;
090    private static final String PROP_URL = "url";
091    private static final String PROP_USER_NAME = "username";
092    private static final String PROP_VALIDATION_QUERY = "validationQuery";
093    private static final String PROP_VALIDATION_QUERY_TIMEOUT = "validationQueryTimeout";
094    private static final String PROP_JMX_NAME = "jmxName";
095    private static final String PROP_REGISTER_CONNECTION_MBEAN = "registerConnectionMBean";
096    private static final String PROP_CONNECTION_FACTORY_CLASS_NAME = "connectionFactoryClassName";
097
098    /**
099     * The property name for connectionInitSqls. The associated value String must be of the form [query;]*
100     */
101    private static final String PROP_CONNECTION_INIT_SQLS = "connectionInitSqls";
102    private static final String PROP_ACCESS_TO_UNDERLYING_CONNECTION_ALLOWED = "accessToUnderlyingConnectionAllowed";
103    private static final String PROP_REMOVE_ABANDONED_ON_BORROW = "removeAbandonedOnBorrow";
104    private static final String PROP_REMOVE_ABANDONED_ON_MAINTENANCE = "removeAbandonedOnMaintenance";
105    private static final String PROP_REMOVE_ABANDONED_TIMEOUT = "removeAbandonedTimeout";
106    private static final String PROP_LOG_ABANDONED = "logAbandoned";
107    private static final String PROP_ABANDONED_USAGE_TRACKING = "abandonedUsageTracking";
108    private static final String PROP_POOL_PREPARED_STATEMENTS = "poolPreparedStatements";
109    private static final String PROP_CLEAR_STATEMENT_POOL_ON_RETURN = "clearStatementPoolOnReturn";
110    private static final String PROP_MAX_OPEN_PREPARED_STATEMENTS = "maxOpenPreparedStatements";
111    private static final String PROP_CONNECTION_PROPERTIES = "connectionProperties";
112    private static final String PROP_MAX_CONN_LIFETIME_MILLIS = "maxConnLifetimeMillis";
113    private static final String PROP_LOG_EXPIRED_CONNECTIONS = "logExpiredConnections";
114    private static final String PROP_ROLLBACK_ON_RETURN = "rollbackOnReturn";
115    private static final String PROP_ENABLE_AUTO_COMMIT_ON_RETURN = "enableAutoCommitOnReturn";
116    private static final String PROP_DEFAULT_QUERY_TIMEOUT = "defaultQueryTimeout";
117    private static final String PROP_FAST_FAIL_VALIDATION = "fastFailValidation";
118
119    /**
120     * Value string must be of the form [STATE_CODE,]*
121     */
122    private static final String PROP_DISCONNECTION_SQL_CODES = "disconnectionSqlCodes";
123
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    /*
133     * Block with properties expected in a DataSource This props will not be listed as ignored - we know that they may
134     * appear in Resource, and not listing them as ignored.
135     */
136    private static final String SILENT_PROP_FACTORY = "factory";
137    private static final String SILENT_PROP_SCOPE = "scope";
138    private static final String SILENT_PROP_SINGLETON = "singleton";
139    private static final String SILENT_PROP_AUTH = "auth";
140
141    private static final List<String> ALL_PROPERTY_NAMES = Arrays.asList(PROP_DEFAULT_AUTO_COMMIT, PROP_DEFAULT_READ_ONLY,
142            PROP_DEFAULT_TRANSACTION_ISOLATION, PROP_DEFAULT_CATALOG, PROP_DEFAULT_SCHEMA, PROP_CACHE_STATE,
143            PROP_DRIVER_CLASS_NAME, PROP_LIFO, PROP_MAX_TOTAL, PROP_MAX_IDLE, PROP_MIN_IDLE, PROP_INITIAL_SIZE,
144            PROP_MAX_WAIT_MILLIS, PROP_TEST_ON_CREATE, PROP_TEST_ON_BORROW, PROP_TEST_ON_RETURN,
145            PROP_TIME_BETWEEN_EVICTION_RUNS_MILLIS, PROP_NUM_TESTS_PER_EVICTION_RUN, PROP_MIN_EVICTABLE_IDLE_TIME_MILLIS,
146            PROP_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS, PROP_EVICTION_POLICY_CLASS_NAME, PROP_TEST_WHILE_IDLE, PROP_PASSWORD,
147            PROP_URL, PROP_USER_NAME, PROP_VALIDATION_QUERY, PROP_VALIDATION_QUERY_TIMEOUT, PROP_CONNECTION_INIT_SQLS,
148            PROP_ACCESS_TO_UNDERLYING_CONNECTION_ALLOWED, PROP_REMOVE_ABANDONED_ON_BORROW, PROP_REMOVE_ABANDONED_ON_MAINTENANCE,
149            PROP_REMOVE_ABANDONED_TIMEOUT, PROP_LOG_ABANDONED, PROP_ABANDONED_USAGE_TRACKING, PROP_POOL_PREPARED_STATEMENTS,
150            PROP_CLEAR_STATEMENT_POOL_ON_RETURN,
151            PROP_MAX_OPEN_PREPARED_STATEMENTS, PROP_CONNECTION_PROPERTIES, PROP_MAX_CONN_LIFETIME_MILLIS,
152            PROP_LOG_EXPIRED_CONNECTIONS, PROP_ROLLBACK_ON_RETURN, PROP_ENABLE_AUTO_COMMIT_ON_RETURN,
153            PROP_DEFAULT_QUERY_TIMEOUT, PROP_FAST_FAIL_VALIDATION, PROP_DISCONNECTION_SQL_CODES, PROP_JMX_NAME,
154            PROP_REGISTER_CONNECTION_MBEAN, PROP_CONNECTION_FACTORY_CLASS_NAME);
155
156    /**
157     * Obsolete properties from DBCP 1.x. with warning strings suggesting new properties. LinkedHashMap will guarantee
158     * that properties will be listed to output in order of insertion into map.
159     */
160    private static final Map<String, String> NUPROP_WARNTEXT = new LinkedHashMap<>();
161
162    static {
163        NUPROP_WARNTEXT.put(NUPROP_MAX_ACTIVE,
164                "Property " + NUPROP_MAX_ACTIVE + " is not used in DBCP2, use " + PROP_MAX_TOTAL + " instead. "
165                        + PROP_MAX_TOTAL + " default value is " + GenericObjectPoolConfig.DEFAULT_MAX_TOTAL + ".");
166        NUPROP_WARNTEXT.put(NUPROP_REMOVE_ABANDONED,
167                "Property " + NUPROP_REMOVE_ABANDONED + " is not used in DBCP2," + " use one or both of "
168                        + PROP_REMOVE_ABANDONED_ON_BORROW + " or " + PROP_REMOVE_ABANDONED_ON_MAINTENANCE + " instead. "
169                        + "Both have default value set to false.");
170        NUPROP_WARNTEXT.put(NUPROP_MAXWAIT,
171                "Property " + NUPROP_MAXWAIT + " is not used in DBCP2" + " , use " + PROP_MAX_WAIT_MILLIS + " instead. "
172                        + PROP_MAX_WAIT_MILLIS + " default value is " + BaseObjectPoolConfig.DEFAULT_MAX_WAIT
173                        + ".");
174    }
175
176    /**
177     * Silent Properties. These properties will not be listed as ignored - we know that they may appear in JDBC Resource
178     * references, and we will not list them as ignored.
179     */
180    private static final List<String> SILENT_PROPERTIES = new ArrayList<>();
181
182    static {
183        SILENT_PROPERTIES.add(SILENT_PROP_FACTORY);
184        SILENT_PROPERTIES.add(SILENT_PROP_SCOPE);
185        SILENT_PROPERTIES.add(SILENT_PROP_SINGLETON);
186        SILENT_PROPERTIES.add(SILENT_PROP_AUTH);
187
188    }
189
190    private static <V> void accept(final Properties properties, final String name, final Function<String, V> parser, final Consumer<V> consumer) {
191        getOptional(properties, name).ifPresent(v -> consumer.accept(parser.apply(v)));
192    }
193
194    private static void acceptBoolean(final Properties properties, final String name, final Consumer<Boolean> consumer) {
195        accept(properties, name, Boolean::parseBoolean, consumer);
196    }
197
198    private static void acceptDurationOfMillis(final Properties properties, final String name, final Consumer<Duration> consumer) {
199        accept(properties, name, s -> Duration.ofMillis(Long.parseLong(s)), consumer);
200    }
201
202    private static void acceptDurationOfSeconds(final Properties properties, final String name, final Consumer<Duration> consumer) {
203        accept(properties, name, s -> Duration.ofSeconds(Long.parseLong(s)), consumer);
204    }
205
206    private static void acceptInt(final Properties properties, final String name, final Consumer<Integer> consumer) {
207        accept(properties, name, Integer::parseInt, consumer);
208    }
209
210    private static void acceptString(final Properties properties, final String name, final Consumer<String> consumer) {
211        accept(properties, name, Function.identity(), consumer);
212    }
213
214    /**
215     * Creates and configures a {@link BasicDataSource} instance based on the given properties.
216     *
217     * @param properties
218     *            The data source configuration properties.
219     * @return A new a {@link BasicDataSource} instance based on the given properties.
220     * @throws SQLException
221     *             Thrown when an error occurs creating the data source.
222     */
223    public static BasicDataSource createDataSource(final Properties properties) throws SQLException {
224        final BasicDataSource dataSource = new BasicDataSource();
225        acceptBoolean(properties, PROP_DEFAULT_AUTO_COMMIT, dataSource::setDefaultAutoCommit);
226        acceptBoolean(properties, PROP_DEFAULT_READ_ONLY, dataSource::setDefaultReadOnly);
227
228        getOptional(properties, PROP_DEFAULT_TRANSACTION_ISOLATION).ifPresent(value -> {
229            value = value.toUpperCase(Locale.ROOT);
230            int level = PoolableConnectionFactory.UNKNOWN_TRANSACTION_ISOLATION;
231            if ("NONE".equals(value)) {
232                level = Connection.TRANSACTION_NONE;
233            } else if ("READ_COMMITTED".equals(value)) {
234                level = Connection.TRANSACTION_READ_COMMITTED;
235            } else if ("READ_UNCOMMITTED".equals(value)) {
236                level = Connection.TRANSACTION_READ_UNCOMMITTED;
237            } else if ("REPEATABLE_READ".equals(value)) {
238                level = Connection.TRANSACTION_REPEATABLE_READ;
239            } else if ("SERIALIZABLE".equals(value)) {
240                level = Connection.TRANSACTION_SERIALIZABLE;
241            } else {
242                try {
243                    level = Integer.parseInt(value);
244                } catch (final NumberFormatException e) {
245                    System.err.println("Could not parse defaultTransactionIsolation: " + value);
246                    System.err.println("WARNING: defaultTransactionIsolation not set");
247                    System.err.println("using default value of database driver");
248                    level = PoolableConnectionFactory.UNKNOWN_TRANSACTION_ISOLATION;
249                }
250            }
251            dataSource.setDefaultTransactionIsolation(level);
252        });
253
254        acceptString(properties, PROP_DEFAULT_SCHEMA, dataSource::setDefaultSchema);
255        acceptString(properties, PROP_DEFAULT_CATALOG, dataSource::setDefaultCatalog);
256        acceptBoolean(properties, PROP_CACHE_STATE, dataSource::setCacheState);
257        acceptString(properties, PROP_DRIVER_CLASS_NAME, dataSource::setDriverClassName);
258        acceptBoolean(properties, PROP_LIFO, dataSource::setLifo);
259        acceptInt(properties, PROP_MAX_TOTAL, dataSource::setMaxTotal);
260        acceptInt(properties, PROP_MAX_IDLE, dataSource::setMaxIdle);
261        acceptInt(properties, PROP_MIN_IDLE, dataSource::setMinIdle);
262        acceptInt(properties, PROP_INITIAL_SIZE, dataSource::setInitialSize);
263        acceptDurationOfMillis(properties, PROP_MAX_WAIT_MILLIS, dataSource::setMaxWait);
264        acceptBoolean(properties, PROP_TEST_ON_CREATE, dataSource::setTestOnCreate);
265        acceptBoolean(properties, PROP_TEST_ON_BORROW, dataSource::setTestOnBorrow);
266        acceptBoolean(properties, PROP_TEST_ON_RETURN, dataSource::setTestOnReturn);
267        acceptDurationOfMillis(properties, PROP_TIME_BETWEEN_EVICTION_RUNS_MILLIS, dataSource::setDurationBetweenEvictionRuns);
268        acceptInt(properties, PROP_NUM_TESTS_PER_EVICTION_RUN, dataSource::setNumTestsPerEvictionRun);
269        acceptDurationOfMillis(properties, PROP_MIN_EVICTABLE_IDLE_TIME_MILLIS, dataSource::setMinEvictableIdle);
270        acceptDurationOfMillis(properties, PROP_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS, dataSource::setSoftMinEvictableIdle);
271        acceptString(properties, PROP_EVICTION_POLICY_CLASS_NAME, dataSource::setEvictionPolicyClassName);
272        acceptBoolean(properties, PROP_TEST_WHILE_IDLE, dataSource::setTestWhileIdle);
273        acceptString(properties, PROP_PASSWORD, dataSource::setPassword);
274        acceptString(properties, PROP_URL, dataSource::setUrl);
275        acceptString(properties, PROP_USER_NAME, dataSource::setUsername);
276        acceptString(properties, PROP_VALIDATION_QUERY, dataSource::setValidationQuery);
277        acceptDurationOfSeconds(properties, PROP_VALIDATION_QUERY_TIMEOUT, dataSource::setValidationQueryTimeout);
278        acceptBoolean(properties, PROP_ACCESS_TO_UNDERLYING_CONNECTION_ALLOWED, dataSource::setAccessToUnderlyingConnectionAllowed);
279        acceptBoolean(properties, PROP_REMOVE_ABANDONED_ON_BORROW, dataSource::setRemoveAbandonedOnBorrow);
280        acceptBoolean(properties, PROP_REMOVE_ABANDONED_ON_MAINTENANCE, dataSource::setRemoveAbandonedOnMaintenance);
281        acceptDurationOfSeconds(properties, PROP_REMOVE_ABANDONED_TIMEOUT, dataSource::setRemoveAbandonedTimeout);
282        acceptBoolean(properties, PROP_LOG_ABANDONED, dataSource::setLogAbandoned);
283        acceptBoolean(properties, PROP_ABANDONED_USAGE_TRACKING, dataSource::setAbandonedUsageTracking);
284        acceptBoolean(properties, PROP_POOL_PREPARED_STATEMENTS, dataSource::setPoolPreparedStatements);
285        acceptBoolean(properties, PROP_CLEAR_STATEMENT_POOL_ON_RETURN, dataSource::setClearStatementPoolOnReturn);
286        acceptInt(properties, PROP_MAX_OPEN_PREPARED_STATEMENTS, dataSource::setMaxOpenPreparedStatements);
287        getOptional(properties, PROP_CONNECTION_INIT_SQLS).ifPresent(v -> dataSource.setConnectionInitSqls(parseList(v, ';')));
288
289        final String value = properties.getProperty(PROP_CONNECTION_PROPERTIES);
290        if (value != null) {
291            for (final Object key : getProperties(value).keySet()) {
292                final String propertyName = Objects.toString(key, null);
293                dataSource.addConnectionProperty(propertyName, getProperties(value).getProperty(propertyName));
294            }
295        }
296
297        acceptDurationOfMillis(properties, PROP_MAX_CONN_LIFETIME_MILLIS, dataSource::setMaxConn);
298        acceptBoolean(properties, PROP_LOG_EXPIRED_CONNECTIONS, dataSource::setLogExpiredConnections);
299        acceptString(properties, PROP_JMX_NAME, dataSource::setJmxName);
300        acceptBoolean(properties, PROP_REGISTER_CONNECTION_MBEAN, dataSource::setRegisterConnectionMBean);
301        acceptBoolean(properties, PROP_ENABLE_AUTO_COMMIT_ON_RETURN, dataSource::setAutoCommitOnReturn);
302        acceptBoolean(properties, PROP_ROLLBACK_ON_RETURN, dataSource::setRollbackOnReturn);
303        acceptDurationOfSeconds(properties, PROP_DEFAULT_QUERY_TIMEOUT, dataSource::setDefaultQueryTimeout);
304        acceptBoolean(properties, PROP_FAST_FAIL_VALIDATION, dataSource::setFastFailValidation);
305        getOptional(properties, PROP_DISCONNECTION_SQL_CODES).ifPresent(v -> dataSource.setDisconnectionSqlCodes(parseList(v, ',')));
306        acceptString(properties, PROP_CONNECTION_FACTORY_CLASS_NAME, dataSource::setConnectionFactoryClassName);
307
308        // DBCP-215
309        // Trick to make sure that initialSize connections are created
310        if (dataSource.getInitialSize() > 0) {
311            dataSource.getLogWriter();
312        }
313
314        // Return the configured DataSource instance
315        return dataSource;
316    }
317
318    private static Optional<String> getOptional(final Properties properties, final String name) {
319        return Optional.ofNullable(properties.getProperty(name));
320    }
321
322    /**
323     * Parse properties from the string. Format of the string must be [propertyName=property;]*
324     *
325     * @param propText The source text
326     * @return Properties A new Properties instance
327     * @throws SQLException When a paring exception occurs
328     */
329    private static Properties getProperties(final String propText) throws SQLException {
330        final Properties p = new Properties();
331        if (propText != null) {
332            try {
333                p.load(new ByteArrayInputStream(propText.replace(';', '\n').getBytes(StandardCharsets.ISO_8859_1)));
334            } catch (IOException e) {
335                throw new SQLException(propText, e);
336            }
337        }
338        return p;
339    }
340
341    /**
342     * Parse list of property values from a delimited string
343     *
344     * @param value
345     *            delimited list of values
346     * @param delimiter
347     *            character used to separate values in the list
348     * @return String Collection of values
349     */
350    private static Collection<String> parseList(final String value, final char delimiter) {
351        final StringTokenizer tokenizer = new StringTokenizer(value, Character.toString(delimiter));
352        final Collection<String> tokens = new ArrayList<>(tokenizer.countTokens());
353        while (tokenizer.hasMoreTokens()) {
354            tokens.add(tokenizer.nextToken());
355        }
356        return tokens;
357    }
358
359    /**
360     * Creates and return a new {@code BasicDataSource} instance. If no instance can be created, return
361     * {@code null} instead.
362     *
363     * @param obj
364     *            The possibly null object containing location or reference information that can be used in creating an
365     *            object
366     * @param name
367     *            The name of this object relative to {@code nameCtx}
368     * @param nameCtx
369     *            The context relative to which the {@code name} parameter is specified, or {@code null} if
370     *            {@code name} is relative to the default initial context
371     * @param environment
372     *            The possibly null environment that is used in creating this object
373     *
374     * @throws SQLException
375     *             if an exception occurs creating the instance
376     */
377    @Override
378    public Object getObjectInstance(final Object obj, final Name name, final Context nameCtx,
379            final Hashtable<?, ?> environment) throws SQLException {
380
381        // We only know how to deal with {@code javax.naming.Reference}s
382        // that specify a class name of "javax.sql.DataSource"
383        if (obj == null || !(obj instanceof Reference)) {
384            return null;
385        }
386        final Reference ref = (Reference) obj;
387        if (!"javax.sql.DataSource".equals(ref.getClassName())) {
388            return null;
389        }
390
391        // Check property names and log warnings about obsolete and / or unknown properties
392        final List<String> warnMessages = new ArrayList<>();
393        final List<String> infoMessages = new ArrayList<>();
394        validatePropertyNames(ref, name, warnMessages, infoMessages);
395        warnMessages.forEach(log::warn);
396        infoMessages.forEach(log::info);
397
398        final Properties properties = new Properties();
399        ALL_PROPERTY_NAMES.forEach(propertyName -> {
400            final RefAddr ra = ref.get(propertyName);
401            if (ra != null) {
402                properties.setProperty(propertyName, Objects.toString(ra.getContent(), null));
403            }
404        });
405
406        return createDataSource(properties);
407    }
408
409    /**
410     * Collects warnings and info messages. Warnings are generated when an obsolete property is set. Unknown properties
411     * generate info messages.
412     *
413     * @param ref
414     *            Reference to check properties of
415     * @param name
416     *            Name provided to getObject
417     * @param warnMessages
418     *            container for warning messages
419     * @param infoMessages
420     *            container for info messages
421     */
422    private void validatePropertyNames(final Reference ref, final Name name, final List<String> warnMessages,
423            final List<String> infoMessages) {
424        final String nameString = name != null ? "Name = " + name.toString() + " " : "";
425        NUPROP_WARNTEXT.forEach((propertyName, value) -> {
426            final RefAddr ra = ref.get(propertyName);
427            if (ra != null && !ALL_PROPERTY_NAMES.contains(ra.getType())) {
428                final StringBuilder stringBuilder = new StringBuilder(nameString);
429                final String propertyValue = Objects.toString(ra.getContent(), null);
430                stringBuilder.append(value).append(" You have set value of \"").append(propertyValue).append("\" for \"").append(propertyName)
431                        .append("\" property, which is being ignored.");
432                warnMessages.add(stringBuilder.toString());
433            }
434        });
435
436        final Enumeration<RefAddr> allRefAddrs = ref.getAll();
437        while (allRefAddrs.hasMoreElements()) {
438            final RefAddr ra = allRefAddrs.nextElement();
439            final String propertyName = ra.getType();
440            // If property name is not in the properties list, we haven't warned on it
441            // and it is not in the "silent" list, tell user we are ignoring it.
442            if (!(ALL_PROPERTY_NAMES.contains(propertyName) || NUPROP_WARNTEXT.containsKey(propertyName) || SILENT_PROPERTIES.contains(propertyName))) {
443                final String propertyValue = Objects.toString(ra.getContent(), null);
444                final StringBuilder stringBuilder = new StringBuilder(nameString);
445                stringBuilder.append("Ignoring unknown property: ").append("value of \"").append(propertyValue).append("\" for \"").append(propertyName)
446                        .append("\" property");
447                infoMessages.add(stringBuilder.toString());
448            }
449        }
450    }
451}