    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}