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    
018    package org.apache.commons.dbcp;
019    
020    import java.io.ByteArrayInputStream;
021    import java.sql.Connection;
022    import java.util.Enumeration;
023    import java.util.Hashtable;
024    import java.util.Properties;
025    import java.util.StringTokenizer;
026    import java.util.Collections;
027    
028    import javax.naming.Context;
029    import javax.naming.Name;
030    import javax.naming.RefAddr;
031    import javax.naming.Reference;
032    import javax.naming.spi.ObjectFactory;
033    import javax.sql.DataSource;
034    
035    /**
036     * <p>JNDI object factory that creates an instance of
037     * <code>BasicDataSource</code> that has been configured based on the
038     * <code>RefAddr</code> values of the specified <code>Reference</code>,
039     * which must match the names and data types of the
040     * <code>BasicDataSource</code> bean properties.</p>
041     *
042     * @author Craig R. McClanahan
043     * @author Dirk Verbeeck
044     * @version $Revision: 828639 $ $Date: 2009-10-22 06:27:43 -0400 (Thu, 22 Oct 2009) $
045     */
046    public class BasicDataSourceFactory implements ObjectFactory {
047    
048        private final static String PROP_DEFAULTAUTOCOMMIT = "defaultAutoCommit";
049        private final static String PROP_DEFAULTREADONLY = "defaultReadOnly";
050        private final static String PROP_DEFAULTTRANSACTIONISOLATION = "defaultTransactionIsolation";
051        private final static String PROP_DEFAULTCATALOG = "defaultCatalog";
052        private final static String PROP_DRIVERCLASSNAME = "driverClassName";
053        private final static String PROP_MAXACTIVE = "maxActive";
054        private final static String PROP_MAXIDLE = "maxIdle";
055        private final static String PROP_MINIDLE = "minIdle";
056        private final static String PROP_INITIALSIZE = "initialSize";
057        private final static String PROP_MAXWAIT = "maxWait";
058        private final static String PROP_TESTONBORROW = "testOnBorrow";
059        private final static String PROP_TESTONRETURN = "testOnReturn";
060        private final static String PROP_TIMEBETWEENEVICTIONRUNSMILLIS = "timeBetweenEvictionRunsMillis";
061        private final static String PROP_NUMTESTSPEREVICTIONRUN = "numTestsPerEvictionRun";
062        private final static String PROP_MINEVICTABLEIDLETIMEMILLIS = "minEvictableIdleTimeMillis";
063        private final static String PROP_TESTWHILEIDLE = "testWhileIdle";
064        private final static String PROP_PASSWORD = "password";
065        private final static String PROP_URL = "url";
066        private final static String PROP_USERNAME = "username";
067        private final static String PROP_VALIDATIONQUERY = "validationQuery";
068        private final static String PROP_VALIDATIONQUERY_TIMEOUT = "validationQueryTimeout";
069        /**
070         * The property name for initConnectionSqls.
071         * The associated value String must be of the form [query;]*
072         * @since 1.3
073         */
074        private final static String PROP_INITCONNECTIONSQLS = "initConnectionSqls";
075        private final static String PROP_ACCESSTOUNDERLYINGCONNECTIONALLOWED = "accessToUnderlyingConnectionAllowed";
076        private final static String PROP_REMOVEABANDONED = "removeAbandoned";
077        private final static String PROP_REMOVEABANDONEDTIMEOUT = "removeAbandonedTimeout";
078        private final static String PROP_LOGABANDONED = "logAbandoned";
079        private final static String PROP_POOLPREPAREDSTATEMENTS = "poolPreparedStatements";
080        private final static String PROP_MAXOPENPREPAREDSTATEMENTS = "maxOpenPreparedStatements";
081        private final static String PROP_CONNECTIONPROPERTIES = "connectionProperties";
082    
083        private final static String[] ALL_PROPERTIES = {
084            PROP_DEFAULTAUTOCOMMIT,
085            PROP_DEFAULTREADONLY,
086            PROP_DEFAULTTRANSACTIONISOLATION,
087            PROP_DEFAULTCATALOG,
088            PROP_DRIVERCLASSNAME,
089            PROP_MAXACTIVE,
090            PROP_MAXIDLE,
091            PROP_MINIDLE,
092            PROP_INITIALSIZE,
093            PROP_MAXWAIT,
094            PROP_TESTONBORROW,
095            PROP_TESTONRETURN,
096            PROP_TIMEBETWEENEVICTIONRUNSMILLIS,
097            PROP_NUMTESTSPEREVICTIONRUN,
098            PROP_MINEVICTABLEIDLETIMEMILLIS,
099            PROP_TESTWHILEIDLE,
100            PROP_PASSWORD,
101            PROP_URL,
102            PROP_USERNAME,
103            PROP_VALIDATIONQUERY,
104            PROP_VALIDATIONQUERY_TIMEOUT,
105            PROP_INITCONNECTIONSQLS,
106            PROP_ACCESSTOUNDERLYINGCONNECTIONALLOWED,
107            PROP_REMOVEABANDONED,
108            PROP_REMOVEABANDONEDTIMEOUT,
109            PROP_LOGABANDONED,
110            PROP_POOLPREPAREDSTATEMENTS,
111            PROP_MAXOPENPREPAREDSTATEMENTS,
112            PROP_CONNECTIONPROPERTIES
113        };
114    
115        // -------------------------------------------------- ObjectFactory Methods
116    
117        /**
118         * <p>Create and return a new <code>BasicDataSource</code> instance.  If no
119         * instance can be created, return <code>null</code> instead.</p>
120         *
121         * @param obj The possibly null object containing location or
122         *  reference information that can be used in creating an object
123         * @param name The name of this object relative to <code>nameCtx</code>
124         * @param nameCtx The context relative to which the <code>name</code>
125         *  parameter is specified, or <code>null</code> if <code>name</code>
126         *  is relative to the default initial context
127         * @param environment The possibly null environment that is used in
128         *  creating this object
129         *
130         * @exception Exception if an exception occurs creating the instance
131         */
132        public Object getObjectInstance(Object obj, Name name, Context nameCtx,
133                                        Hashtable environment)
134            throws Exception {
135    
136            // We only know how to deal with <code>javax.naming.Reference</code>s
137            // that specify a class name of "javax.sql.DataSource"
138            if ((obj == null) || !(obj instanceof Reference)) {
139                return null;
140            }
141            Reference ref = (Reference) obj;
142            if (!"javax.sql.DataSource".equals(ref.getClassName())) {
143                return null;
144            }
145    
146            Properties properties = new Properties();
147            for (int i = 0 ; i < ALL_PROPERTIES.length ; i++) {
148                String propertyName = ALL_PROPERTIES[i];
149                RefAddr ra = ref.get(propertyName);
150                if (ra != null) {
151                    String propertyValue = ra.getContent().toString();
152                    properties.setProperty(propertyName, propertyValue);
153                }
154            }
155    
156            return createDataSource(properties);
157        }
158    
159        /**
160         * Creates and configures a {@link BasicDataSource} instance based on the
161         * given properties.
162         * 
163         * @param properties the datasource configuration properties
164         * @throws Exception if an error occurs creating the data source
165         */
166        public static DataSource createDataSource(Properties properties) throws Exception {
167            BasicDataSource dataSource = new BasicDataSource();
168            String value = null;
169    
170            value = properties.getProperty(PROP_DEFAULTAUTOCOMMIT);
171            if (value != null) {
172                dataSource.setDefaultAutoCommit(Boolean.valueOf(value).booleanValue());
173            }
174    
175            value = properties.getProperty(PROP_DEFAULTREADONLY);
176            if (value != null) {
177                dataSource.setDefaultReadOnly(Boolean.valueOf(value).booleanValue());
178            }
179    
180            value = properties.getProperty(PROP_DEFAULTTRANSACTIONISOLATION);
181            if (value != null) {
182                int level = PoolableConnectionFactory.UNKNOWN_TRANSACTIONISOLATION;
183                if ("NONE".equalsIgnoreCase(value)) {
184                    level = Connection.TRANSACTION_NONE;
185                }
186                else if ("READ_COMMITTED".equalsIgnoreCase(value)) {
187                    level = Connection.TRANSACTION_READ_COMMITTED;
188                }
189                else if ("READ_UNCOMMITTED".equalsIgnoreCase(value)) {
190                    level = Connection.TRANSACTION_READ_UNCOMMITTED;
191                }
192                else if ("REPEATABLE_READ".equalsIgnoreCase(value)) {
193                    level = Connection.TRANSACTION_REPEATABLE_READ;
194                }
195                else if ("SERIALIZABLE".equalsIgnoreCase(value)) {
196                    level = Connection.TRANSACTION_SERIALIZABLE;
197                }
198                else {
199                    try {
200                        level = Integer.parseInt(value);
201                    } catch (NumberFormatException e) {
202                        System.err.println("Could not parse defaultTransactionIsolation: " + value);
203                        System.err.println("WARNING: defaultTransactionIsolation not set");
204                        System.err.println("using default value of database driver");
205                        level = PoolableConnectionFactory.UNKNOWN_TRANSACTIONISOLATION;
206                    }
207                }
208                dataSource.setDefaultTransactionIsolation(level);
209            }
210    
211            value = properties.getProperty(PROP_DEFAULTCATALOG);
212            if (value != null) {
213                dataSource.setDefaultCatalog(value);
214            }
215    
216            value = properties.getProperty(PROP_DRIVERCLASSNAME);
217            if (value != null) {
218                dataSource.setDriverClassName(value);
219            }
220    
221            value = properties.getProperty(PROP_MAXACTIVE);
222            if (value != null) {
223                dataSource.setMaxActive(Integer.parseInt(value));
224            }
225    
226            value = properties.getProperty(PROP_MAXIDLE);
227            if (value != null) {
228                dataSource.setMaxIdle(Integer.parseInt(value));
229            }
230    
231            value = properties.getProperty(PROP_MINIDLE);
232            if (value != null) {
233                dataSource.setMinIdle(Integer.parseInt(value));
234            }
235    
236            value = properties.getProperty(PROP_INITIALSIZE);
237            if (value != null) {
238                dataSource.setInitialSize(Integer.parseInt(value));
239            }
240    
241            value = properties.getProperty(PROP_MAXWAIT);
242            if (value != null) {
243                dataSource.setMaxWait(Long.parseLong(value));
244            }
245    
246            value = properties.getProperty(PROP_TESTONBORROW);
247            if (value != null) {
248                dataSource.setTestOnBorrow(Boolean.valueOf(value).booleanValue());
249            }
250    
251            value = properties.getProperty(PROP_TESTONRETURN);
252            if (value != null) {
253                dataSource.setTestOnReturn(Boolean.valueOf(value).booleanValue());
254            }
255    
256            value = properties.getProperty(PROP_TIMEBETWEENEVICTIONRUNSMILLIS);
257            if (value != null) {
258                dataSource.setTimeBetweenEvictionRunsMillis(Long.parseLong(value));
259            }
260    
261            value = properties.getProperty(PROP_NUMTESTSPEREVICTIONRUN);
262            if (value != null) {
263                dataSource.setNumTestsPerEvictionRun(Integer.parseInt(value));
264            }
265    
266            value = properties.getProperty(PROP_MINEVICTABLEIDLETIMEMILLIS);
267            if (value != null) {
268                dataSource.setMinEvictableIdleTimeMillis(Long.parseLong(value));
269            }
270    
271            value = properties.getProperty(PROP_TESTWHILEIDLE);
272            if (value != null) {
273                dataSource.setTestWhileIdle(Boolean.valueOf(value).booleanValue());
274            }
275    
276            value = properties.getProperty(PROP_PASSWORD);
277            if (value != null) {
278                dataSource.setPassword(value);
279            }
280    
281            value = properties.getProperty(PROP_URL);
282            if (value != null) {
283                dataSource.setUrl(value);
284            }
285    
286            value = properties.getProperty(PROP_USERNAME);
287            if (value != null) {
288                dataSource.setUsername(value);
289            }
290    
291            value = properties.getProperty(PROP_VALIDATIONQUERY);
292            if (value != null) {
293                dataSource.setValidationQuery(value);
294            }
295    
296            value = properties.getProperty(PROP_VALIDATIONQUERY_TIMEOUT);
297            if (value != null) {
298                dataSource.setValidationQueryTimeout(Integer.parseInt(value));
299            }
300            
301            value = properties.getProperty(PROP_ACCESSTOUNDERLYINGCONNECTIONALLOWED);
302            if (value != null) {
303                dataSource.setAccessToUnderlyingConnectionAllowed(Boolean.valueOf(value).booleanValue());
304            }
305    
306            value = properties.getProperty(PROP_REMOVEABANDONED);
307            if (value != null) {
308                dataSource.setRemoveAbandoned(Boolean.valueOf(value).booleanValue());
309            }
310    
311            value = properties.getProperty(PROP_REMOVEABANDONEDTIMEOUT);
312            if (value != null) {     
313                dataSource.setRemoveAbandonedTimeout(Integer.parseInt(value));
314            }
315    
316            value = properties.getProperty(PROP_LOGABANDONED);
317            if (value != null) {
318                dataSource.setLogAbandoned(Boolean.valueOf(value).booleanValue());
319            }
320    
321            value = properties.getProperty(PROP_POOLPREPAREDSTATEMENTS);
322            if (value != null) {
323                dataSource.setPoolPreparedStatements(Boolean.valueOf(value).booleanValue());
324            }
325    
326            value = properties.getProperty(PROP_MAXOPENPREPAREDSTATEMENTS);
327            if (value != null) {
328                dataSource.setMaxOpenPreparedStatements(Integer.parseInt(value));
329            }
330    
331            value = properties.getProperty(PROP_INITCONNECTIONSQLS);
332            if (value != null) {
333                StringTokenizer tokenizer = new StringTokenizer(value, ";");
334                dataSource.setConnectionInitSqls(Collections.list(tokenizer));
335            }
336    
337            value = properties.getProperty(PROP_CONNECTIONPROPERTIES);
338            if (value != null) {
339              Properties p = getProperties(value);
340              Enumeration e = p.propertyNames();
341              while (e.hasMoreElements()) {
342                String propertyName = (String) e.nextElement();
343                dataSource.addConnectionProperty(propertyName, p.getProperty(propertyName));
344              }
345            }
346    
347            // DBCP-215
348            // Trick to make sure that initialSize connections are created
349            if (dataSource.getInitialSize() > 0) {
350                dataSource.getLogWriter();
351            }
352    
353            // Return the configured DataSource instance
354            return dataSource;
355        }
356    
357        /**
358         * <p>Parse properties from the string. Format of the string must be [propertyName=property;]*<p>
359         * @param propText
360         * @return Properties
361         * @throws Exception
362         */
363        static private Properties getProperties(String propText) throws Exception {
364          Properties p = new Properties();
365          if (propText != null) {
366            p.load(new ByteArrayInputStream(propText.replace(';', '\n').getBytes()));
367          }
368          return p;
369        }
370    }