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.Collection;
025import java.util.Enumeration;
026import java.util.Hashtable;
027import java.util.Properties;
028import java.util.StringTokenizer;
029
030import javax.naming.Context;
031import javax.naming.Name;
032import javax.naming.RefAddr;
033import javax.naming.Reference;
034import javax.naming.spi.ObjectFactory;
035
036/**
037 * <p>JNDI object factory that creates an instance of
038 * <code>BasicDataSource</code> that has been configured based on the
039 * <code>RefAddr</code> values of the specified <code>Reference</code>, which
040 * must match the names and data types of the <code>BasicDataSource</code> bean
041 * properties with the following exceptions:</p>
042 * <ul>
043 * <li><code>connectionInitSqls</code> must be passed to this factory as a
044 *     single String using semi-colon to delimt the statements whereas
045 *     <code>BasicDataSource</code> requires a collection of Strings.</li>
046 * </ul>
047 *
048 * @author Craig R. McClanahan
049 * @author Dirk Verbeeck
050 * @version $Revision: 1572242 $ $Date: 2014-02-26 20:34:39 +0000 (Wed, 26 Feb 2014) $
051 * @since 2.0
052 */
053public class BasicDataSourceFactory implements ObjectFactory {
054
055    private final static String PROP_DEFAULTAUTOCOMMIT = "defaultAutoCommit";
056    private final static String PROP_DEFAULTREADONLY = "defaultReadOnly";
057    private final static String PROP_DEFAULTTRANSACTIONISOLATION = "defaultTransactionIsolation";
058    private final static String PROP_DEFAULTCATALOG = "defaultCatalog";
059    private final static String PROP_CACHESTATE ="cacheState";
060    private final static String PROP_DRIVERCLASSNAME = "driverClassName";
061    private final static String PROP_LIFO = "lifo";
062    private final static String PROP_MAXTOTAL = "maxTotal";
063    private final static String PROP_MAXIDLE = "maxIdle";
064    private final static String PROP_MINIDLE = "minIdle";
065    private final static String PROP_INITIALSIZE = "initialSize";
066    private final static String PROP_MAXWAITMILLIS = "maxWaitMillis";
067    private final static String PROP_TESTONCREATE = "testOnCreate";
068    private final static String PROP_TESTONBORROW = "testOnBorrow";
069    private final static String PROP_TESTONRETURN = "testOnReturn";
070    private final static String PROP_TIMEBETWEENEVICTIONRUNSMILLIS = "timeBetweenEvictionRunsMillis";
071    private final static String PROP_NUMTESTSPEREVICTIONRUN = "numTestsPerEvictionRun";
072    private final static String PROP_MINEVICTABLEIDLETIMEMILLIS = "minEvictableIdleTimeMillis";
073    private final static String PROP_SOFTMINEVICTABLEIDLETIMEMILLIS = "softMinEvictableIdleTimeMillis";
074    private final static String PROP_EVICTIONPOLICYCLASSNAME = "evictionPolicyClassName";
075    private final static String PROP_TESTWHILEIDLE = "testWhileIdle";
076    private final static String PROP_PASSWORD = "password";
077    private final static String PROP_URL = "url";
078    private final static String PROP_USERNAME = "username";
079    private final static String PROP_VALIDATIONQUERY = "validationQuery";
080    private final static String PROP_VALIDATIONQUERY_TIMEOUT = "validationQueryTimeout";
081    private final static String PROP_JMX_NAME = "jmxName";
082
083    /**
084     * The property name for connectionInitSqls.
085     * The associated value String must be of the form [query;]*
086     */
087    private final static String PROP_CONNECTIONINITSQLS = "connectionInitSqls";
088    private final static String PROP_ACCESSTOUNDERLYINGCONNECTIONALLOWED = "accessToUnderlyingConnectionAllowed";
089    private final static String PROP_REMOVEABANDONEDONBORROW = "removeAbandonedOnBorrow";
090    private final static String PROP_REMOVEABANDONEDONMAINTENANCE = "removeAbandonedOnMaintenance";
091    private final static String PROP_REMOVEABANDONEDTIMEOUT = "removeAbandonedTimeout";
092    private final static String PROP_LOGABANDONED = "logAbandoned";
093    private final static String PROP_POOLPREPAREDSTATEMENTS = "poolPreparedStatements";
094    private final static String PROP_MAXOPENPREPAREDSTATEMENTS = "maxOpenPreparedStatements";
095    private final static String PROP_CONNECTIONPROPERTIES = "connectionProperties";
096    private final static String PROP_MAXCONNLIFETIMEMILLIS = "maxConnLifetimeMillis";
097    private final static String PROP_ROLLBACK_ON_RETURN = "rollbackOnReturn";
098    private final static String PROP_ENABLE_AUTOCOMMIT_ON_RETURN = "enableAutoCommitOnReturn";
099    private final static String PROP_DEFAULT_QUERYTIMEOUT = "defaultQueryTimeout";
100
101    private final static String[] ALL_PROPERTIES = {
102        PROP_DEFAULTAUTOCOMMIT,
103        PROP_DEFAULTREADONLY,
104        PROP_DEFAULTTRANSACTIONISOLATION,
105        PROP_DEFAULTCATALOG,
106        PROP_CACHESTATE,
107        PROP_DRIVERCLASSNAME,
108        PROP_LIFO,
109        PROP_MAXTOTAL,
110        PROP_MAXIDLE,
111        PROP_MINIDLE,
112        PROP_INITIALSIZE,
113        PROP_MAXWAITMILLIS,
114        PROP_TESTONCREATE,
115        PROP_TESTONBORROW,
116        PROP_TESTONRETURN,
117        PROP_TIMEBETWEENEVICTIONRUNSMILLIS,
118        PROP_NUMTESTSPEREVICTIONRUN,
119        PROP_MINEVICTABLEIDLETIMEMILLIS,
120        PROP_SOFTMINEVICTABLEIDLETIMEMILLIS,
121        PROP_EVICTIONPOLICYCLASSNAME,
122        PROP_TESTWHILEIDLE,
123        PROP_PASSWORD,
124        PROP_URL,
125        PROP_USERNAME,
126        PROP_VALIDATIONQUERY,
127        PROP_VALIDATIONQUERY_TIMEOUT,
128        PROP_CONNECTIONINITSQLS,
129        PROP_ACCESSTOUNDERLYINGCONNECTIONALLOWED,
130        PROP_REMOVEABANDONEDONBORROW,
131        PROP_REMOVEABANDONEDONMAINTENANCE,
132        PROP_REMOVEABANDONEDTIMEOUT,
133        PROP_LOGABANDONED,
134        PROP_POOLPREPAREDSTATEMENTS,
135        PROP_MAXOPENPREPAREDSTATEMENTS,
136        PROP_CONNECTIONPROPERTIES,
137        PROP_MAXCONNLIFETIMEMILLIS,
138        PROP_ROLLBACK_ON_RETURN,
139        PROP_ENABLE_AUTOCOMMIT_ON_RETURN,
140        PROP_DEFAULT_QUERYTIMEOUT
141    };
142
143    // -------------------------------------------------- ObjectFactory Methods
144
145    /**
146     * <p>Create and return a new <code>BasicDataSource</code> instance.  If no
147     * instance can be created, return <code>null</code> instead.</p>
148     *
149     * @param obj The possibly null object containing location or
150     *  reference information that can be used in creating an object
151     * @param name The name of this object relative to <code>nameCtx</code>
152     * @param nameCtx The context relative to which the <code>name</code>
153     *  parameter is specified, or <code>null</code> if <code>name</code>
154     *  is relative to the default initial context
155     * @param environment The possibly null environment that is used in
156     *  creating this object
157     *
158     * @exception Exception if an exception occurs creating the instance
159     */
160    @Override
161    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?,?> environment)
162        throws Exception {
163
164        // We only know how to deal with <code>javax.naming.Reference</code>s
165        // that specify a class name of "javax.sql.DataSource"
166        if (obj == null || !(obj instanceof Reference)) {
167            return null;
168        }
169        Reference ref = (Reference) obj;
170        if (!"javax.sql.DataSource".equals(ref.getClassName())) {
171            return null;
172        }
173
174        Properties properties = new Properties();
175        for (String propertyName : ALL_PROPERTIES) {
176            RefAddr ra = ref.get(propertyName);
177            if (ra != null) {
178                String propertyValue = ra.getContent().toString();
179                properties.setProperty(propertyName, propertyValue);
180            }
181        }
182
183        return createDataSource(properties);
184    }
185
186    /**
187     * Creates and configures a {@link BasicDataSource} instance based on the
188     * given properties.
189     *
190     * @param properties the datasource configuration properties
191     * @throws Exception if an error occurs creating the data source
192     */
193    public static BasicDataSource createDataSource(Properties properties) throws Exception {
194        BasicDataSource dataSource = new BasicDataSource();
195        String value = null;
196
197        value = properties.getProperty(PROP_DEFAULTAUTOCOMMIT);
198        if (value != null) {
199            dataSource.setDefaultAutoCommit(Boolean.valueOf(value));
200        }
201
202        value = properties.getProperty(PROP_DEFAULTREADONLY);
203        if (value != null) {
204            dataSource.setDefaultReadOnly(Boolean.valueOf(value));
205        }
206
207        value = properties.getProperty(PROP_DEFAULTTRANSACTIONISOLATION);
208        if (value != null) {
209            int level = PoolableConnectionFactory.UNKNOWN_TRANSACTIONISOLATION;
210            if ("NONE".equalsIgnoreCase(value)) {
211                level = Connection.TRANSACTION_NONE;
212            }
213            else if ("READ_COMMITTED".equalsIgnoreCase(value)) {
214                level = Connection.TRANSACTION_READ_COMMITTED;
215            }
216            else if ("READ_UNCOMMITTED".equalsIgnoreCase(value)) {
217                level = Connection.TRANSACTION_READ_UNCOMMITTED;
218            }
219            else if ("REPEATABLE_READ".equalsIgnoreCase(value)) {
220                level = Connection.TRANSACTION_REPEATABLE_READ;
221            }
222            else if ("SERIALIZABLE".equalsIgnoreCase(value)) {
223                level = Connection.TRANSACTION_SERIALIZABLE;
224            }
225            else {
226                try {
227                    level = Integer.parseInt(value);
228                } catch (NumberFormatException e) {
229                    System.err.println("Could not parse defaultTransactionIsolation: " + value);
230                    System.err.println("WARNING: defaultTransactionIsolation not set");
231                    System.err.println("using default value of database driver");
232                    level = PoolableConnectionFactory.UNKNOWN_TRANSACTIONISOLATION;
233                }
234            }
235            dataSource.setDefaultTransactionIsolation(level);
236        }
237
238        value = properties.getProperty(PROP_DEFAULTCATALOG);
239        if (value != null) {
240            dataSource.setDefaultCatalog(value);
241        }
242
243        value = properties.getProperty(PROP_CACHESTATE);
244        if (value != null) {
245            dataSource.setCacheState(Boolean.valueOf(value).booleanValue());
246        }
247
248        value = properties.getProperty(PROP_DRIVERCLASSNAME);
249        if (value != null) {
250            dataSource.setDriverClassName(value);
251        }
252
253        value = properties.getProperty(PROP_LIFO);
254        if (value != null) {
255            dataSource.setLifo(Boolean.valueOf(value).booleanValue());
256        }
257
258        value = properties.getProperty(PROP_MAXTOTAL);
259        if (value != null) {
260            dataSource.setMaxTotal(Integer.parseInt(value));
261        }
262
263        value = properties.getProperty(PROP_MAXIDLE);
264        if (value != null) {
265            dataSource.setMaxIdle(Integer.parseInt(value));
266        }
267
268        value = properties.getProperty(PROP_MINIDLE);
269        if (value != null) {
270            dataSource.setMinIdle(Integer.parseInt(value));
271        }
272
273        value = properties.getProperty(PROP_INITIALSIZE);
274        if (value != null) {
275            dataSource.setInitialSize(Integer.parseInt(value));
276        }
277
278        value = properties.getProperty(PROP_MAXWAITMILLIS);
279        if (value != null) {
280            dataSource.setMaxWaitMillis(Long.parseLong(value));
281        }
282
283        value = properties.getProperty(PROP_TESTONCREATE);
284        if (value != null) {
285            dataSource.setTestOnCreate(Boolean.valueOf(value).booleanValue());
286        }
287
288        value = properties.getProperty(PROP_TESTONBORROW);
289        if (value != null) {
290            dataSource.setTestOnBorrow(Boolean.valueOf(value).booleanValue());
291        }
292
293        value = properties.getProperty(PROP_TESTONRETURN);
294        if (value != null) {
295            dataSource.setTestOnReturn(Boolean.valueOf(value).booleanValue());
296        }
297
298        value = properties.getProperty(PROP_TIMEBETWEENEVICTIONRUNSMILLIS);
299        if (value != null) {
300            dataSource.setTimeBetweenEvictionRunsMillis(Long.parseLong(value));
301        }
302
303        value = properties.getProperty(PROP_NUMTESTSPEREVICTIONRUN);
304        if (value != null) {
305            dataSource.setNumTestsPerEvictionRun(Integer.parseInt(value));
306        }
307
308        value = properties.getProperty(PROP_MINEVICTABLEIDLETIMEMILLIS);
309        if (value != null) {
310            dataSource.setMinEvictableIdleTimeMillis(Long.parseLong(value));
311        }
312
313        value = properties.getProperty(PROP_SOFTMINEVICTABLEIDLETIMEMILLIS);
314        if (value != null) {
315            dataSource.setSoftMinEvictableIdleTimeMillis(Long.parseLong(value));
316        }
317
318        value = properties.getProperty(PROP_EVICTIONPOLICYCLASSNAME);
319        if (value != null) {
320            dataSource.setEvictionPolicyClassName(value);
321        }
322
323        value = properties.getProperty(PROP_TESTWHILEIDLE);
324        if (value != null) {
325            dataSource.setTestWhileIdle(Boolean.valueOf(value).booleanValue());
326        }
327
328        value = properties.getProperty(PROP_PASSWORD);
329        if (value != null) {
330            dataSource.setPassword(value);
331        }
332
333        value = properties.getProperty(PROP_URL);
334        if (value != null) {
335            dataSource.setUrl(value);
336        }
337
338        value = properties.getProperty(PROP_USERNAME);
339        if (value != null) {
340            dataSource.setUsername(value);
341        }
342
343        value = properties.getProperty(PROP_VALIDATIONQUERY);
344        if (value != null) {
345            dataSource.setValidationQuery(value);
346        }
347
348        value = properties.getProperty(PROP_VALIDATIONQUERY_TIMEOUT);
349        if (value != null) {
350            dataSource.setValidationQueryTimeout(Integer.parseInt(value));
351        }
352
353        value = properties.getProperty(PROP_ACCESSTOUNDERLYINGCONNECTIONALLOWED);
354        if (value != null) {
355            dataSource.setAccessToUnderlyingConnectionAllowed(Boolean.valueOf(value).booleanValue());
356        }
357
358        value = properties.getProperty(PROP_REMOVEABANDONEDONBORROW);
359        if (value != null) {
360            dataSource.setRemoveAbandonedOnBorrow(Boolean.valueOf(value).booleanValue());
361        }
362
363        value = properties.getProperty(PROP_REMOVEABANDONEDONMAINTENANCE);
364        if (value != null) {
365            dataSource.setRemoveAbandonedOnMaintenance(Boolean.valueOf(value).booleanValue());
366        }
367
368        value = properties.getProperty(PROP_REMOVEABANDONEDTIMEOUT);
369        if (value != null) {
370            dataSource.setRemoveAbandonedTimeout(Integer.parseInt(value));
371        }
372
373        value = properties.getProperty(PROP_LOGABANDONED);
374        if (value != null) {
375            dataSource.setLogAbandoned(Boolean.valueOf(value).booleanValue());
376        }
377
378        value = properties.getProperty(PROP_POOLPREPAREDSTATEMENTS);
379        if (value != null) {
380            dataSource.setPoolPreparedStatements(Boolean.valueOf(value).booleanValue());
381        }
382
383        value = properties.getProperty(PROP_MAXOPENPREPAREDSTATEMENTS);
384        if (value != null) {
385            dataSource.setMaxOpenPreparedStatements(Integer.parseInt(value));
386        }
387
388        value = properties.getProperty(PROP_CONNECTIONINITSQLS);
389        if (value != null) {
390            StringTokenizer tokenizer = new StringTokenizer(value, ";");
391            // Have to jump through these hoops as StringTokenizer implements
392            // Enumeration<Object> rather than Enumeration<String>
393            Collection<String> tokens =
394                    new ArrayList<>(tokenizer.countTokens());
395            while (tokenizer.hasMoreTokens()) {
396                tokens.add(tokenizer.nextToken());
397            }
398            dataSource.setConnectionInitSqls(tokens);
399        }
400
401        value = properties.getProperty(PROP_CONNECTIONPROPERTIES);
402        if (value != null) {
403          Properties p = getProperties(value);
404          Enumeration<?> e = p.propertyNames();
405          while (e.hasMoreElements()) {
406            String propertyName = (String) e.nextElement();
407            dataSource.addConnectionProperty(propertyName, p.getProperty(propertyName));
408          }
409        }
410
411        value = properties.getProperty(PROP_MAXCONNLIFETIMEMILLIS);
412        if (value != null) {
413            dataSource.setMaxConnLifetimeMillis(Long.parseLong(value));
414        }
415
416        value = properties.getProperty(PROP_JMX_NAME);
417        if (value != null) {
418            dataSource.setJmxName(value);
419        }
420
421        value = properties.getProperty(PROP_ENABLE_AUTOCOMMIT_ON_RETURN);
422        if (value != null) {
423            dataSource.setEnableAutoCommitOnReturn(Boolean.valueOf(value).booleanValue());
424        }
425
426        value = properties.getProperty(PROP_ROLLBACK_ON_RETURN);
427        if (value != null) {
428            dataSource.setRollbackOnReturn(Boolean.valueOf(value).booleanValue());
429        }
430
431        value = properties.getProperty(PROP_DEFAULT_QUERYTIMEOUT);
432        if (value != null) {
433            dataSource.setDefaultQueryTimeout(Integer.valueOf(value));
434        }
435
436
437        // DBCP-215
438        // Trick to make sure that initialSize connections are created
439        if (dataSource.getInitialSize() > 0) {
440            dataSource.getLogWriter();
441        }
442
443        // Return the configured DataSource instance
444        return dataSource;
445    }
446
447    /**
448     * <p>Parse properties from the string. Format of the string must be [propertyName=property;]*<p>
449     * @param propText
450     * @return Properties
451     * @throws Exception
452     */
453    static private Properties getProperties(String propText) throws Exception {
454      Properties p = new Properties();
455      if (propText != null) {
456        p.load(new ByteArrayInputStream(
457                propText.replace(';', '\n').getBytes(StandardCharsets.ISO_8859_1)));
458      }
459      return p;
460    }
461}