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