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 * https://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 */ 017package org.apache.commons.dbcp2; 018 019import java.io.ByteArrayInputStream; 020import java.io.IOException; 021import java.nio.charset.StandardCharsets; 022import java.sql.Connection; 023import java.sql.SQLException; 024import java.time.Duration; 025import java.util.ArrayList; 026import java.util.Arrays; 027import java.util.Enumeration; 028import java.util.Hashtable; 029import java.util.LinkedHashMap; 030import java.util.List; 031import java.util.Locale; 032import java.util.Map; 033import java.util.Objects; 034import java.util.Optional; 035import java.util.Properties; 036import java.util.StringTokenizer; 037import java.util.function.Consumer; 038import java.util.function.Function; 039 040import javax.naming.Context; 041import javax.naming.Name; 042import javax.naming.RefAddr; 043import javax.naming.Reference; 044import javax.naming.spi.ObjectFactory; 045 046import org.apache.commons.logging.Log; 047import org.apache.commons.logging.LogFactory; 048import org.apache.commons.pool2.impl.BaseObjectPoolConfig; 049import org.apache.commons.pool2.impl.GenericObjectPoolConfig; 050 051/** 052 * JNDI object factory that creates an instance of {@link BasicDataSource} that has been configured based on the 053 * {@link RefAddr} values of the specified {@code Reference}, which must match the names and data types of the 054 * {@link BasicDataSource} bean properties with the following exceptions: 055 * <ul> 056 * <li>{@code connectionInitSqls} must be passed to this factory as a single String using semicolon to delimit the 057 * statements whereas {@link BasicDataSource} requires a collection of Strings.</li> 058 * </ul> 059 * 060 * @since 2.0 061 */ 062public class BasicDataSourceFactory implements ObjectFactory { 063 064 private static final Log log = LogFactory.getLog(BasicDataSourceFactory.class); 065 066 private static final String PROP_DEFAULT_AUTO_COMMIT = "defaultAutoCommit"; 067 private static final String PROP_DEFAULT_READ_ONLY = "defaultReadOnly"; 068 private static final String PROP_DEFAULT_TRANSACTION_ISOLATION = "defaultTransactionIsolation"; 069 private static final String PROP_DEFAULT_CATALOG = "defaultCatalog"; 070 private static final String PROP_DEFAULT_SCHEMA = "defaultSchema"; 071 private static final String PROP_CACHE_STATE = "cacheState"; 072 private static final String PROP_DRIVER_CLASS_NAME = "driverClassName"; 073 private static final String PROP_LIFO = "lifo"; 074 private static final String PROP_MAX_TOTAL = "maxTotal"; 075 private static final String PROP_MAX_IDLE = "maxIdle"; 076 private static final String PROP_MIN_IDLE = "minIdle"; 077 private static final String PROP_INITIAL_SIZE = "initialSize"; 078 private static final String PROP_MAX_WAIT_MILLIS = "maxWaitMillis"; 079 private static final String PROP_TEST_ON_CREATE = "testOnCreate"; 080 private static final String PROP_TEST_ON_BORROW = "testOnBorrow"; 081 private static final String PROP_TEST_ON_RETURN = "testOnReturn"; 082 private static final String PROP_TIME_BETWEEN_EVICTION_RUNS_MILLIS = "timeBetweenEvictionRunsMillis"; 083 private static final String PROP_NUM_TESTS_PER_EVICTION_RUN = "numTestsPerEvictionRun"; 084 private static final String PROP_MIN_EVICTABLE_IDLE_TIME_MILLIS = "minEvictableIdleTimeMillis"; 085 private static final String PROP_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS = "softMinEvictableIdleTimeMillis"; 086 private static final String PROP_EVICTION_POLICY_CLASS_NAME = "evictionPolicyClassName"; 087 private static final String PROP_TEST_WHILE_IDLE = "testWhileIdle"; 088 private static final String PROP_PASSWORD = Constants.KEY_PASSWORD; 089 private static final String PROP_URL = "url"; 090 private static final String PROP_USER_NAME = "username"; 091 private static final String PROP_VALIDATION_QUERY = "validationQuery"; 092 private static final String PROP_VALIDATION_QUERY_TIMEOUT = "validationQueryTimeout"; 093 private static final String PROP_JMX_NAME = "jmxName"; 094 private static final String PROP_REGISTER_CONNECTION_MBEAN = "registerConnectionMBean"; 095 private static final String PROP_CONNECTION_FACTORY_CLASS_NAME = "connectionFactoryClassName"; 096 097 /** 098 * The property name for connectionInitSqls. The associated value String must be of the form [query;]* 099 */ 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 * Value string must be of the form [STATE_CODE,]* 120 */ 121 private static final String PROP_DISCONNECTION_SQL_CODES = "disconnectionSqlCodes"; 122 123 /** 124 * Property key for specifying the SQL State codes that should be ignored during disconnection checks. 125 * <p> 126 * The value for this property must be a comma-separated string of SQL State codes, where each code represents 127 * a state that will be excluded from being treated as a fatal disconnection. The expected format is a series 128 * of SQL State codes separated by commas, with no spaces between them (e.g., "08003,08004"). 129 * </p> 130 * @since 2.13.0 131 */ 132 private static final String PROP_DISCONNECTION_IGNORE_SQL_CODES = "disconnectionIgnoreSqlCodes"; 133 134 /* 135 * Block with obsolete properties from DBCP 1.x. Warn users that these are ignored and they should use the 2.x 136 * properties. 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 * Block with properties expected in a DataSource This props will not be listed as ignored - we know that they may 144 * appear in Resource, and not listing them as ignored. 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 * Obsolete properties from DBCP 1.x. with warning strings suggesting new properties. LinkedHashMap will guarantee 168 * that properties will be listed to output in order of insertion into map. 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 * Silent Properties. These properties will not be listed as ignored - we know that they may appear in JDBC Resource 188 * references, and we will not list them as ignored. 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 * Creates and configures a {@link BasicDataSource} instance based on the given properties. 226 * 227 * @param properties 228 * The data source configuration properties. 229 * @return A new a {@link BasicDataSource} instance based on the given properties. 230 * @throws SQLException 231 * Thrown when an error occurs creating the data source. 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 // DBCP-215 328 // Trick to make sure that initialSize connections are created 329 if (dataSource.getInitialSize() > 0) { 330 dataSource.getLogWriter(); 331 } 332 333 // Return the configured DataSource instance 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 * Parse properties from the string. Format of the string must be [propertyName=property;]* 343 * 344 * @param propText The source text 345 * @return Properties A new Properties instance 346 * @throws SQLException When a paring exception occurs 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 * Parses list of property values from a delimited string 362 * 363 * @param value 364 * delimited list of values 365 * @param delimiter 366 * character used to separate values in the list 367 * @return String Collection of values 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 * Constructs a new instance. 380 */ 381 public BasicDataSourceFactory() { 382 // empty 383 } 384 385 /** 386 * Creates and return a new {@link BasicDataSource} instance. If no instance can be created, return 387 * {@code null} instead. 388 * 389 * @param obj 390 * The possibly null object containing location or reference information that can be used in creating an 391 * object 392 * @param name 393 * The name of this object relative to {@code nameCtx} 394 * @param nameCtx 395 * The context relative to which the {@code name} parameter is specified, or {@code null} if 396 * {@code name} is relative to the default initial context 397 * @param environment 398 * The possibly null environment that is used in creating this object 399 * 400 * @throws SQLException 401 * if an exception occurs creating the instance 402 */ 403 @Override 404 public Object getObjectInstance(final Object obj, final Name name, final Context nameCtx, 405 final Hashtable<?, ?> environment) throws SQLException { 406 407 // We only know how to deal with {@code javax.naming.Reference}s 408 // that specify a class name of "javax.sql.DataSource" 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 // Check property names and log warnings about obsolete and / or unknown properties 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 * Collects warnings and info messages. Warnings are generated when an obsolete property is set. Unknown properties 437 * generate info messages. 438 * 439 * @param ref 440 * Reference to check properties of 441 * @param name 442 * Name provided to getObject 443 * @param warnMessages 444 * container for warning messages 445 * @param infoMessages 446 * container for info messages 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 // If property name is not in the properties list, we haven't warned on it 467 // and it is not in the "silent" list, tell user we are ignoring it. 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}