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