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