    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>JNDI object factory that creates an instance of
    047 * <code>BasicDataSource</code> that has been configured based on the
    048 * <code>RefAddr</code> values of the specified <code>Reference</code>, which
    049 * must match the names and data types of the <code>BasicDataSource</code> bean
    050 * properties with the following exceptions:</p>
    051 * <ul>
    052 * <li><code>connectionInitSqls</code> must be passed to this factory as a
    053 *     single String using semi-colon to delimit the statements whereas
    054 *     <code>BasicDataSource</code> requires a collection of Strings.</li>
    055 * </ul>
    056 *
    057 * @author Craig R. McClanahan
    058 * @author Dirk Verbeeck
    059 * @since 2.0
    060 */
    061public class BasicDataSourceFactory implements ObjectFactory {
    062
    063    private static final Log log = LogFactory.getLog(BasicDataSourceFactory.class);
    064
    065    private static final String PROP_DEFAULTAUTOCOMMIT = "defaultAutoCommit";
    066    private static final String PROP_DEFAULTREADONLY = "defaultReadOnly";
    067    private static final String PROP_DEFAULTTRANSACTIONISOLATION = "defaultTransactionIsolation";
    068    private static final String PROP_DEFAULTCATALOG = "defaultCatalog";
    069    private static final String PROP_CACHESTATE ="cacheState";
    070    private static final String PROP_DRIVERCLASSNAME = "driverClassName";
    071    private static final String PROP_LIFO = "lifo";
    072    private static final String PROP_MAXTOTAL = "maxTotal";
    073    private static final String PROP_MAXIDLE = "maxIdle";
    074    private static final String PROP_MINIDLE = "minIdle";
    075    private static final String PROP_INITIALSIZE = "initialSize";
    076    private static final String PROP_MAXWAITMILLIS = "maxWaitMillis";
    077    private static final String PROP_TESTONCREATE = "testOnCreate";
    078    private static final String PROP_TESTONBORROW = "testOnBorrow";
    079    private static final String PROP_TESTONRETURN = "testOnReturn";
    080    private static final String PROP_TIMEBETWEENEVICTIONRUNSMILLIS = "timeBetweenEvictionRunsMillis";
    081    private static final String PROP_NUMTESTSPEREVICTIONRUN = "numTestsPerEvictionRun";
    082    private static final String PROP_MINEVICTABLEIDLETIMEMILLIS = "minEvictableIdleTimeMillis";
    083    private static final String PROP_SOFTMINEVICTABLEIDLETIMEMILLIS = "softMinEvictableIdleTimeMillis";
    084    private static final String PROP_EVICTIONPOLICYCLASSNAME = "evictionPolicyClassName";
    085    private static final String PROP_TESTWHILEIDLE = "testWhileIdle";
    086    private static final String PROP_PASSWORD = "password";
    087    private static final String PROP_URL = "url";
    088    private static final String PROP_USERNAME = "username";
    089    private static final String PROP_VALIDATIONQUERY = "validationQuery";
    090    private static final String PROP_VALIDATIONQUERY_TIMEOUT = "validationQueryTimeout";
    091    private static final String PROP_JMX_NAME = "jmxName";
    092
    093    /**
    094     * The property name for connectionInitSqls.
    095     * The associated value String must be of the form [query;]*
    096     */
    097    private static final String PROP_CONNECTIONINITSQLS = "connectionInitSqls";
    098    private static final String PROP_ACCESSTOUNDERLYINGCONNECTIONALLOWED = "accessToUnderlyingConnectionAllowed";
    099    private static final String PROP_REMOVEABANDONEDONBORROW = "removeAbandonedOnBorrow";
    100    private static final String PROP_REMOVEABANDONEDONMAINTENANCE = "removeAbandonedOnMaintenance";
    101    private static final String PROP_REMOVEABANDONEDTIMEOUT = "removeAbandonedTimeout";
    102    private static final String PROP_LOGABANDONED = "logAbandoned";
    103    private static final String PROP_ABANDONEDUSAGETRACKING = "abandonedUsageTracking";
    104    private static final String PROP_POOLPREPAREDSTATEMENTS = "poolPreparedStatements";
    105    private static final String PROP_MAXOPENPREPAREDSTATEMENTS = "maxOpenPreparedStatements";
    106    private static final String PROP_CONNECTIONPROPERTIES = "connectionProperties";
    107    private static final String PROP_MAXCONNLIFETIMEMILLIS = "maxConnLifetimeMillis";
    108    private static final String PROP_LOGEXPIREDCONNECTIONS = "logExpiredConnections";
    109    private static final String PROP_ROLLBACK_ON_RETURN = "rollbackOnReturn";
    110    private static final String PROP_ENABLE_AUTOCOMMIT_ON_RETURN = "enableAutoCommitOnReturn";
    111    private static final String PROP_DEFAULT_QUERYTIMEOUT = "defaultQueryTimeout";
    112    private static final String PROP_FASTFAIL_VALIDATION = "fastFailValidation";
    113
    114    /**
    115     * Value string must be of the form [STATE_CODE,]*
    116     */
    117    private static final String PROP_DISCONNECTION_SQL_CODES = "disconnectionSqlCodes";
    118
    119    /*
    120     * Block with obsolete properties from DBCP 1.x.
    121     * Warn users that these are ignored and they should use the 2.x properties.
    122     */
    123    private static final String NUPROP_MAXACTIVE = "maxActive";
    124    private static final String NUPROP_REMOVEABANDONED = "removeAbandoned";
    125    private static final String NUPROP_MAXWAIT = "maxWait";
    126
    127    /*
    128     * Block with properties expected in a DataSource
    129     * This props will not be listed as ignored - we know that they may appear in Resource,
    130     * and not listing them as ignored.
    131     */
    132    private static final String SILENTPROP_FACTORY = "factory";
    133    private static final String SILENTPROP_SCOPE = "scope";
    134    private static final String SILENTPROP_SINGLETON = "singleton";
    135    private static final String SILENTPROP_AUTH = "auth";
    136
    137    private static final String[] ALL_PROPERTIES = {
    138        PROP_DEFAULTAUTOCOMMIT,
    139        PROP_DEFAULTREADONLY,
    140        PROP_DEFAULTTRANSACTIONISOLATION,
    141        PROP_DEFAULTCATALOG,
    142        PROP_CACHESTATE,
    143        PROP_DRIVERCLASSNAME,
    144        PROP_LIFO,
    145        PROP_MAXTOTAL,
    146        PROP_MAXIDLE,
    147        PROP_MINIDLE,
    148        PROP_INITIALSIZE,
    149        PROP_MAXWAITMILLIS,
    150        PROP_TESTONCREATE,
    151        PROP_TESTONBORROW,
    152        PROP_TESTONRETURN,
    153        PROP_TIMEBETWEENEVICTIONRUNSMILLIS,
    154        PROP_NUMTESTSPEREVICTIONRUN,
    155        PROP_MINEVICTABLEIDLETIMEMILLIS,
    156        PROP_SOFTMINEVICTABLEIDLETIMEMILLIS,
    157        PROP_EVICTIONPOLICYCLASSNAME,
    158        PROP_TESTWHILEIDLE,
    159        PROP_PASSWORD,
    160        PROP_URL,
    161        PROP_USERNAME,
    162        PROP_VALIDATIONQUERY,
    163        PROP_VALIDATIONQUERY_TIMEOUT,
    164        PROP_CONNECTIONINITSQLS,
    165        PROP_ACCESSTOUNDERLYINGCONNECTIONALLOWED,
    166        PROP_REMOVEABANDONEDONBORROW,
    167        PROP_REMOVEABANDONEDONMAINTENANCE,
    168        PROP_REMOVEABANDONEDTIMEOUT,
    169        PROP_LOGABANDONED,
    170        PROP_ABANDONEDUSAGETRACKING,
    171        PROP_POOLPREPAREDSTATEMENTS,
    172        PROP_MAXOPENPREPAREDSTATEMENTS,
    173        PROP_CONNECTIONPROPERTIES,
    174        PROP_MAXCONNLIFETIMEMILLIS,
    175        PROP_LOGEXPIREDCONNECTIONS,
    176        PROP_ROLLBACK_ON_RETURN,
    177        PROP_ENABLE_AUTOCOMMIT_ON_RETURN,
    178        PROP_DEFAULT_QUERYTIMEOUT,
    179        PROP_FASTFAIL_VALIDATION,
    180        PROP_DISCONNECTION_SQL_CODES,
    181        PROP_JMX_NAME
    182    };
    183
    184    /**
    185     * Obsolete properties from DBCP 1.x. with warning strings suggesting
    186     * new properties. LinkedHashMap will guarantee that properties will be listed
    187     * to output in order of insertion into map.
    188     */
    189    private static final Map<String, String> NUPROP_WARNTEXT = new LinkedHashMap<>();
    190
    191    static {
    192        NUPROP_WARNTEXT.put(
    193                NUPROP_MAXACTIVE,
    194                "Property " + NUPROP_MAXACTIVE + " is not used in DBCP2, use " + PROP_MAXTOTAL + " instead. "
    195                        + PROP_MAXTOTAL + " default value is " + GenericObjectPoolConfig.DEFAULT_MAX_TOTAL+".");
    196        NUPROP_WARNTEXT.put(
    197                NUPROP_REMOVEABANDONED,
    198                "Property " + NUPROP_REMOVEABANDONED + " is not used in DBCP2,"
    199                        + " use one or both of "
    200                        + PROP_REMOVEABANDONEDONBORROW + " or " + PROP_REMOVEABANDONEDONMAINTENANCE + " instead. "
    201                        + "Both have default value set to false.");
    202        NUPROP_WARNTEXT.put(
    203                NUPROP_MAXWAIT,
    204                "Property " + NUPROP_MAXWAIT + " is not used in DBCP2"
    205                        + " , use " + PROP_MAXWAITMILLIS + " instead. "
    206                        + PROP_MAXWAITMILLIS + " default value is " + BaseObjectPoolConfig.DEFAULT_MAX_WAIT_MILLIS+".");
    207    }
    208
    209    /**
    210     * Silent Properties.
    211     * These properties will not be listed as ignored - we know that they may appear in JDBC Resource references,
    212     * and we will not list them as ignored.
    213     */
    214    private static final List<String> SILENT_PROPERTIES = new ArrayList<>();
    215
    216    static {
    217        SILENT_PROPERTIES.add(SILENTPROP_FACTORY);
    218        SILENT_PROPERTIES.add(SILENTPROP_SCOPE);
    219        SILENT_PROPERTIES.add(SILENTPROP_SINGLETON);
    220        SILENT_PROPERTIES.add(SILENTPROP_AUTH);
    221
    222    }
    223
    224    // -------------------------------------------------- ObjectFactory Methods
    225
    226    /**
    227     * <p>Create and return a new <code>BasicDataSource</code> instance.  If no
    228     * instance can be created, return <code>null</code> instead.</p>
    229     *
    230     * @param obj The possibly null object containing location or
    231     *  reference information that can be used in creating an object
    232     * @param name The name of this object relative to <code>nameCtx</code>
    233     * @param nameCtx The context relative to which the <code>name</code>
    234     *  parameter is specified, or <code>null</code> if <code>name</code>
    235     *  is relative to the default initial context
    236     * @param environment The possibly null environment that is used in
    237     *  creating this object
    238     *
    239     * @throws Exception if an exception occurs creating the instance
    240     */
    241    @Override
    242    public Object getObjectInstance(final Object obj, final Name name, final Context nameCtx, final Hashtable<?,?> environment)
    243        throws Exception {
    244
    245        // We only know how to deal with <code>javax.naming.Reference</code>s
    246        // that specify a class name of "javax.sql.DataSource"
    247        if (obj == null || !(obj instanceof Reference)) {
    248            return null;
    249        }
    250        final Reference ref = (Reference) obj;
    251        if (!"javax.sql.DataSource".equals(ref.getClassName())) {
    252            return null;
    253        }
    254
    255        // Check property names and log warnings about obsolete and / or unknown properties
    256        final List<String> warnings = new ArrayList<>();
    257        final List<String> infoMessages = new ArrayList<>();
    258        validatePropertyNames(ref, name, warnings, infoMessages);
    259        for (final String warning : warnings) {
    260            log.warn(warning);
    261        }
    262        for (final String infoMessage : infoMessages) {
    263            log.info(infoMessage);
    264        }
    265
    266        final Properties properties = new Properties();
    267        for (final String propertyName : ALL_PROPERTIES) {
    268            final RefAddr ra = ref.get(propertyName);
    269            if (ra != null) {
    270                final String propertyValue = ra.getContent().toString();
    271                properties.setProperty(propertyName, propertyValue);
    272            }
    273        }
    274
    275        return createDataSource(properties);
    276    }
    277
    278    /**
    279     * Collects warnings and info messages.  Warnings are generated when an obsolete
    280     * property is set.  Unknown properties generate info messages.
    281     *
    282     * @param ref Reference to check properties of
    283     * @param name Name provided to getObject
    284     * @param warnings container for warning messages
    285     * @param infoMessages container for info messages
    286     */
    287    private void validatePropertyNames(final Reference ref, final Name name, final List<String> warnings,
    288                                      final List<String> infoMessages) {
    289        final List<String> allPropsAsList = Arrays.asList(ALL_PROPERTIES);
    290        final String nameString = name != null ? "Name = " + name.toString() + " " : "";
    291        if (NUPROP_WARNTEXT!=null && !NUPROP_WARNTEXT.keySet().isEmpty()) {
    292            for (final String propertyName : NUPROP_WARNTEXT.keySet()) {
    293                final RefAddr ra = ref.get(propertyName);
    294                if (ra != null && !allPropsAsList.contains(ra.getType())) {
    295                    final StringBuilder stringBuilder = new StringBuilder(nameString);
    296                    final String propertyValue = ra.getContent().toString();
    297                    stringBuilder.append(NUPROP_WARNTEXT.get(propertyName))
    298                            .append(" You have set value of \"")
    299                            .append(propertyValue)
    300                            .append("\" for \"")
    301                            .append(propertyName)
    302                            .append("\" property, which is being ignored.");
    303                    warnings.add(stringBuilder.toString());
    304                }
    305            }
    306        }
    307
    308        final Enumeration<RefAddr> allRefAddrs = ref.getAll();
    309        while (allRefAddrs.hasMoreElements()) {
    310            final RefAddr ra = allRefAddrs.nextElement();
    311            final String propertyName = ra.getType();
    312            // If property name is not in the properties list, we haven't warned on it
    313            // and it is not in the "silent" list, tell user we are ignoring it.
    314            if (!(allPropsAsList.contains(propertyName)
    315                    || NUPROP_WARNTEXT.keySet().contains(propertyName)
    316                    || SILENT_PROPERTIES.contains(propertyName))) {
    317                final String propertyValue = ra.getContent().toString();
    318                final StringBuilder stringBuilder = new StringBuilder(nameString);
    319                stringBuilder.append("Ignoring unknown property: ")
    320                        .append("value of \"")
    321                        .append(propertyValue)
    322                        .append("\" for \"")
    323                        .append(propertyName)
    324                        .append("\" property");
    325                infoMessages.add(stringBuilder.toString());
    326            }
    327        }
    328    }
    329
    330    /**
    331     * Creates and configures a {@link BasicDataSource} instance based on the
    332     * given properties.
    333     *
    334     * @param properties the datasource configuration properties
    335     * @throws Exception if an error occurs creating the data source
    336     */
    337    public static BasicDataSource createDataSource(final Properties properties) throws Exception {
    338        final BasicDataSource dataSource = new BasicDataSource();
    339        String value = null;
    340
    341        value = properties.getProperty(PROP_DEFAULTAUTOCOMMIT);
    342        if (value != null) {
    343            dataSource.setDefaultAutoCommit(Boolean.valueOf(value));
    344        }
    345
    346        value = properties.getProperty(PROP_DEFAULTREADONLY);
    347        if (value != null) {
    348            dataSource.setDefaultReadOnly(Boolean.valueOf(value));
    349        }
    350
    351        value = properties.getProperty(PROP_DEFAULTTRANSACTIONISOLATION);
    352        if (value != null) {
    353            int level = PoolableConnectionFactory.UNKNOWN_TRANSACTIONISOLATION;
    354            if ("NONE".equalsIgnoreCase(value)) {
    355                level = Connection.TRANSACTION_NONE;
    356            }
    357            else if ("READ_COMMITTED".equalsIgnoreCase(value)) {
    358                level = Connection.TRANSACTION_READ_COMMITTED;
    359            }
    360            else if ("READ_UNCOMMITTED".equalsIgnoreCase(value)) {
    361                level = Connection.TRANSACTION_READ_UNCOMMITTED;
    362            }
    363            else if ("REPEATABLE_READ".equalsIgnoreCase(value)) {
    364                level = Connection.TRANSACTION_REPEATABLE_READ;
    365            }
    366            else if ("SERIALIZABLE".equalsIgnoreCase(value)) {
    367                level = Connection.TRANSACTION_SERIALIZABLE;
    368            }
    369            else {
    370                try {
    371                    level = Integer.parseInt(value);
    372                } catch (final NumberFormatException e) {
    373                    System.err.println("Could not parse defaultTransactionIsolation: " + value);
    374                    System.err.println("WARNING: defaultTransactionIsolation not set");
    375                    System.err.println("using default value of database driver");
    376                    level = PoolableConnectionFactory.UNKNOWN_TRANSACTIONISOLATION;
    377                }
    378            }
    379            dataSource.setDefaultTransactionIsolation(level);
    380        }
    381
    382        value = properties.getProperty(PROP_DEFAULTCATALOG);
    383        if (value != null) {
    384            dataSource.setDefaultCatalog(value);
    385        }
    386
    387        value = properties.getProperty(PROP_CACHESTATE);
    388        if (value != null) {
    389            dataSource.setCacheState(Boolean.valueOf(value).booleanValue());
    390        }
    391
    392        value = properties.getProperty(PROP_DRIVERCLASSNAME);
    393        if (value != null) {
    394            dataSource.setDriverClassName(value);
    395        }
    396
    397        value = properties.getProperty(PROP_LIFO);
    398        if (value != null) {
    399            dataSource.setLifo(Boolean.valueOf(value).booleanValue());
    400        }
    401
    402        value = properties.getProperty(PROP_MAXTOTAL);
    403        if (value != null) {
    404            dataSource.setMaxTotal(Integer.parseInt(value));
    405        }
    406
    407        value = properties.getProperty(PROP_MAXIDLE);
    408        if (value != null) {
    409            dataSource.setMaxIdle(Integer.parseInt(value));
    410        }
    411
    412        value = properties.getProperty(PROP_MINIDLE);
    413        if (value != null) {
    414            dataSource.setMinIdle(Integer.parseInt(value));
    415        }
    416
    417        value = properties.getProperty(PROP_INITIALSIZE);
    418        if (value != null) {
    419            dataSource.setInitialSize(Integer.parseInt(value));
    420        }
    421
    422        value = properties.getProperty(PROP_MAXWAITMILLIS);
    423        if (value != null) {
    424            dataSource.setMaxWaitMillis(Long.parseLong(value));
    425        }
    426
    427        value = properties.getProperty(PROP_TESTONCREATE);
    428        if (value != null) {
    429            dataSource.setTestOnCreate(Boolean.valueOf(value).booleanValue());
    430        }
    431
    432        value = properties.getProperty(PROP_TESTONBORROW);
    433        if (value != null) {
    434            dataSource.setTestOnBorrow(Boolean.valueOf(value).booleanValue());
    435        }
    436
    437        value = properties.getProperty(PROP_TESTONRETURN);
    438        if (value != null) {
    439            dataSource.setTestOnReturn(Boolean.valueOf(value).booleanValue());
    440        }
    441
    442        value = properties.getProperty(PROP_TIMEBETWEENEVICTIONRUNSMILLIS);
    443        if (value != null) {
    444            dataSource.setTimeBetweenEvictionRunsMillis(Long.parseLong(value));
    445        }
    446
    447        value = properties.getProperty(PROP_NUMTESTSPEREVICTIONRUN);
    448        if (value != null) {
    449            dataSource.setNumTestsPerEvictionRun(Integer.parseInt(value));
    450        }
    451
    452        value = properties.getProperty(PROP_MINEVICTABLEIDLETIMEMILLIS);
    453        if (value != null) {
    454            dataSource.setMinEvictableIdleTimeMillis(Long.parseLong(value));
    455        }
    456
    457        value = properties.getProperty(PROP_SOFTMINEVICTABLEIDLETIMEMILLIS);
    458        if (value != null) {
    459            dataSource.setSoftMinEvictableIdleTimeMillis(Long.parseLong(value));
    460        }
    461
    462        value = properties.getProperty(PROP_EVICTIONPOLICYCLASSNAME);
    463        if (value != null) {
    464            dataSource.setEvictionPolicyClassName(value);
    465        }
    466
    467        value = properties.getProperty(PROP_TESTWHILEIDLE);
    468        if (value != null) {
    469            dataSource.setTestWhileIdle(Boolean.valueOf(value).booleanValue());
    470        }
    471
    472        value = properties.getProperty(PROP_PASSWORD);
    473        if (value != null) {
    474            dataSource.setPassword(value);
    475        }
    476
    477        value = properties.getProperty(PROP_URL);
    478        if (value != null) {
    479            dataSource.setUrl(value);
    480        }
    481
    482        value = properties.getProperty(PROP_USERNAME);
    483        if (value != null) {
    484            dataSource.setUsername(value);
    485        }
    486
    487        value = properties.getProperty(PROP_VALIDATIONQUERY);
    488        if (value != null) {
    489            dataSource.setValidationQuery(value);
    490        }
    491
    492        value = properties.getProperty(PROP_VALIDATIONQUERY_TIMEOUT);
    493        if (value != null) {
    494            dataSource.setValidationQueryTimeout(Integer.parseInt(value));
    495        }
    496
    497        value = properties.getProperty(PROP_ACCESSTOUNDERLYINGCONNECTIONALLOWED);
    498        if (value != null) {
    499            dataSource.setAccessToUnderlyingConnectionAllowed(Boolean.valueOf(value).booleanValue());
    500        }
    501
    502        value = properties.getProperty(PROP_REMOVEABANDONEDONBORROW);
    503        if (value != null) {
    504            dataSource.setRemoveAbandonedOnBorrow(Boolean.valueOf(value).booleanValue());
    505        }
    506
    507        value = properties.getProperty(PROP_REMOVEABANDONEDONMAINTENANCE);
    508        if (value != null) {
    509            dataSource.setRemoveAbandonedOnMaintenance(Boolean.valueOf(value).booleanValue());
    510        }
    511
    512        value = properties.getProperty(PROP_REMOVEABANDONEDTIMEOUT);
    513        if (value != null) {
    514            dataSource.setRemoveAbandonedTimeout(Integer.parseInt(value));
    515        }
    516
    517        value = properties.getProperty(PROP_LOGABANDONED);
    518        if (value != null) {
    519            dataSource.setLogAbandoned(Boolean.valueOf(value).booleanValue());
    520        }
    521
    522        value = properties.getProperty(PROP_ABANDONEDUSAGETRACKING);
    523        if (value != null) {
    524            dataSource.setAbandonedUsageTracking(Boolean.valueOf(value).booleanValue());
    525        }
    526
    527        value = properties.getProperty(PROP_POOLPREPAREDSTATEMENTS);
    528        if (value != null) {
    529            dataSource.setPoolPreparedStatements(Boolean.valueOf(value).booleanValue());
    530        }
    531
    532        value = properties.getProperty(PROP_MAXOPENPREPAREDSTATEMENTS);
    533        if (value != null) {
    534            dataSource.setMaxOpenPreparedStatements(Integer.parseInt(value));
    535        }
    536
    537        value = properties.getProperty(PROP_CONNECTIONINITSQLS);
    538        if (value != null) {
    539            dataSource.setConnectionInitSqls(parseList(value, ';'));
    540        }
    541
    542        value = properties.getProperty(PROP_CONNECTIONPROPERTIES);
    543        if (value != null) {
    544          final Properties p = getProperties(value);
    545          final Enumeration<?> e = p.propertyNames();
    546          while (e.hasMoreElements()) {
    547            final String propertyName = (String) e.nextElement();
    548            dataSource.addConnectionProperty(propertyName, p.getProperty(propertyName));
    549          }
    550        }
    551
    552        value = properties.getProperty(PROP_MAXCONNLIFETIMEMILLIS);
    553        if (value != null) {
    554            dataSource.setMaxConnLifetimeMillis(Long.parseLong(value));
    555        }
    556
    557        value = properties.getProperty(PROP_LOGEXPIREDCONNECTIONS);
    558        if (value != null) {
    559            dataSource.setLogExpiredConnections(Boolean.valueOf(value).booleanValue());
    560        }
    561
    562        value = properties.getProperty(PROP_JMX_NAME);
    563        if (value != null) {
    564            dataSource.setJmxName(value);
    565        }
    566
    567        value = properties.getProperty(PROP_ENABLE_AUTOCOMMIT_ON_RETURN);
    568        if (value != null) {
    569            dataSource.setEnableAutoCommitOnReturn(Boolean.valueOf(value).booleanValue());
    570        }
    571
    572        value = properties.getProperty(PROP_ROLLBACK_ON_RETURN);
    573        if (value != null) {
    574            dataSource.setRollbackOnReturn(Boolean.valueOf(value).booleanValue());
    575        }
    576
    577        value = properties.getProperty(PROP_DEFAULT_QUERYTIMEOUT);
    578        if (value != null) {
    579            dataSource.setDefaultQueryTimeout(Integer.valueOf(value));
    580        }
    581
    582        value = properties.getProperty(PROP_FASTFAIL_VALIDATION);
    583        if (value != null) {
    584            dataSource.setFastFailValidation(Boolean.valueOf(value).booleanValue());
    585        }
    586
    587        value = properties.getProperty(PROP_DISCONNECTION_SQL_CODES);
    588        if (value != null) {
    589            dataSource.setDisconnectionSqlCodes(parseList(value, ','));
    590        }
    591
    592        // DBCP-215
    593        // Trick to make sure that initialSize connections are created
    594        if (dataSource.getInitialSize() > 0) {
    595            dataSource.getLogWriter();
    596        }
    597
    598        // Return the configured DataSource instance
    599        return dataSource;
    600    }
    601
    602    /**
    603     * <p>Parse properties from the string. Format of the string must be [propertyName=property;]*<p>
    604     * @param propText
    605     * @return Properties
    606     * @throws Exception
    607     */
    608    private static Properties getProperties(final String propText) throws Exception {
    609      final Properties p = new Properties();
    610      if (propText != null) {
    611        p.load(new ByteArrayInputStream(
    612                propText.replace(';', '\n').getBytes(StandardCharsets.ISO_8859_1)));
    613      }
    614      return p;
    615    }
    616
    617    /**
    618     * Parse list of property values from a delimited string
    619     * @param value delimited list of values
    620     * @param delimiter character used to separate values in the list
    621     * @return String Collection of values
    622     */
    623    private static Collection<String> parseList(final String value, final char delimiter) {
    624        final StringTokenizer tokenizer = new StringTokenizer(value, Character.toString(delimiter));
    625        final Collection<String> tokens = new ArrayList<>(tokenizer.countTokens());
    626        while (tokenizer.hasMoreTokens()) {
    627            tokens.add(tokenizer.nextToken());
    628        }
    629        return tokens;
    630    }
    631}