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 semi-colon 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_MAX_OPEN_PREPARED_STATEMENTS = "maxOpenPreparedStatements";
104    private static final String PROP_CONNECTION_PROPERTIES = "connectionProperties";
105    private static final String PROP_MAX_CONN_LIFETIME_MILLIS = "maxConnLifetimeMillis";
106    private static final String PROP_LOG_EXPIRED_CONNECTIONS = "logExpiredConnections";
107    private static final String PROP_ROLLBACK_ON_RETURN = "rollbackOnReturn";
108    private static final String PROP_ENABLE_AUTO_COMMIT_ON_RETURN = "enableAutoCommitOnReturn";
109    private static final String PROP_DEFAULT_QUERY_TIMEOUT = "defaultQueryTimeout";
110    private static final String PROP_FAST_FAIL_VALIDATION = "fastFailValidation";
111
112    /**
113     * Value string must be of the form [STATE_CODE,]*
114     */
115    private static final String PROP_DISCONNECTION_SQL_CODES = "disconnectionSqlCodes";
116
117    /*
118     * Block with obsolete properties from DBCP 1.x. Warn users that these are ignored and they should use the 2.x
119     * properties.
120     */
121    private static final String NUPROP_MAX_ACTIVE = "maxActive";
122    private static final String NUPROP_REMOVE_ABANDONED = "removeAbandoned";
123    private static final String NUPROP_MAXWAIT = "maxWait";
124
125    /*
126     * Block with properties expected in a DataSource This props will not be listed as ignored - we know that they may
127     * appear in Resource, and not listing them as ignored.
128     */
129    private static final String SILENT_PROP_FACTORY = "factory";
130    private static final String SILENT_PROP_SCOPE = "scope";
131    private static final String SILENT_PROP_SINGLETON = "singleton";
132    private static final String SILENT_PROP_AUTH = "auth";
133
134    private static final String[] ALL_PROPERTIES = {PROP_DEFAULT_AUTO_COMMIT, PROP_DEFAULT_READ_ONLY,
135            PROP_DEFAULT_TRANSACTION_ISOLATION, PROP_DEFAULT_CATALOG, PROP_DEFAULT_SCHEMA, PROP_CACHE_STATE,
136            PROP_DRIVER_CLASS_NAME, PROP_LIFO, PROP_MAX_TOTAL, PROP_MAX_IDLE, PROP_MIN_IDLE, PROP_INITIAL_SIZE,
137            PROP_MAX_WAIT_MILLIS, PROP_TEST_ON_CREATE, PROP_TEST_ON_BORROW, PROP_TEST_ON_RETURN,
138            PROP_TIME_BETWEEN_EVICTION_RUNS_MILLIS, PROP_NUM_TESTS_PER_EVICTION_RUN, PROP_MIN_EVICTABLE_IDLE_TIME_MILLIS,
139            PROP_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS, PROP_EVICTION_POLICY_CLASS_NAME, PROP_TEST_WHILE_IDLE, PROP_PASSWORD,
140            PROP_URL, PROP_USER_NAME, PROP_VALIDATION_QUERY, PROP_VALIDATION_QUERY_TIMEOUT, PROP_CONNECTION_INIT_SQLS,
141            PROP_ACCESS_TO_UNDERLYING_CONNECTION_ALLOWED, PROP_REMOVE_ABANDONED_ON_BORROW, PROP_REMOVE_ABANDONED_ON_MAINTENANCE,
142            PROP_REMOVE_ABANDONED_TIMEOUT, PROP_LOG_ABANDONED, PROP_ABANDONED_USAGE_TRACKING, PROP_POOL_PREPARED_STATEMENTS,
143            PROP_MAX_OPEN_PREPARED_STATEMENTS, PROP_CONNECTION_PROPERTIES, PROP_MAX_CONN_LIFETIME_MILLIS,
144            PROP_LOG_EXPIRED_CONNECTIONS, PROP_ROLLBACK_ON_RETURN, PROP_ENABLE_AUTO_COMMIT_ON_RETURN,
145            PROP_DEFAULT_QUERY_TIMEOUT, PROP_FAST_FAIL_VALIDATION, PROP_DISCONNECTION_SQL_CODES, PROP_JMX_NAME,
146            PROP_CONNECTION_FACTORY_CLASS_NAME };
147
148    /**
149     * Obsolete properties from DBCP 1.x. with warning strings suggesting new properties. LinkedHashMap will guarantee
150     * that properties will be listed to output in order of insertion into map.
151     */
152    private static final Map<String, String> NUPROP_WARNTEXT = new LinkedHashMap<>();
153
154    static {
155        NUPROP_WARNTEXT.put(NUPROP_MAX_ACTIVE,
156                "Property " + NUPROP_MAX_ACTIVE + " is not used in DBCP2, use " + PROP_MAX_TOTAL + " instead. "
157                        + PROP_MAX_TOTAL + " default value is " + GenericObjectPoolConfig.DEFAULT_MAX_TOTAL + ".");
158        NUPROP_WARNTEXT.put(NUPROP_REMOVE_ABANDONED,
159                "Property " + NUPROP_REMOVE_ABANDONED + " is not used in DBCP2," + " use one or both of "
160                        + PROP_REMOVE_ABANDONED_ON_BORROW + " or " + PROP_REMOVE_ABANDONED_ON_MAINTENANCE + " instead. "
161                        + "Both have default value set to false.");
162        NUPROP_WARNTEXT.put(NUPROP_MAXWAIT,
163                "Property " + NUPROP_MAXWAIT + " is not used in DBCP2" + " , use " + PROP_MAX_WAIT_MILLIS + " instead. "
164                        + PROP_MAX_WAIT_MILLIS + " default value is " + BaseObjectPoolConfig.DEFAULT_MAX_WAIT_MILLIS
165                        + ".");
166    }
167
168    /**
169     * Silent Properties. These properties will not be listed as ignored - we know that they may appear in JDBC Resource
170     * references, and we will not list them as ignored.
171     */
172    private static final List<String> SILENT_PROPERTIES = new ArrayList<>();
173
174    static {
175        SILENT_PROPERTIES.add(SILENT_PROP_FACTORY);
176        SILENT_PROPERTIES.add(SILENT_PROP_SCOPE);
177        SILENT_PROPERTIES.add(SILENT_PROP_SINGLETON);
178        SILENT_PROPERTIES.add(SILENT_PROP_AUTH);
179
180    }
181
182    // -------------------------------------------------- ObjectFactory Methods
183
184    /**
185     * <p>
186     * Create and return a new <code>BasicDataSource</code> instance. If no instance can be created, return
187     * <code>null</code> instead.
188     * </p>
189     *
190     * @param obj
191     *            The possibly null object containing location or reference information that can be used in creating an
192     *            object
193     * @param name
194     *            The name of this object relative to <code>nameCtx</code>
195     * @param nameCtx
196     *            The context relative to which the <code>name</code> parameter is specified, or <code>null</code> if
197     *            <code>name</code> is relative to the default initial context
198     * @param environment
199     *            The possibly null environment that is used in creating this object
200     *
201     * @throws Exception
202     *             if an exception occurs creating the instance
203     */
204    @Override
205    public Object getObjectInstance(final Object obj, final Name name, final Context nameCtx,
206            final Hashtable<?, ?> environment) throws Exception {
207
208        // We only know how to deal with <code>javax.naming.Reference</code>s
209        // that specify a class name of "javax.sql.DataSource"
210        if (obj == null || !(obj instanceof Reference)) {
211            return null;
212        }
213        final Reference ref = (Reference) obj;
214        if (!"javax.sql.DataSource".equals(ref.getClassName())) {
215            return null;
216        }
217
218        // Check property names and log warnings about obsolete and / or unknown properties
219        final List<String> warnings = new ArrayList<>();
220        final List<String> infoMessages = new ArrayList<>();
221        validatePropertyNames(ref, name, warnings, infoMessages);
222        for (final String warning : warnings) {
223            log.warn(warning);
224        }
225        for (final String infoMessage : infoMessages) {
226            log.info(infoMessage);
227        }
228
229        final Properties properties = new Properties();
230        for (final String propertyName : ALL_PROPERTIES) {
231            final RefAddr ra = ref.get(propertyName);
232            if (ra != null) {
233                final String propertyValue = ra.getContent().toString();
234                properties.setProperty(propertyName, propertyValue);
235            }
236        }
237
238        return createDataSource(properties);
239    }
240
241    /**
242     * Collects warnings and info messages. Warnings are generated when an obsolete property is set. Unknown properties
243     * generate info messages.
244     *
245     * @param ref
246     *            Reference to check properties of
247     * @param name
248     *            Name provided to getObject
249     * @param warnings
250     *            container for warning messages
251     * @param infoMessages
252     *            container for info messages
253     */
254    private void validatePropertyNames(final Reference ref, final Name name, final List<String> warnings,
255            final List<String> infoMessages) {
256        final List<String> allPropsAsList = Arrays.asList(ALL_PROPERTIES);
257        final String nameString = name != null ? "Name = " + name.toString() + " " : "";
258        if (NUPROP_WARNTEXT != null && !NUPROP_WARNTEXT.keySet().isEmpty()) {
259            for (final String propertyName : NUPROP_WARNTEXT.keySet()) {
260                final RefAddr ra = ref.get(propertyName);
261                if (ra != null && !allPropsAsList.contains(ra.getType())) {
262                    final StringBuilder stringBuilder = new StringBuilder(nameString);
263                    final String propertyValue = ra.getContent().toString();
264                    stringBuilder.append(NUPROP_WARNTEXT.get(propertyName)).append(" You have set value of \"")
265                            .append(propertyValue).append("\" for \"").append(propertyName)
266                            .append("\" property, which is being ignored.");
267                    warnings.add(stringBuilder.toString());
268                }
269            }
270        }
271
272        final Enumeration<RefAddr> allRefAddrs = ref.getAll();
273        while (allRefAddrs.hasMoreElements()) {
274            final RefAddr ra = allRefAddrs.nextElement();
275            final String propertyName = ra.getType();
276            // If property name is not in the properties list, we haven't warned on it
277            // and it is not in the "silent" list, tell user we are ignoring it.
278            if (!(allPropsAsList.contains(propertyName) || NUPROP_WARNTEXT.keySet().contains(propertyName)
279                    || SILENT_PROPERTIES.contains(propertyName))) {
280                final String propertyValue = ra.getContent().toString();
281                final StringBuilder stringBuilder = new StringBuilder(nameString);
282                stringBuilder.append("Ignoring unknown property: ").append("value of \"").append(propertyValue)
283                        .append("\" for \"").append(propertyName).append("\" property");
284                infoMessages.add(stringBuilder.toString());
285            }
286        }
287    }
288
289    /**
290     * Creates and configures a {@link BasicDataSource} instance based on the given properties.
291     *
292     * @param properties
293     *            The data source configuration properties.
294     * @return A new a {@link BasicDataSource} instance based on the given properties.
295     * @throws Exception
296     *             Thrown when an error occurs creating the data source.
297     */
298    public static BasicDataSource createDataSource(final Properties properties) throws Exception {
299        final BasicDataSource dataSource = new BasicDataSource();
300        String value = null;
301
302        value = properties.getProperty(PROP_DEFAULT_AUTO_COMMIT);
303        if (value != null) {
304            dataSource.setDefaultAutoCommit(Boolean.valueOf(value));
305        }
306
307        value = properties.getProperty(PROP_DEFAULT_READ_ONLY);
308        if (value != null) {
309            dataSource.setDefaultReadOnly(Boolean.valueOf(value));
310        }
311
312        value = properties.getProperty(PROP_DEFAULT_TRANSACTION_ISOLATION);
313        if (value != null) {
314            int level = PoolableConnectionFactory.UNKNOWN_TRANSACTION_ISOLATION;
315            if ("NONE".equalsIgnoreCase(value)) {
316                level = Connection.TRANSACTION_NONE;
317            } else if ("READ_COMMITTED".equalsIgnoreCase(value)) {
318                level = Connection.TRANSACTION_READ_COMMITTED;
319            } else if ("READ_UNCOMMITTED".equalsIgnoreCase(value)) {
320                level = Connection.TRANSACTION_READ_UNCOMMITTED;
321            } else if ("REPEATABLE_READ".equalsIgnoreCase(value)) {
322                level = Connection.TRANSACTION_REPEATABLE_READ;
323            } else if ("SERIALIZABLE".equalsIgnoreCase(value)) {
324                level = Connection.TRANSACTION_SERIALIZABLE;
325            } else {
326                try {
327                    level = Integer.parseInt(value);
328                } catch (final NumberFormatException e) {
329                    System.err.println("Could not parse defaultTransactionIsolation: " + value);
330                    System.err.println("WARNING: defaultTransactionIsolation not set");
331                    System.err.println("using default value of database driver");
332                    level = PoolableConnectionFactory.UNKNOWN_TRANSACTION_ISOLATION;
333                }
334            }
335            dataSource.setDefaultTransactionIsolation(level);
336        }
337
338        value = properties.getProperty(PROP_DEFAULT_CATALOG);
339        if (value != null) {
340            dataSource.setDefaultCatalog(value);
341        }
342
343        value = properties.getProperty(PROP_DEFAULT_SCHEMA);
344        if (value != null) {
345            dataSource.setDefaultSchema(value);
346        }
347
348        value = properties.getProperty(PROP_CACHE_STATE);
349        if (value != null) {
350            dataSource.setCacheState(Boolean.valueOf(value).booleanValue());
351        }
352
353        value = properties.getProperty(PROP_DRIVER_CLASS_NAME);
354        if (value != null) {
355            dataSource.setDriverClassName(value);
356        }
357
358        value = properties.getProperty(PROP_LIFO);
359        if (value != null) {
360            dataSource.setLifo(Boolean.valueOf(value).booleanValue());
361        }
362
363        value = properties.getProperty(PROP_MAX_TOTAL);
364        if (value != null) {
365            dataSource.setMaxTotal(Integer.parseInt(value));
366        }
367
368        value = properties.getProperty(PROP_MAX_IDLE);
369        if (value != null) {
370            dataSource.setMaxIdle(Integer.parseInt(value));
371        }
372
373        value = properties.getProperty(PROP_MIN_IDLE);
374        if (value != null) {
375            dataSource.setMinIdle(Integer.parseInt(value));
376        }
377
378        value = properties.getProperty(PROP_INITIAL_SIZE);
379        if (value != null) {
380            dataSource.setInitialSize(Integer.parseInt(value));
381        }
382
383        value = properties.getProperty(PROP_MAX_WAIT_MILLIS);
384        if (value != null) {
385            dataSource.setMaxWaitMillis(Long.parseLong(value));
386        }
387
388        value = properties.getProperty(PROP_TEST_ON_CREATE);
389        if (value != null) {
390            dataSource.setTestOnCreate(Boolean.valueOf(value).booleanValue());
391        }
392
393        value = properties.getProperty(PROP_TEST_ON_BORROW);
394        if (value != null) {
395            dataSource.setTestOnBorrow(Boolean.valueOf(value).booleanValue());
396        }
397
398        value = properties.getProperty(PROP_TEST_ON_RETURN);
399        if (value != null) {
400            dataSource.setTestOnReturn(Boolean.valueOf(value).booleanValue());
401        }
402
403        value = properties.getProperty(PROP_TIME_BETWEEN_EVICTION_RUNS_MILLIS);
404        if (value != null) {
405            dataSource.setTimeBetweenEvictionRunsMillis(Long.parseLong(value));
406        }
407
408        value = properties.getProperty(PROP_NUM_TESTS_PER_EVICTION_RUN);
409        if (value != null) {
410            dataSource.setNumTestsPerEvictionRun(Integer.parseInt(value));
411        }
412
413        value = properties.getProperty(PROP_MIN_EVICTABLE_IDLE_TIME_MILLIS);
414        if (value != null) {
415            dataSource.setMinEvictableIdleTimeMillis(Long.parseLong(value));
416        }
417
418        value = properties.getProperty(PROP_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS);
419        if (value != null) {
420            dataSource.setSoftMinEvictableIdleTimeMillis(Long.parseLong(value));
421        }
422
423        value = properties.getProperty(PROP_EVICTION_POLICY_CLASS_NAME);
424        if (value != null) {
425            dataSource.setEvictionPolicyClassName(value);
426        }
427
428        value = properties.getProperty(PROP_TEST_WHILE_IDLE);
429        if (value != null) {
430            dataSource.setTestWhileIdle(Boolean.valueOf(value).booleanValue());
431        }
432
433        value = properties.getProperty(PROP_PASSWORD);
434        if (value != null) {
435            dataSource.setPassword(value);
436        }
437
438        value = properties.getProperty(PROP_URL);
439        if (value != null) {
440            dataSource.setUrl(value);
441        }
442
443        value = properties.getProperty(PROP_USER_NAME);
444        if (value != null) {
445            dataSource.setUsername(value);
446        }
447
448        value = properties.getProperty(PROP_VALIDATION_QUERY);
449        if (value != null) {
450            dataSource.setValidationQuery(value);
451        }
452
453        value = properties.getProperty(PROP_VALIDATION_QUERY_TIMEOUT);
454        if (value != null) {
455            dataSource.setValidationQueryTimeout(Integer.parseInt(value));
456        }
457
458        value = properties.getProperty(PROP_ACCESS_TO_UNDERLYING_CONNECTION_ALLOWED);
459        if (value != null) {
460            dataSource.setAccessToUnderlyingConnectionAllowed(Boolean.valueOf(value).booleanValue());
461        }
462
463        value = properties.getProperty(PROP_REMOVE_ABANDONED_ON_BORROW);
464        if (value != null) {
465            dataSource.setRemoveAbandonedOnBorrow(Boolean.valueOf(value).booleanValue());
466        }
467
468        value = properties.getProperty(PROP_REMOVE_ABANDONED_ON_MAINTENANCE);
469        if (value != null) {
470            dataSource.setRemoveAbandonedOnMaintenance(Boolean.valueOf(value).booleanValue());
471        }
472
473        value = properties.getProperty(PROP_REMOVE_ABANDONED_TIMEOUT);
474        if (value != null) {
475            dataSource.setRemoveAbandonedTimeout(Integer.parseInt(value));
476        }
477
478        value = properties.getProperty(PROP_LOG_ABANDONED);
479        if (value != null) {
480            dataSource.setLogAbandoned(Boolean.valueOf(value).booleanValue());
481        }
482
483        value = properties.getProperty(PROP_ABANDONED_USAGE_TRACKING);
484        if (value != null) {
485            dataSource.setAbandonedUsageTracking(Boolean.valueOf(value).booleanValue());
486        }
487
488        value = properties.getProperty(PROP_POOL_PREPARED_STATEMENTS);
489        if (value != null) {
490            dataSource.setPoolPreparedStatements(Boolean.valueOf(value).booleanValue());
491        }
492
493        value = properties.getProperty(PROP_MAX_OPEN_PREPARED_STATEMENTS);
494        if (value != null) {
495            dataSource.setMaxOpenPreparedStatements(Integer.parseInt(value));
496        }
497
498        value = properties.getProperty(PROP_CONNECTION_INIT_SQLS);
499        if (value != null) {
500            dataSource.setConnectionInitSqls(parseList(value, ';'));
501        }
502
503        value = properties.getProperty(PROP_CONNECTION_PROPERTIES);
504        if (value != null) {
505            final Properties p = getProperties(value);
506            final Enumeration<?> e = p.propertyNames();
507            while (e.hasMoreElements()) {
508                final String propertyName = (String) e.nextElement();
509                dataSource.addConnectionProperty(propertyName, p.getProperty(propertyName));
510            }
511        }
512
513        value = properties.getProperty(PROP_MAX_CONN_LIFETIME_MILLIS);
514        if (value != null) {
515            dataSource.setMaxConnLifetimeMillis(Long.parseLong(value));
516        }
517
518        value = properties.getProperty(PROP_LOG_EXPIRED_CONNECTIONS);
519        if (value != null) {
520            dataSource.setLogExpiredConnections(Boolean.valueOf(value).booleanValue());
521        }
522
523        value = properties.getProperty(PROP_JMX_NAME);
524        if (value != null) {
525            dataSource.setJmxName(value);
526        }
527
528        value = properties.getProperty(PROP_ENABLE_AUTO_COMMIT_ON_RETURN);
529        if (value != null) {
530            dataSource.setAutoCommitOnReturn(Boolean.valueOf(value).booleanValue());
531        }
532
533        value = properties.getProperty(PROP_ROLLBACK_ON_RETURN);
534        if (value != null) {
535            dataSource.setRollbackOnReturn(Boolean.valueOf(value).booleanValue());
536        }
537
538        value = properties.getProperty(PROP_DEFAULT_QUERY_TIMEOUT);
539        if (value != null) {
540            dataSource.setDefaultQueryTimeout(Integer.valueOf(value));
541        }
542
543        value = properties.getProperty(PROP_FAST_FAIL_VALIDATION);
544        if (value != null) {
545            dataSource.setFastFailValidation(Boolean.valueOf(value).booleanValue());
546        }
547
548        value = properties.getProperty(PROP_DISCONNECTION_SQL_CODES);
549        if (value != null) {
550            dataSource.setDisconnectionSqlCodes(parseList(value, ','));
551        }
552
553        value = properties.getProperty(PROP_CONNECTION_FACTORY_CLASS_NAME);
554        if (value != null) {
555            dataSource.setConnectionFactoryClassName(value);
556        }
557
558        // DBCP-215
559        // Trick to make sure that initialSize connections are created
560        if (dataSource.getInitialSize() > 0) {
561            dataSource.getLogWriter();
562        }
563
564        // Return the configured DataSource instance
565        return dataSource;
566    }
567
568    /**
569     * <p>
570     * Parse properties from the string. Format of the string must be [propertyName=property;]*
571     * <p>
572     *
573     * @param propText
574     * @return Properties
575     * @throws Exception
576     */
577    private static Properties getProperties(final String propText) throws Exception {
578        final Properties p = new Properties();
579        if (propText != null) {
580            p.load(new ByteArrayInputStream(propText.replace(';', '\n').getBytes(StandardCharsets.ISO_8859_1)));
581        }
582        return p;
583    }
584
585    /**
586     * Parse list of property values from a delimited string
587     *
588     * @param value
589     *            delimited list of values
590     * @param delimiter
591     *            character used to separate values in the list
592     * @return String Collection of values
593     */
594    private static Collection<String> parseList(final String value, final char delimiter) {
595        final StringTokenizer tokenizer = new StringTokenizer(value, Character.toString(delimiter));
596        final Collection<String> tokens = new ArrayList<>(tokenizer.countTokens());
597        while (tokenizer.hasMoreTokens()) {
598            tokens.add(tokenizer.nextToken());
599        }
600        return tokens;
601    }
602}