DataSourceFactory.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.jdbc.pool;
import java.sql.Connection;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Properties;
import java.util.Set;
import javax.management.ObjectName;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.RefAddr;
import javax.naming.Reference;
import javax.naming.spi.ObjectFactory;
import javax.sql.DataSource;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.jdbc.pool.util.MessageFactory;
/**
* <p>
* JNDI object factory that creates an instance of <code>BasicDataSource</code> that has been configured based on the
* <code>RefAddr</code> values of the specified <code>Reference</code>, which must match the names and data types of the
* <code>BasicDataSource</code> bean properties.
* </p>
* <br>
* Properties available for configuration:<br>
* <a href="https://commons.apache.org/dbcp/configuration.html">Commons DBCP properties</a><br>
* <ol>
* <li>initSQL - A query that gets executed once, right after the connection is established.</li>
* <li>testOnConnect - run validationQuery after connection has been established.</li>
* <li>validationInterval - avoid excess validation, only run validation at most at this frequency - time in
* milliseconds.</li>
* <li>jdbcInterceptors - a semicolon separated list of classnames extending {@link JdbcInterceptor} class.</li>
* <li>jmxEnabled - true of false, whether to register the pool with JMX.</li>
* <li>fairQueue - true of false, whether the pool should sacrifice a little bit of performance for true fairness.</li>
* </ol>
*/
public class DataSourceFactory implements ObjectFactory {
/**
* Default constructor.
*/
public DataSourceFactory() {
}
private static final Log log = LogFactory.getLog(DataSourceFactory.class);
/**
* Property name for defaultAutoCommit.
*/
protected static final String PROP_DEFAULTAUTOCOMMIT = "defaultAutoCommit";
/**
* Property name for defaultReadOnly.
*/
protected static final String PROP_DEFAULTREADONLY = "defaultReadOnly";
/**
* Property name for defaultTransactionIsolation.
*/
protected static final String PROP_DEFAULTTRANSACTIONISOLATION = "defaultTransactionIsolation";
/**
* Property name for defaultCatalog.
*/
protected static final String PROP_DEFAULTCATALOG = "defaultCatalog";
/**
* Property name for driverClassName.
*/
protected static final String PROP_DRIVERCLASSNAME = "driverClassName";
/**
* Property name for password.
*/
protected static final String PROP_PASSWORD = "password";
/**
* Property name for url.
*/
protected static final String PROP_URL = "url";
/**
* Property name for username.
*/
protected static final String PROP_USERNAME = "username";
/**
* Property name for maxActive.
*/
protected static final String PROP_MAXACTIVE = "maxActive";
/**
* Property name for maxIdle.
*/
protected static final String PROP_MAXIDLE = "maxIdle";
/**
* Property name for minIdle.
*/
protected static final String PROP_MINIDLE = "minIdle";
/**
* Property name for initialSize.
*/
protected static final String PROP_INITIALSIZE = "initialSize";
/**
* Property name for maxWait.
*/
protected static final String PROP_MAXWAIT = "maxWait";
/**
* Property name for maxAge.
*/
protected static final String PROP_MAXAGE = "maxAge";
/**
* Property name for testOnBorrow.
*/
protected static final String PROP_TESTONBORROW = "testOnBorrow";
/**
* Property name for testOnReturn.
*/
protected static final String PROP_TESTONRETURN = "testOnReturn";
/**
* Property name for testWhileIdle.
*/
protected static final String PROP_TESTWHILEIDLE = "testWhileIdle";
/**
* Property name for testOnConnect.
*/
protected static final String PROP_TESTONCONNECT = "testOnConnect";
/**
* Property name for validationQuery.
*/
protected static final String PROP_VALIDATIONQUERY = "validationQuery";
/**
* Property name for validationQueryTimeout.
*/
protected static final String PROP_VALIDATIONQUERY_TIMEOUT = "validationQueryTimeout";
/**
* Property name for validatorClassName.
*/
protected static final String PROP_VALIDATOR_CLASS_NAME = "validatorClassName";
/**
* Property name for numTestsPerEvictionRun.
*/
protected static final String PROP_NUMTESTSPEREVICTIONRUN = "numTestsPerEvictionRun";
/**
* Property name for timeBetweenEvictionRunsMillis.
*/
protected static final String PROP_TIMEBETWEENEVICTIONRUNSMILLIS = "timeBetweenEvictionRunsMillis";
/**
* Property name for minEvictableIdleTimeMillis.
*/
protected static final String PROP_MINEVICTABLEIDLETIMEMILLIS = "minEvictableIdleTimeMillis";
/**
* Property name for accessToUnderlyingConnectionAllowed.
*/
protected static final String PROP_ACCESSTOUNDERLYINGCONNECTIONALLOWED = "accessToUnderlyingConnectionAllowed";
/**
* Property name for removeAbandoned.
*/
protected static final String PROP_REMOVEABANDONED = "removeAbandoned";
/**
* Property name for removeAbandonedTimeout.
*/
protected static final String PROP_REMOVEABANDONEDTIMEOUT = "removeAbandonedTimeout";
/**
* Property name for logAbandoned.
*/
protected static final String PROP_LOGABANDONED = "logAbandoned";
/**
* Property name for abandonWhenPercentageFull.
*/
protected static final String PROP_ABANDONWHENPERCENTAGEFULL = "abandonWhenPercentageFull";
/**
* Property name for poolPreparedStatements.
*/
protected static final String PROP_POOLPREPAREDSTATEMENTS = "poolPreparedStatements";
/**
* Property name for maxOpenPreparedStatements.
*/
protected static final String PROP_MAXOPENPREPAREDSTATEMENTS = "maxOpenPreparedStatements";
/**
* Property name for connectionProperties.
*/
protected static final String PROP_CONNECTIONPROPERTIES = "connectionProperties";
/**
* Property name for initSQL.
*/
protected static final String PROP_INITSQL = "initSQL";
/**
* Property name for jdbcInterceptors.
*/
protected static final String PROP_INTERCEPTORS = "jdbcInterceptors";
/**
* Property name for validationInterval.
*/
protected static final String PROP_VALIDATIONINTERVAL = "validationInterval";
/**
* Property name for jmxEnabled.
*/
protected static final String PROP_JMX_ENABLED = "jmxEnabled";
/**
* Property name for fairQueue.
*/
protected static final String PROP_FAIR_QUEUE = "fairQueue";
/**
* Property name for useEquals.
*/
protected static final String PROP_USE_EQUALS = "useEquals";
/**
* Property name for useLock.
*/
protected static final String PROP_USE_CON_LOCK = "useLock";
/**
* Property name for dataSource.
*/
protected static final String PROP_DATASOURCE = "dataSource";
/**
* Property name for dataSourceJNDI.
*/
protected static final String PROP_DATASOURCE_JNDI = "dataSourceJNDI";
/**
* Property name for suspectTimeout.
*/
protected static final String PROP_SUSPECT_TIMEOUT = "suspectTimeout";
/**
* Property name for alternateUsernameAllowed.
*/
protected static final String PROP_ALTERNATE_USERNAME_ALLOWED = "alternateUsernameAllowed";
/**
* Property name for commitOnReturn.
*/
protected static final String PROP_COMMITONRETURN = "commitOnReturn";
/**
* Property name for rollbackOnReturn.
*/
protected static final String PROP_ROLLBACKONRETURN = "rollbackOnReturn";
/**
* Property name for useDisposableConnectionFacade. The property is deprecated and has no effect; the disposable
* connection facade is always used.
*/
protected static final String PROP_USEDISPOSABLECONNECTIONFACADE = "useDisposableConnectionFacade";
/**
* Property name for logValidationErrors.
*/
protected static final String PROP_LOGVALIDATIONERRORS = "logValidationErrors";
/**
* Property name for propagateInterruptState.
*/
protected static final String PROP_PROPAGATEINTERRUPTSTATE = "propagateInterruptState";
/**
* Property name for ignoreExceptionOnPreLoad.
*/
protected static final String PROP_IGNOREEXCEPTIONONPRELOAD = "ignoreExceptionOnPreLoad";
/**
* Property name for useStatementFacade. The property is deprecated and has no effect; the statement facade is
* always used.
*/
protected static final String PROP_USESTATEMENTFACADE = "useStatementFacade";
/**
* Value indicating an unknown transaction isolation level.
*/
public static final int UNKNOWN_TRANSACTIONISOLATION = -1;
/**
* Property name for the JMX object name.
*/
public static final String OBJECT_NAME = "object_name";
/**
* Array of all supported property names.
*/
protected static final String[] ALL_PROPERTIES = { PROP_DEFAULTAUTOCOMMIT, PROP_DEFAULTREADONLY,
PROP_DEFAULTTRANSACTIONISOLATION, PROP_DEFAULTCATALOG, PROP_DRIVERCLASSNAME, PROP_MAXACTIVE, PROP_MAXIDLE,
PROP_MINIDLE, PROP_INITIALSIZE, PROP_MAXWAIT, PROP_TESTONBORROW, PROP_TESTONRETURN,
PROP_TIMEBETWEENEVICTIONRUNSMILLIS, PROP_NUMTESTSPEREVICTIONRUN, PROP_MINEVICTABLEIDLETIMEMILLIS,
PROP_TESTWHILEIDLE, PROP_TESTONCONNECT, PROP_PASSWORD, PROP_URL, PROP_USERNAME, PROP_VALIDATIONQUERY,
PROP_VALIDATIONQUERY_TIMEOUT, PROP_VALIDATOR_CLASS_NAME, PROP_VALIDATIONINTERVAL,
PROP_ACCESSTOUNDERLYINGCONNECTIONALLOWED, PROP_REMOVEABANDONED, PROP_REMOVEABANDONEDTIMEOUT,
PROP_LOGABANDONED, PROP_POOLPREPAREDSTATEMENTS, PROP_MAXOPENPREPAREDSTATEMENTS, PROP_CONNECTIONPROPERTIES,
PROP_INITSQL, PROP_INTERCEPTORS, PROP_JMX_ENABLED, PROP_FAIR_QUEUE, PROP_USE_EQUALS, OBJECT_NAME,
PROP_ABANDONWHENPERCENTAGEFULL, PROP_MAXAGE, PROP_USE_CON_LOCK, PROP_DATASOURCE, PROP_DATASOURCE_JNDI,
PROP_SUSPECT_TIMEOUT, PROP_ALTERNATE_USERNAME_ALLOWED, PROP_COMMITONRETURN, PROP_ROLLBACKONRETURN,
PROP_USEDISPOSABLECONNECTIONFACADE, PROP_LOGVALIDATIONERRORS, PROP_PROPAGATEINTERRUPTSTATE,
PROP_IGNOREEXCEPTIONONPRELOAD, PROP_USESTATEMENTFACADE };
/**
* Names of the reference addresses that the JNDI container itself places
* into a resource reference alongside the pool properties. They configure
* the naming layer, not the pool, and are therefore not reported as
* unrecognized:
* <ul>
* <li>{@code factory}: the object factory to delegate to; the container
* reads it as a reference address through
* {@code ref.get(org.apache.naming.factory.Constants.FACTORY)} in
* {@code org.apache.naming.factory.FactoryBase}.</li>
* <li>{@code singleton}, {@code scope}, {@code auth} and
* {@code description}: the resource metadata that
* {@code org.apache.naming.ResourceRef} adds to every resource
* reference.</li>
* <li>{@code forceString}: the removed legacy attribute that the
* container's {@code org.apache.naming.factory.BeanFactory} still
* recognizes and reports on its own. With this factory it is inert,
* but it is reserved to stay silent exactly as BeanFactory does, so a
* resource switched between the two factories does not start to warn
* about it.</li>
* </ul>
*/
private static final Set<String> RESERVED_JNDI_ADDRESS_NAMES = Collections.unmodifiableSet(
new HashSet<>(Arrays.asList("factory", "singleton", "scope", "auth", "description", "forceString")));
/*
* The recognized property names as a set, for the sweep over the
* reference's addresses that reports everything the lookup loop above
* never read.
*/
private static final Set<String> ALL_PROPERTY_NAMES = Collections.unmodifiableSet(
new HashSet<>(Arrays.asList(ALL_PROPERTIES)));
// -------------------------------------------------- ObjectFactory Methods
/**
* <p>
* Create and return a new <code>BasicDataSource</code> instance. If no instance can be created, return
* <code>null</code> instead.
* </p>
*
* @param obj The possibly null object containing location or reference information that can be used in
* creating an object
* @param name The name of this object relative to <code>nameCtx</code>
* @param nameCtx The context relative to which the <code>name</code> parameter is specified, or
* <code>null</code> if <code>name</code> is relative to the default initial context
* @param environment The possibly null environment that is used in creating this object
*
* @exception Exception if an exception occurs creating the instance
*/
@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment)
throws Exception {
// We only know how to deal with <code>javax.naming.Reference</code>s
// that specify a class name of "javax.sql.DataSource"
if ((obj == null) || !(obj instanceof Reference)) {
return null;
}
Reference ref = (Reference) obj;
boolean XA = false;
boolean ok = false;
if ("javax.sql.DataSource".equals(ref.getClassName())) {
ok = true;
}
if ("javax.sql.XADataSource".equals(ref.getClassName())) {
ok = true;
XA = true;
}
if (org.apache.tomcat.jdbc.pool.DataSource.class.getName().equals(ref.getClassName())) {
ok = true;
}
if (!ok) {
log.warn(MessageFactory.get("dataSourceFactory.invalidClassName", ref.getClassName()));
return null;
}
Properties properties = new Properties();
DataSource dataSourceObject = null;
for (int i = 0; i < ALL_PROPERTIES.length; i++) {
String propertyName = ALL_PROPERTIES[i];
RefAddr ra = ref.get(propertyName);
if (ra != null) {
Object content = ra.getContent();
if (content == null) {
// RefAddr.getContent() is nullable, and a property with no
// content simply has no value to apply
continue;
}
if (PROP_DATASOURCE.equals(propertyName) && content instanceof DataSource) {
// A live data source cannot travel through Properties as a
// string, so keep the object and hand it to the pool below
dataSourceObject = (DataSource) content;
continue;
}
properties.setProperty(propertyName, content.toString());
}
}
// Whatever the lookup above did not recognize is never read, so the
// operator hears about it here instead of watching the pool come up
// with defaults. Names the JNDI container itself uses to build the
// reference are not pool properties and stay unmentioned.
Enumeration<RefAddr> addresses = ref.getAll();
while (addresses.hasMoreElements()) {
String addressName = addresses.nextElement().getType();
if (!ALL_PROPERTY_NAMES.contains(addressName)
&& !RESERVED_JNDI_ADDRESS_NAMES.contains(addressName)) {
log.warn(MessageFactory.get("dataSourceFactory.invalidProperty", addressName));
}
}
if (dataSourceObject == null) {
// no live data source involved - keep the legacy dispatch, so that
// subclasses overriding the three argument createDataSource() are
// still used for plain JNDI resources
return createDataSource(properties, nameCtx, XA);
}
return createDataSource(properties, nameCtx, XA, dataSourceObject);
}
/**
* Parse the given properties into a PoolConfiguration.
*
* @param properties The properties to parse
*
* @return The configured PoolConfiguration
*/
public static PoolConfiguration parsePoolProperties(Properties properties) {
PoolConfiguration poolProperties = new PoolProperties();
String value = null;
value = properties.getProperty(PROP_DEFAULTAUTOCOMMIT);
if (value != null) {
Boolean b = parseBoolean(PROP_DEFAULTAUTOCOMMIT, value);
if (b != null) {
poolProperties.setDefaultAutoCommit(b);
}
}
value = properties.getProperty(PROP_DEFAULTREADONLY);
if (value != null) {
Boolean b = parseBoolean(PROP_DEFAULTREADONLY, value);
if (b != null) {
poolProperties.setDefaultReadOnly(b);
}
}
value = properties.getProperty(PROP_DEFAULTTRANSACTIONISOLATION);
if (value != null) {
int level = UNKNOWN_TRANSACTIONISOLATION;
if ("NONE".equalsIgnoreCase(value)) {
level = Connection.TRANSACTION_NONE;
} else if ("READ_COMMITTED".equalsIgnoreCase(value)) {
level = Connection.TRANSACTION_READ_COMMITTED;
} else if ("READ_UNCOMMITTED".equalsIgnoreCase(value)) {
level = Connection.TRANSACTION_READ_UNCOMMITTED;
} else if ("REPEATABLE_READ".equalsIgnoreCase(value)) {
level = Connection.TRANSACTION_REPEATABLE_READ;
} else if ("SERIALIZABLE".equalsIgnoreCase(value)) {
level = Connection.TRANSACTION_SERIALIZABLE;
} else {
try {
level = Integer.parseInt(value);
} catch (NumberFormatException e) {
System.err.println("Could not parse defaultTransactionIsolation: " + value);
System.err.println("WARNING: defaultTransactionIsolation not set");
System.err.println("using default value of database driver");
level = UNKNOWN_TRANSACTIONISOLATION;
}
}
poolProperties.setDefaultTransactionIsolation(level);
}
value = properties.getProperty(PROP_DEFAULTCATALOG);
if (value != null) {
poolProperties.setDefaultCatalog(value);
}
value = properties.getProperty(PROP_DRIVERCLASSNAME);
if (value != null) {
poolProperties.setDriverClassName(value);
}
value = properties.getProperty(PROP_MAXACTIVE);
if (value != null) {
poolProperties.setMaxActive(Integer.parseInt(value));
}
value = properties.getProperty(PROP_MAXIDLE);
if (value != null) {
poolProperties.setMaxIdle(Integer.parseInt(value));
}
value = properties.getProperty(PROP_MINIDLE);
if (value != null) {
poolProperties.setMinIdle(Integer.parseInt(value));
}
value = properties.getProperty(PROP_INITIALSIZE);
if (value != null) {
poolProperties.setInitialSize(Integer.parseInt(value));
}
value = properties.getProperty(PROP_MAXWAIT);
if (value != null) {
poolProperties.setMaxWait(Integer.parseInt(value));
}
value = properties.getProperty(PROP_TESTONBORROW);
if (value != null) {
Boolean b = parseBoolean(PROP_TESTONBORROW, value);
if (b != null) {
poolProperties.setTestOnBorrow(b.booleanValue());
}
}
value = properties.getProperty(PROP_TESTONRETURN);
if (value != null) {
Boolean b = parseBoolean(PROP_TESTONRETURN, value);
if (b != null) {
poolProperties.setTestOnReturn(b.booleanValue());
}
}
value = properties.getProperty(PROP_TESTONCONNECT);
if (value != null) {
Boolean b = parseBoolean(PROP_TESTONCONNECT, value);
if (b != null) {
poolProperties.setTestOnConnect(b.booleanValue());
}
}
value = properties.getProperty(PROP_TIMEBETWEENEVICTIONRUNSMILLIS);
if (value != null) {
poolProperties.setTimeBetweenEvictionRunsMillis(Integer.parseInt(value));
}
value = properties.getProperty(PROP_NUMTESTSPEREVICTIONRUN);
if (value != null) {
log.warn(MessageFactory.get("dataSourceFactory.invalidSetting", PROP_NUMTESTSPEREVICTIONRUN));
poolProperties.setNumTestsPerEvictionRun(Integer.parseInt(value));
}
value = properties.getProperty(PROP_MINEVICTABLEIDLETIMEMILLIS);
if (value != null) {
poolProperties.setMinEvictableIdleTimeMillis(Integer.parseInt(value));
}
value = properties.getProperty(PROP_TESTWHILEIDLE);
if (value != null) {
Boolean b = parseBoolean(PROP_TESTWHILEIDLE, value);
if (b != null) {
poolProperties.setTestWhileIdle(b.booleanValue());
}
}
value = properties.getProperty(PROP_PASSWORD);
if (value != null) {
poolProperties.setPassword(value);
}
value = properties.getProperty(PROP_URL);
if (value != null) {
poolProperties.setUrl(value);
}
value = properties.getProperty(PROP_USERNAME);
if (value != null) {
poolProperties.setUsername(value);
}
value = properties.getProperty(PROP_VALIDATIONQUERY);
if (value != null) {
poolProperties.setValidationQuery(value);
}
value = properties.getProperty(PROP_VALIDATIONQUERY_TIMEOUT);
if (value != null) {
poolProperties.setValidationQueryTimeout(Integer.parseInt(value));
}
value = properties.getProperty(PROP_VALIDATOR_CLASS_NAME);
if (value != null) {
poolProperties.setValidatorClassName(value);
}
value = properties.getProperty(PROP_VALIDATIONINTERVAL);
if (value != null) {
poolProperties.setValidationInterval(Long.parseLong(value));
}
value = properties.getProperty(PROP_ACCESSTOUNDERLYINGCONNECTIONALLOWED);
if (value != null) {
log.warn(MessageFactory.get("dataSourceFactory.invalidSetting", PROP_ACCESSTOUNDERLYINGCONNECTIONALLOWED));
Boolean b = parseBoolean(PROP_ACCESSTOUNDERLYINGCONNECTIONALLOWED, value);
if (b != null) {
poolProperties.setAccessToUnderlyingConnectionAllowed(b.booleanValue());
}
}
value = properties.getProperty(PROP_REMOVEABANDONED);
if (value != null) {
Boolean b = parseBoolean(PROP_REMOVEABANDONED, value);
if (b != null) {
poolProperties.setRemoveAbandoned(b.booleanValue());
}
}
value = properties.getProperty(PROP_REMOVEABANDONEDTIMEOUT);
if (value != null) {
poolProperties.setRemoveAbandonedTimeout(Integer.parseInt(value));
}
value = properties.getProperty(PROP_LOGABANDONED);
if (value != null) {
Boolean b = parseBoolean(PROP_LOGABANDONED, value);
if (b != null) {
poolProperties.setLogAbandoned(b.booleanValue());
}
}
value = properties.getProperty(PROP_POOLPREPAREDSTATEMENTS);
if (value != null) {
log.warn(MessageFactory.get("dataSourceFactory.invalidSetting", PROP_POOLPREPAREDSTATEMENTS));
}
value = properties.getProperty(PROP_MAXOPENPREPAREDSTATEMENTS);
if (value != null) {
log.warn(MessageFactory.get("dataSourceFactory.invalidSetting", PROP_MAXOPENPREPAREDSTATEMENTS));
}
value = properties.getProperty(PROP_CONNECTIONPROPERTIES);
if (value != null) {
// Use the canonical setter so that the configured string also
// round-trips through getConnectionProperties(); it parses the
// pairs into getDbProperties() with the very same parser that
// getProperties(value) below used to apply.
poolProperties.setConnectionProperties(value);
} else {
poolProperties.setDbProperties(new Properties());
}
if (poolProperties.getUsername() != null) {
poolProperties.getDbProperties().setProperty("user", poolProperties.getUsername());
}
if (poolProperties.getPassword() != null) {
poolProperties.getDbProperties().setProperty("password", poolProperties.getPassword());
}
value = properties.getProperty(PROP_INITSQL);
if (value != null) {
poolProperties.setInitSQL(value);
}
value = properties.getProperty(PROP_INTERCEPTORS);
if (value != null) {
poolProperties.setJdbcInterceptors(value);
}
value = properties.getProperty(PROP_JMX_ENABLED);
if (value != null) {
Boolean b = parseBoolean(PROP_JMX_ENABLED, value);
if (b != null) {
poolProperties.setJmxEnabled(b.booleanValue());
}
}
value = properties.getProperty(PROP_FAIR_QUEUE);
if (value != null) {
Boolean b = parseBoolean(PROP_FAIR_QUEUE, value);
if (b != null) {
poolProperties.setFairQueue(b.booleanValue());
}
}
value = properties.getProperty(PROP_USE_EQUALS);
if (value != null) {
Boolean b = parseBoolean(PROP_USE_EQUALS, value);
if (b != null) {
poolProperties.setUseEquals(b.booleanValue());
}
}
value = properties.getProperty(OBJECT_NAME);
if (value != null) {
poolProperties.setName(ObjectName.quote(value));
}
value = properties.getProperty(PROP_ABANDONWHENPERCENTAGEFULL);
if (value != null) {
poolProperties.setAbandonWhenPercentageFull(Integer.parseInt(value));
}
value = properties.getProperty(PROP_MAXAGE);
if (value != null) {
poolProperties.setMaxAge(Long.parseLong(value));
}
value = properties.getProperty(PROP_USE_CON_LOCK);
if (value != null) {
Boolean b = parseBoolean(PROP_USE_CON_LOCK, value);
if (b != null) {
poolProperties.setUseLock(b.booleanValue());
}
}
value = properties.getProperty(PROP_DATASOURCE);
if (value != null) {
// Only genuinely string-valued dataSource attributes reach this
// point: getObjectInstance hands an actual javax.sql.DataSource
// RefAddr content straight to the pool instead of stringifying it.
// A string cannot be turned into a data source here.
throw new IllegalArgumentException(MessageFactory.get("dataSourceFactory.cannotSetDataSourceString",
value));
}
value = properties.getProperty(PROP_DATASOURCE_JNDI);
if (value != null) {
poolProperties.setDataSourceJNDI(value);
}
value = properties.getProperty(PROP_SUSPECT_TIMEOUT);
if (value != null) {
poolProperties.setSuspectTimeout(Integer.parseInt(value));
}
value = properties.getProperty(PROP_ALTERNATE_USERNAME_ALLOWED);
if (value != null) {
Boolean b = parseBoolean(PROP_ALTERNATE_USERNAME_ALLOWED, value);
if (b != null) {
poolProperties.setAlternateUsernameAllowed(b.booleanValue());
}
}
value = properties.getProperty(PROP_COMMITONRETURN);
if (value != null) {
Boolean b = parseBoolean(PROP_COMMITONRETURN, value);
if (b != null) {
poolProperties.setCommitOnReturn(b.booleanValue());
}
}
value = properties.getProperty(PROP_ROLLBACKONRETURN);
if (value != null) {
Boolean b = parseBoolean(PROP_ROLLBACKONRETURN, value);
if (b != null) {
poolProperties.setRollbackOnReturn(b.booleanValue());
}
}
value = properties.getProperty(PROP_USEDISPOSABLECONNECTIONFACADE);
if (value != null) {
Boolean b = parseBoolean(PROP_USEDISPOSABLECONNECTIONFACADE, value);
if (b != null) {
storeDeprecatedUseDisposableConnectionFacade(poolProperties, b.booleanValue());
}
}
value = properties.getProperty(PROP_LOGVALIDATIONERRORS);
if (value != null) {
Boolean b = parseBoolean(PROP_LOGVALIDATIONERRORS, value);
if (b != null) {
poolProperties.setLogValidationErrors(b.booleanValue());
}
}
value = properties.getProperty(PROP_PROPAGATEINTERRUPTSTATE);
if (value != null) {
Boolean b = parseBoolean(PROP_PROPAGATEINTERRUPTSTATE, value);
if (b != null) {
poolProperties.setPropagateInterruptState(b.booleanValue());
}
}
value = properties.getProperty(PROP_IGNOREEXCEPTIONONPRELOAD);
if (value != null) {
Boolean b = parseBoolean(PROP_IGNOREEXCEPTIONONPRELOAD, value);
if (b != null) {
poolProperties.setIgnoreExceptionOnPreLoad(b.booleanValue());
}
}
value = properties.getProperty(PROP_USESTATEMENTFACADE);
if (value != null) {
Boolean b = parseBoolean(PROP_USESTATEMENTFACADE, value);
if (b != null) {
storeDeprecatedUseStatementFacade(poolProperties, b.booleanValue());
}
}
return poolProperties;
}
/**
* Parses the value configured for a boolean property. Unlike {@link Boolean#parseBoolean(String)}, which silently
* yields {@code false} for any input, an unrecognized value is reported instead of being applied as {@code false}.
*
* @param name the property name, for the error message
* @param value the configured value, never {@code null}
*
* @return {@link Boolean#TRUE} or {@link Boolean#FALSE}, or {@code null} when the value is empty and the property
* is therefore to be left unset: at its {@link PoolProperties} default for a plain boolean, at its "use
* the driver default" state for a tri-state property
*
* @throws IllegalArgumentException if the value is neither {@code true} nor {@code false}, ignoring case and
* surrounding whitespace
*/
private static Boolean parseBoolean(String name, String value) {
String trimmed = value.trim();
if (trimmed.isEmpty()) {
return null;
}
if ("true".equalsIgnoreCase(trimmed)) {
return Boolean.TRUE;
}
if ("false".equalsIgnoreCase(trimmed)) {
return Boolean.FALSE;
}
throw new IllegalArgumentException(MessageFactory.get("dataSourceFactory.invalidBoolean", value, name));
}
/**
* Creates and configures a {@link DataSource} instance based on the given properties.
*
* @param properties the datasource configuration properties
*
* @return the datasource
*
* @throws Exception if an error occurs creating the data source
*/
public DataSource createDataSource(Properties properties) throws Exception {
return createDataSource(properties, null, false);
}
/**
* Creates and configures a {@link DataSource} instance based on the given properties.
*
* @param properties The datasource configuration properties
* @param context The JNDI context
* @param XA {@code true} to create an XA data source
*
* @return The configured datasource
*
* @throws Exception if an error occurs creating the data source
*/
public DataSource createDataSource(Properties properties, Context context, boolean XA) throws Exception {
return createDataSource(properties, context, XA, null);
}
/**
* Creates and configures a {@link DataSource} instance based on the given properties, with an optional live data
* source for the pool to wrap.
* <p>
* When {@code dataSourceObject} is not {@code null}, it is set on the pool configuration before the pool is
* created, and the {@code dataSourceJNDI} lookup is skipped: the pool is built around the object that was handed
* to the factory directly.
* </p>
*
* @param properties The datasource configuration properties
* @param context The JNDI context
* @param XA {@code true} to create an XA data source
* @param dataSourceObject A live {@link DataSource} the pool should wrap, or {@code null}
*
* @return The configured datasource
*
* @throws Exception if an error occurs creating the data source
*/
protected DataSource createDataSource(Properties properties, Context context, boolean XA,
DataSource dataSourceObject) throws Exception {
PoolConfiguration poolProperties = parsePoolProperties(properties);
if (dataSourceObject != null) {
poolProperties.setDataSource(dataSourceObject);
}
if (poolProperties.getDataSourceJNDI() != null && poolProperties.getDataSource() == null) {
performJNDILookup(context, poolProperties);
}
org.apache.tomcat.jdbc.pool.DataSource dataSource = XA ? new XADataSource(poolProperties)
: new org.apache.tomcat.jdbc.pool.DataSource(poolProperties);
// Initialise the pool itself
dataSource.createPool();
// Return the configured DataSource instance
return dataSource;
}
/**
* Perform a JNDI lookup for the data source.
*
* @param context The JNDI context
* @param poolProperties The pool configuration
*
* @throws NamingException if the name configured through the dataSourceJNDI property cannot be resolved. The name
* was configured specifically to supply the connections for the pool, so a pool without
* that data source is not a usable fallback and the failure is reported instead of
* being deferred to the first connection request.
*/
public void performJNDILookup(Context context, PoolConfiguration poolProperties) throws NamingException {
Object jndiDS = null;
try {
if (context != null) {
jndiDS = context.lookup(poolProperties.getDataSourceJNDI());
} else {
log.warn(MessageFactory.get("dataSourceFactory.jndiContextNull"));
}
} catch (NamingException e) {
if (log.isDebugEnabled()) {
log.debug(
"The name \"" + poolProperties.getDataSourceJNDI() + "\" cannot be found in the local context.",
e);
}
}
NamingException initialContextFailure = null;
if (jndiDS == null) {
try {
context = new InitialContext();
jndiDS = context.lookup(poolProperties.getDataSourceJNDI());
} catch (NamingException e) {
initialContextFailure = e;
log.warn(MessageFactory.get("dataSourceFactory.jndiNameNotFound",
poolProperties.getDataSourceJNDI()), e);
}
}
if (jndiDS != null) {
poolProperties.setDataSource(jndiDS);
return;
}
NamingException failure =
new NamingException(MessageFactory.get("dataSourceFactory.jndiLookupFailed",
poolProperties.getDataSourceJNDI()));
if (initialContextFailure != null) {
failure.setRootCause(initialContextFailure);
}
throw failure;
}
/**
* Parse properties from the string. Format of the string must be [propertyName=property;]*.
*
* @param propText The properties string
*
* @return the properties
*/
protected static Properties getProperties(String propText) {
return PoolProperties.getProperties(propText, null);
}
/**
* Stores the configured value of the deprecated useDisposableConnectionFacade property. The property has no effect
* any more, the disposable connection facade is always used; the value is stored as configured so that the property
* keeps showing up the way it was set, the same as through any other configuration path.
*
* @param poolProperties the properties to store the value on
* @param value the configured value
*/
@SuppressWarnings("deprecation")
private static void storeDeprecatedUseDisposableConnectionFacade(PoolConfiguration poolProperties, boolean value) {
poolProperties.setUseDisposableConnectionFacade(value);
}
/**
* Stores the configured value of the deprecated useStatementFacade property. The property has no effect any more,
* the statement facade is always used; the value is stored as configured so that the property keeps showing up the
* way it was set, the same as through any other configuration path.
*
* @param poolProperties the properties to store the value on
* @param value the configured value
*/
@SuppressWarnings("deprecation")
private static void storeDeprecatedUseStatementFacade(PoolConfiguration poolProperties, boolean value) {
poolProperties.setUseStatementFacade(value);
}
}