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: 892307 $ $Date: 2013-12-31 23:27:28 +0000 (Tue, 31 Dec 2013) $
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 }