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}