1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.dbcp2;
18
19 import java.io.ByteArrayInputStream;
20 import java.io.IOException;
21 import java.nio.charset.StandardCharsets;
22 import java.sql.Connection;
23 import java.sql.SQLException;
24 import java.time.Duration;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.Enumeration;
28 import java.util.Hashtable;
29 import java.util.LinkedHashMap;
30 import java.util.List;
31 import java.util.Locale;
32 import java.util.Map;
33 import java.util.Objects;
34 import java.util.Optional;
35 import java.util.Properties;
36 import java.util.StringTokenizer;
37 import java.util.function.Consumer;
38 import java.util.function.Function;
39
40 import javax.naming.Context;
41 import javax.naming.Name;
42 import javax.naming.RefAddr;
43 import javax.naming.Reference;
44 import javax.naming.spi.ObjectFactory;
45
46 import org.apache.commons.logging.Log;
47 import org.apache.commons.logging.LogFactory;
48 import org.apache.commons.pool2.impl.BaseObjectPoolConfig;
49 import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
50
51
52
53
54
55
56
57
58
59
60
61
62 public class BasicDataSourceFactory implements ObjectFactory {
63
64 private static final Log log = LogFactory.getLog(BasicDataSourceFactory.class);
65
66 private static final String PROP_DEFAULT_AUTO_COMMIT = "defaultAutoCommit";
67 private static final String PROP_DEFAULT_READ_ONLY = "defaultReadOnly";
68 private static final String PROP_DEFAULT_TRANSACTION_ISOLATION = "defaultTransactionIsolation";
69 private static final String PROP_DEFAULT_CATALOG = "defaultCatalog";
70 private static final String PROP_DEFAULT_SCHEMA = "defaultSchema";
71 private static final String PROP_CACHE_STATE = "cacheState";
72 private static final String PROP_DRIVER_CLASS_NAME = "driverClassName";
73 private static final String PROP_LIFO = "lifo";
74 private static final String PROP_MAX_TOTAL = "maxTotal";
75 private static final String PROP_MAX_IDLE = "maxIdle";
76 private static final String PROP_MIN_IDLE = "minIdle";
77 private static final String PROP_INITIAL_SIZE = "initialSize";
78 private static final String PROP_MAX_WAIT_MILLIS = "maxWaitMillis";
79 private static final String PROP_TEST_ON_CREATE = "testOnCreate";
80 private static final String PROP_TEST_ON_BORROW = "testOnBorrow";
81 private static final String PROP_TEST_ON_RETURN = "testOnReturn";
82 private static final String PROP_TIME_BETWEEN_EVICTION_RUNS_MILLIS = "timeBetweenEvictionRunsMillis";
83 private static final String PROP_NUM_TESTS_PER_EVICTION_RUN = "numTestsPerEvictionRun";
84 private static final String PROP_MIN_EVICTABLE_IDLE_TIME_MILLIS = "minEvictableIdleTimeMillis";
85 private static final String PROP_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS = "softMinEvictableIdleTimeMillis";
86 private static final String PROP_EVICTION_POLICY_CLASS_NAME = "evictionPolicyClassName";
87 private static final String PROP_TEST_WHILE_IDLE = "testWhileIdle";
88 private static final String PROP_PASSWORD = Constants.KEY_PASSWORD;
89 private static final String PROP_URL = "url";
90 private static final String PROP_USER_NAME = "username";
91 private static final String PROP_VALIDATION_QUERY = "validationQuery";
92 private static final String PROP_VALIDATION_QUERY_TIMEOUT = "validationQueryTimeout";
93 private static final String PROP_JMX_NAME = "jmxName";
94 private static final String PROP_REGISTER_CONNECTION_MBEAN = "registerConnectionMBean";
95 private static final String PROP_CONNECTION_FACTORY_CLASS_NAME = "connectionFactoryClassName";
96
97
98
99
100 private static final String PROP_CONNECTION_INIT_SQLS = "connectionInitSqls";
101 private static final String PROP_ACCESS_TO_UNDERLYING_CONNECTION_ALLOWED = "accessToUnderlyingConnectionAllowed";
102 private static final String PROP_REMOVE_ABANDONED_ON_BORROW = "removeAbandonedOnBorrow";
103 private static final String PROP_REMOVE_ABANDONED_ON_MAINTENANCE = "removeAbandonedOnMaintenance";
104 private static final String PROP_REMOVE_ABANDONED_TIMEOUT = "removeAbandonedTimeout";
105 private static final String PROP_LOG_ABANDONED = "logAbandoned";
106 private static final String PROP_ABANDONED_USAGE_TRACKING = "abandonedUsageTracking";
107 private static final String PROP_POOL_PREPARED_STATEMENTS = "poolPreparedStatements";
108 private static final String PROP_CLEAR_STATEMENT_POOL_ON_RETURN = "clearStatementPoolOnReturn";
109 private static final String PROP_MAX_OPEN_PREPARED_STATEMENTS = "maxOpenPreparedStatements";
110 private static final String PROP_CONNECTION_PROPERTIES = "connectionProperties";
111 private static final String PROP_MAX_CONN_LIFETIME_MILLIS = "maxConnLifetimeMillis";
112 private static final String PROP_LOG_EXPIRED_CONNECTIONS = "logExpiredConnections";
113 private static final String PROP_ROLLBACK_ON_RETURN = "rollbackOnReturn";
114 private static final String PROP_ENABLE_AUTO_COMMIT_ON_RETURN = "enableAutoCommitOnReturn";
115 private static final String PROP_DEFAULT_QUERY_TIMEOUT = "defaultQueryTimeout";
116 private static final String PROP_FAST_FAIL_VALIDATION = "fastFailValidation";
117
118
119
120
121 private static final String PROP_DISCONNECTION_SQL_CODES = "disconnectionSqlCodes";
122
123
124
125
126
127
128
129
130
131
132 private static final String PROP_DISCONNECTION_IGNORE_SQL_CODES = "disconnectionIgnoreSqlCodes";
133
134
135
136
137
138 private static final String NUPROP_MAX_ACTIVE = "maxActive";
139 private static final String NUPROP_REMOVE_ABANDONED = "removeAbandoned";
140 private static final String NUPROP_MAXWAIT = "maxWait";
141
142
143
144
145
146 private static final String SILENT_PROP_FACTORY = "factory";
147 private static final String SILENT_PROP_SCOPE = "scope";
148 private static final String SILENT_PROP_SINGLETON = "singleton";
149 private static final String SILENT_PROP_AUTH = "auth";
150
151 private static final List<String> ALL_PROPERTY_NAMES = Arrays.asList(PROP_DEFAULT_AUTO_COMMIT, PROP_DEFAULT_READ_ONLY,
152 PROP_DEFAULT_TRANSACTION_ISOLATION, PROP_DEFAULT_CATALOG, PROP_DEFAULT_SCHEMA, PROP_CACHE_STATE,
153 PROP_DRIVER_CLASS_NAME, PROP_LIFO, PROP_MAX_TOTAL, PROP_MAX_IDLE, PROP_MIN_IDLE, PROP_INITIAL_SIZE,
154 PROP_MAX_WAIT_MILLIS, PROP_TEST_ON_CREATE, PROP_TEST_ON_BORROW, PROP_TEST_ON_RETURN,
155 PROP_TIME_BETWEEN_EVICTION_RUNS_MILLIS, PROP_NUM_TESTS_PER_EVICTION_RUN, PROP_MIN_EVICTABLE_IDLE_TIME_MILLIS,
156 PROP_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS, PROP_EVICTION_POLICY_CLASS_NAME, PROP_TEST_WHILE_IDLE, PROP_PASSWORD,
157 PROP_URL, PROP_USER_NAME, PROP_VALIDATION_QUERY, PROP_VALIDATION_QUERY_TIMEOUT, PROP_CONNECTION_INIT_SQLS,
158 PROP_ACCESS_TO_UNDERLYING_CONNECTION_ALLOWED, PROP_REMOVE_ABANDONED_ON_BORROW, PROP_REMOVE_ABANDONED_ON_MAINTENANCE,
159 PROP_REMOVE_ABANDONED_TIMEOUT, PROP_LOG_ABANDONED, PROP_ABANDONED_USAGE_TRACKING, PROP_POOL_PREPARED_STATEMENTS,
160 PROP_CLEAR_STATEMENT_POOL_ON_RETURN,
161 PROP_MAX_OPEN_PREPARED_STATEMENTS, PROP_CONNECTION_PROPERTIES, PROP_MAX_CONN_LIFETIME_MILLIS,
162 PROP_LOG_EXPIRED_CONNECTIONS, PROP_ROLLBACK_ON_RETURN, PROP_ENABLE_AUTO_COMMIT_ON_RETURN,
163 PROP_DEFAULT_QUERY_TIMEOUT, PROP_FAST_FAIL_VALIDATION, PROP_DISCONNECTION_SQL_CODES, PROP_DISCONNECTION_IGNORE_SQL_CODES,
164 PROP_JMX_NAME, PROP_REGISTER_CONNECTION_MBEAN, PROP_CONNECTION_FACTORY_CLASS_NAME);
165
166
167
168
169
170 private static final Map<String, String> NUPROP_WARNTEXT = new LinkedHashMap<>();
171
172 static {
173 NUPROP_WARNTEXT.put(NUPROP_MAX_ACTIVE,
174 "Property " + NUPROP_MAX_ACTIVE + " is not used in DBCP2, use " + PROP_MAX_TOTAL + " instead. "
175 + PROP_MAX_TOTAL + " default value is " + GenericObjectPoolConfig.DEFAULT_MAX_TOTAL + ".");
176 NUPROP_WARNTEXT.put(NUPROP_REMOVE_ABANDONED,
177 "Property " + NUPROP_REMOVE_ABANDONED + " is not used in DBCP2, use one or both of "
178 + PROP_REMOVE_ABANDONED_ON_BORROW + " or " + PROP_REMOVE_ABANDONED_ON_MAINTENANCE + " instead. "
179 + "Both have default value set to false.");
180 NUPROP_WARNTEXT.put(NUPROP_MAXWAIT,
181 "Property " + NUPROP_MAXWAIT + " is not used in DBCP2 , use " + PROP_MAX_WAIT_MILLIS + " instead. "
182 + PROP_MAX_WAIT_MILLIS + " default value is " + BaseObjectPoolConfig.DEFAULT_MAX_WAIT
183 + ".");
184 }
185
186
187
188
189
190 private static final List<String> SILENT_PROPERTIES = new ArrayList<>();
191
192 static {
193 SILENT_PROPERTIES.add(SILENT_PROP_FACTORY);
194 SILENT_PROPERTIES.add(SILENT_PROP_SCOPE);
195 SILENT_PROPERTIES.add(SILENT_PROP_SINGLETON);
196 SILENT_PROPERTIES.add(SILENT_PROP_AUTH);
197
198 }
199
200 private static <V> void accept(final Properties properties, final String name, final Function<String, V> parser, final Consumer<V> consumer) {
201 getOptional(properties, name).ifPresent(v -> consumer.accept(parser.apply(v)));
202 }
203
204 private static void acceptBoolean(final Properties properties, final String name, final Consumer<Boolean> consumer) {
205 accept(properties, name, Boolean::parseBoolean, consumer);
206 }
207
208 private static void acceptDurationOfMillis(final Properties properties, final String name, final Consumer<Duration> consumer) {
209 accept(properties, name, s -> Duration.ofMillis(Long.parseLong(s)), consumer);
210 }
211
212 private static void acceptDurationOfSeconds(final Properties properties, final String name, final Consumer<Duration> consumer) {
213 accept(properties, name, s -> Duration.ofSeconds(Long.parseLong(s)), consumer);
214 }
215
216 private static void acceptInt(final Properties properties, final String name, final Consumer<Integer> consumer) {
217 accept(properties, name, Integer::parseInt, consumer);
218 }
219
220 private static void acceptString(final Properties properties, final String name, final Consumer<String> consumer) {
221 accept(properties, name, Function.identity(), consumer);
222 }
223
224
225
226
227
228
229
230
231
232
233 public static BasicDataSource createDataSource(final Properties properties) throws SQLException {
234 final BasicDataSource dataSource = new BasicDataSource();
235 acceptBoolean(properties, PROP_DEFAULT_AUTO_COMMIT, dataSource::setDefaultAutoCommit);
236 acceptBoolean(properties, PROP_DEFAULT_READ_ONLY, dataSource::setDefaultReadOnly);
237
238 getOptional(properties, PROP_DEFAULT_TRANSACTION_ISOLATION).ifPresent(value -> {
239 value = value.toUpperCase(Locale.ROOT);
240 int level = PoolableConnectionFactory.UNKNOWN_TRANSACTION_ISOLATION;
241 switch (value) {
242 case "NONE":
243 level = Connection.TRANSACTION_NONE;
244 break;
245 case "READ_COMMITTED":
246 level = Connection.TRANSACTION_READ_COMMITTED;
247 break;
248 case "READ_UNCOMMITTED":
249 level = Connection.TRANSACTION_READ_UNCOMMITTED;
250 break;
251 case "REPEATABLE_READ":
252 level = Connection.TRANSACTION_REPEATABLE_READ;
253 break;
254 case "SERIALIZABLE":
255 level = Connection.TRANSACTION_SERIALIZABLE;
256 break;
257 default:
258 try {
259 level = Integer.parseInt(value);
260 } catch (final NumberFormatException e) {
261 System.err.println("Could not parse defaultTransactionIsolation: " + value);
262 System.err.println("WARNING: defaultTransactionIsolation not set");
263 System.err.println("using default value of database driver");
264 level = PoolableConnectionFactory.UNKNOWN_TRANSACTION_ISOLATION;
265 }
266 break;
267 }
268 dataSource.setDefaultTransactionIsolation(level);
269 });
270
271 acceptString(properties, PROP_DEFAULT_SCHEMA, dataSource::setDefaultSchema);
272 acceptString(properties, PROP_DEFAULT_CATALOG, dataSource::setDefaultCatalog);
273 acceptBoolean(properties, PROP_CACHE_STATE, dataSource::setCacheState);
274 acceptString(properties, PROP_DRIVER_CLASS_NAME, dataSource::setDriverClassName);
275 acceptBoolean(properties, PROP_LIFO, dataSource::setLifo);
276 acceptInt(properties, PROP_MAX_TOTAL, dataSource::setMaxTotal);
277 acceptInt(properties, PROP_MAX_IDLE, dataSource::setMaxIdle);
278 acceptInt(properties, PROP_MIN_IDLE, dataSource::setMinIdle);
279 acceptInt(properties, PROP_INITIAL_SIZE, dataSource::setInitialSize);
280 acceptDurationOfMillis(properties, PROP_MAX_WAIT_MILLIS, dataSource::setMaxWait);
281 acceptBoolean(properties, PROP_TEST_ON_CREATE, dataSource::setTestOnCreate);
282 acceptBoolean(properties, PROP_TEST_ON_BORROW, dataSource::setTestOnBorrow);
283 acceptBoolean(properties, PROP_TEST_ON_RETURN, dataSource::setTestOnReturn);
284 acceptDurationOfMillis(properties, PROP_TIME_BETWEEN_EVICTION_RUNS_MILLIS, dataSource::setDurationBetweenEvictionRuns);
285 acceptInt(properties, PROP_NUM_TESTS_PER_EVICTION_RUN, dataSource::setNumTestsPerEvictionRun);
286 acceptDurationOfMillis(properties, PROP_MIN_EVICTABLE_IDLE_TIME_MILLIS, dataSource::setMinEvictableIdle);
287 acceptDurationOfMillis(properties, PROP_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS, dataSource::setSoftMinEvictableIdle);
288 acceptString(properties, PROP_EVICTION_POLICY_CLASS_NAME, dataSource::setEvictionPolicyClassName);
289 acceptBoolean(properties, PROP_TEST_WHILE_IDLE, dataSource::setTestWhileIdle);
290 acceptString(properties, PROP_PASSWORD, dataSource::setPassword);
291 acceptString(properties, PROP_URL, dataSource::setUrl);
292 acceptString(properties, PROP_USER_NAME, dataSource::setUsername);
293 acceptString(properties, PROP_VALIDATION_QUERY, dataSource::setValidationQuery);
294 acceptDurationOfSeconds(properties, PROP_VALIDATION_QUERY_TIMEOUT, dataSource::setValidationQueryTimeout);
295 acceptBoolean(properties, PROP_ACCESS_TO_UNDERLYING_CONNECTION_ALLOWED, dataSource::setAccessToUnderlyingConnectionAllowed);
296 acceptBoolean(properties, PROP_REMOVE_ABANDONED_ON_BORROW, dataSource::setRemoveAbandonedOnBorrow);
297 acceptBoolean(properties, PROP_REMOVE_ABANDONED_ON_MAINTENANCE, dataSource::setRemoveAbandonedOnMaintenance);
298 acceptDurationOfSeconds(properties, PROP_REMOVE_ABANDONED_TIMEOUT, dataSource::setRemoveAbandonedTimeout);
299 acceptBoolean(properties, PROP_LOG_ABANDONED, dataSource::setLogAbandoned);
300 acceptBoolean(properties, PROP_ABANDONED_USAGE_TRACKING, dataSource::setAbandonedUsageTracking);
301 acceptBoolean(properties, PROP_POOL_PREPARED_STATEMENTS, dataSource::setPoolPreparedStatements);
302 acceptBoolean(properties, PROP_CLEAR_STATEMENT_POOL_ON_RETURN, dataSource::setClearStatementPoolOnReturn);
303 acceptInt(properties, PROP_MAX_OPEN_PREPARED_STATEMENTS, dataSource::setMaxOpenPreparedStatements);
304 getOptional(properties, PROP_CONNECTION_INIT_SQLS).ifPresent(v -> dataSource.setConnectionInitSqls(parseList(v, ';')));
305
306 final String value = properties.getProperty(PROP_CONNECTION_PROPERTIES);
307 if (value != null) {
308 final Properties connectionProperties = getProperties(value);
309 for (final Object key : connectionProperties.keySet()) {
310 final String propertyName = Objects.toString(key);
311 dataSource.addConnectionProperty(propertyName, connectionProperties.getProperty(propertyName));
312 }
313 }
314
315 acceptDurationOfMillis(properties, PROP_MAX_CONN_LIFETIME_MILLIS, dataSource::setMaxConn);
316 acceptBoolean(properties, PROP_LOG_EXPIRED_CONNECTIONS, dataSource::setLogExpiredConnections);
317 acceptString(properties, PROP_JMX_NAME, dataSource::setJmxName);
318 acceptBoolean(properties, PROP_REGISTER_CONNECTION_MBEAN, dataSource::setRegisterConnectionMBean);
319 acceptBoolean(properties, PROP_ENABLE_AUTO_COMMIT_ON_RETURN, dataSource::setAutoCommitOnReturn);
320 acceptBoolean(properties, PROP_ROLLBACK_ON_RETURN, dataSource::setRollbackOnReturn);
321 acceptDurationOfSeconds(properties, PROP_DEFAULT_QUERY_TIMEOUT, dataSource::setDefaultQueryTimeout);
322 acceptBoolean(properties, PROP_FAST_FAIL_VALIDATION, dataSource::setFastFailValidation);
323 getOptional(properties, PROP_DISCONNECTION_SQL_CODES).ifPresent(v -> dataSource.setDisconnectionSqlCodes(parseList(v, ',')));
324 getOptional(properties, PROP_DISCONNECTION_IGNORE_SQL_CODES).ifPresent(v -> dataSource.setDisconnectionIgnoreSqlCodes(parseList(v, ',')));
325 acceptString(properties, PROP_CONNECTION_FACTORY_CLASS_NAME, dataSource::setConnectionFactoryClassName);
326
327
328
329 if (dataSource.getInitialSize() > 0) {
330 dataSource.getLogWriter();
331 }
332
333
334 return dataSource;
335 }
336
337 private static Optional<String> getOptional(final Properties properties, final String name) {
338 return Optional.ofNullable(properties.getProperty(name));
339 }
340
341
342
343
344
345
346
347
348 private static Properties getProperties(final String propText) throws SQLException {
349 final Properties p = new Properties();
350 if (propText != null) {
351 try {
352 p.load(new ByteArrayInputStream(propText.replace(';', '\n').getBytes(StandardCharsets.ISO_8859_1)));
353 } catch (final IOException e) {
354 throw new SQLException(propText, e);
355 }
356 }
357 return p;
358 }
359
360
361
362
363
364
365
366
367
368
369 private static List<String> parseList(final String value, final char delimiter) {
370 final StringTokenizer tokenizer = new StringTokenizer(value, Character.toString(delimiter));
371 final List<String> tokens = new ArrayList<>(tokenizer.countTokens());
372 while (tokenizer.hasMoreTokens()) {
373 tokens.add(tokenizer.nextToken());
374 }
375 return tokens;
376 }
377
378
379
380
381 public BasicDataSourceFactory() {
382
383 }
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403 @Override
404 public Object getObjectInstance(final Object obj, final Name name, final Context nameCtx,
405 final Hashtable<?, ?> environment) throws SQLException {
406
407
408
409 if (obj == null || !(obj instanceof Reference)) {
410 return null;
411 }
412 final Reference ref = (Reference) obj;
413 if (!"javax.sql.DataSource".equals(ref.getClassName())) {
414 return null;
415 }
416
417
418 final List<String> warnMessages = new ArrayList<>();
419 final List<String> infoMessages = new ArrayList<>();
420 validatePropertyNames(ref, name, warnMessages, infoMessages);
421 warnMessages.forEach(log::warn);
422 infoMessages.forEach(log::info);
423
424 final Properties properties = new Properties();
425 ALL_PROPERTY_NAMES.forEach(propertyName -> {
426 final RefAddr ra = ref.get(propertyName);
427 if (ra != null) {
428 properties.setProperty(propertyName, Objects.toString(ra.getContent(), null));
429 }
430 });
431
432 return createDataSource(properties);
433 }
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448 private void validatePropertyNames(final Reference ref, final Name name, final List<String> warnMessages,
449 final List<String> infoMessages) {
450 final String nameString = name != null ? "Name = " + name.toString() + " " : "";
451 NUPROP_WARNTEXT.forEach((propertyName, value) -> {
452 final RefAddr ra = ref.get(propertyName);
453 if (ra != null && !ALL_PROPERTY_NAMES.contains(ra.getType())) {
454 final StringBuilder stringBuilder = new StringBuilder(nameString);
455 final String propertyValue = Objects.toString(ra.getContent(), null);
456 stringBuilder.append(value).append(" You have set value of \"").append(propertyValue).append("\" for \"").append(propertyName)
457 .append("\" property, which is being ignored.");
458 warnMessages.add(stringBuilder.toString());
459 }
460 });
461
462 final Enumeration<RefAddr> allRefAddrs = ref.getAll();
463 while (allRefAddrs.hasMoreElements()) {
464 final RefAddr ra = allRefAddrs.nextElement();
465 final String propertyName = ra.getType();
466
467
468 if (!(ALL_PROPERTY_NAMES.contains(propertyName) || NUPROP_WARNTEXT.containsKey(propertyName) || SILENT_PROPERTIES.contains(propertyName))) {
469 final String propertyValue = Objects.toString(ra.getContent(), null);
470 final StringBuilder stringBuilder = new StringBuilder(nameString);
471 stringBuilder.append("Ignoring unknown property: ").append("value of \"").append(propertyValue).append("\" for \"").append(propertyName)
472 .append("\" property");
473 infoMessages.add(stringBuilder.toString());
474 }
475 }
476 }
477 }