PoolProperties.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.io.IOException;
import java.io.Serializable;
import java.io.StringReader;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.jdbc.pool.util.MessageFactory;
/**
* Implementation of {@link PoolConfiguration} that holds the configuration properties for a connection pool.
*/
public class PoolProperties implements PoolConfiguration, Cloneable, Serializable {
private static final long serialVersionUID = -8519283440854213745L;
private static final Log log = LogFactory.getLog(PoolProperties.class);
/** The mask the {@link #toString()} dump prints in place of a password. */
private static final String PASSWORD_MASK = "********";
/**
* Constructs a PoolProperties with default values.
*/
public PoolProperties() {
}
/**
* Default maximum number of active connections.
*/
public static final int DEFAULT_MAX_ACTIVE = 100;
/**
* Counter for tracking the number of pools created.
*/
protected static final AtomicInteger poolCounter = new AtomicInteger(0);
/** Database-specific properties. */
private volatile Properties dbProperties = new Properties();
/** The JDBC connection URL. */
private volatile String url = null;
/** The JDBC driver class name. */
private volatile String driverClassName = null;
/** The default auto-commit setting for connections. */
private volatile Boolean defaultAutoCommit = null;
/** The default read-only setting for connections. */
private volatile Boolean defaultReadOnly = null;
/** The default transaction isolation level. */
private volatile int defaultTransactionIsolation = DataSourceFactory.UNKNOWN_TRANSACTIONISOLATION;
/** The default catalog for connections. */
private volatile String defaultCatalog = null;
/** Additional connection properties. */
private volatile String connectionProperties;
/** Keys the last parse of {@link #connectionProperties} contributed to {@link #dbProperties}. */
private volatile Set<String> connectionPropertyKeys = Collections.emptySet();
/** The initial number of connections in the pool. */
private volatile int initialSize = 10;
/** The maximum number of active connections. */
private volatile int maxActive = DEFAULT_MAX_ACTIVE;
/** The maximum number of idle connections. */
private volatile int maxIdle = maxActive;
/** The minimum number of idle connections. */
private volatile int minIdle = initialSize;
/** The maximum wait time in milliseconds for a connection. */
private volatile int maxWait = 30000;
/** The SQL query used for validation. */
private volatile String validationQuery;
/** The validation query timeout in seconds. */
private volatile int validationQueryTimeout = -1;
/** The fully qualified class name of the validator. */
private volatile String validatorClassName;
/** The validator instance. */
private transient volatile Validator validator;
/** Whether to validate connections on borrow. */
private volatile boolean testOnBorrow = false;
/** Whether to validate connections on return. */
private volatile boolean testOnReturn = false;
/** Whether to validate idle connections. */
private volatile boolean testWhileIdle = false;
/** The time between eviction runs in milliseconds. */
private volatile int timeBetweenEvictionRunsMillis = 5000;
/** The number of connections to test per eviction run. */
private volatile int numTestsPerEvictionRun;
/** The minimum idle time before a connection is eligible for eviction. */
private volatile int minEvictableIdleTimeMillis = 60000;
/** Whether to allow access to the underlying connection. */
private volatile boolean accessToUnderlyingConnectionAllowed = true;
/** Whether to remove abandoned connections. */
private volatile boolean removeAbandoned = false;
/** The timeout in seconds for abandoned connection removal. */
private volatile int removeAbandonedTimeout = 60;
/** Whether to log abandoned connections. */
private volatile boolean logAbandoned = false;
/** The pool name. */
private volatile String name = "Tomcat Connection Pool[" + (poolCounter.addAndGet(1)) + "-" +
System.identityHashCode(PoolProperties.class) + "]";
/** The database password. */
private volatile String password;
/** The database username. */
private volatile String username;
/** The validation interval in milliseconds. */
private volatile long validationInterval = 3000;
/** Whether JMX registration is enabled. */
private volatile boolean jmxEnabled = true;
/** The SQL to execute on connection creation. */
private volatile String initSQL;
/** Whether to validate connections on creation. */
private volatile boolean testOnConnect = false;
/** The JDBC interceptors configuration. */
private volatile String jdbcInterceptors = null;
/** Whether to use a fair queue for connection requests. */
private volatile boolean fairQueue = true;
/** Whether to use equals for object comparison. */
private volatile boolean useEquals = true;
/** The percentage threshold for abandoning connections. */
private volatile int abandonWhenPercentageFull = 0;
/** The maximum age of a connection in milliseconds. */
private volatile long maxAge = 0;
/** Whether to use locking for thread safety. */
private volatile boolean useLock = false;
/** The interceptor definitions. */
private volatile InterceptorDefinition[] interceptors = null;
/** The suspect timeout in seconds. */
private volatile int suspectTimeout = 0;
/** The underlying data source. */
private volatile Object dataSource = null;
/** The JNDI name of the data source. */
private volatile String dataSourceJNDI = null;
/** Whether alternate usernames are allowed. */
private volatile boolean alternateUsernameAllowed = false;
/** Whether to commit on connection return. */
private volatile boolean commitOnReturn = false;
/** Whether to rollback on connection return. */
private volatile boolean rollbackOnReturn = false;
/**
* Whether to use a disposable connection facade.
*
* @deprecated The disposable connection facade is always used, so this property no longer has any effect.
*/
@Deprecated
private volatile boolean useDisposableConnectionFacade = true;
/** Whether to log validation errors. */
private volatile boolean logValidationErrors = false;
/** Whether to propagate interrupt state. */
private volatile boolean propagateInterruptState = false;
/** Whether to ignore exceptions during pre-load. */
private volatile boolean ignoreExceptionOnPreLoad = false;
/**
* Whether to use a statement facade.
*
* @deprecated The statement facade is always used, so this property no longer has any effect.
*/
@Deprecated
private volatile boolean useStatementFacade = true;
@Override
public void setAbandonWhenPercentageFull(int percentage) {
if (percentage < 0) {
abandonWhenPercentageFull = 0;
} else if (percentage > 100) {
abandonWhenPercentageFull = 100;
} else {
abandonWhenPercentageFull = percentage;
}
}
@Override
public int getAbandonWhenPercentageFull() {
return abandonWhenPercentageFull;
}
@Override
public boolean isFairQueue() {
return fairQueue;
}
@Override
public void setFairQueue(boolean fairQueue) {
this.fairQueue = fairQueue;
}
@Override
public boolean isAccessToUnderlyingConnectionAllowed() {
return accessToUnderlyingConnectionAllowed;
}
@Override
public String getConnectionProperties() {
return connectionProperties;
}
@Override
public Properties getDbProperties() {
return dbProperties;
}
@Override
public Boolean isDefaultAutoCommit() {
return defaultAutoCommit;
}
@Override
public String getDefaultCatalog() {
return defaultCatalog;
}
@Override
public Boolean isDefaultReadOnly() {
return defaultReadOnly;
}
@Override
public int getDefaultTransactionIsolation() {
return defaultTransactionIsolation;
}
@Override
public String getDriverClassName() {
return driverClassName;
}
@Override
public int getInitialSize() {
return initialSize;
}
@Override
public boolean isLogAbandoned() {
return logAbandoned;
}
@Override
public int getMaxActive() {
return maxActive;
}
@Override
public int getMaxIdle() {
return maxIdle;
}
@Override
public int getMaxWait() {
return maxWait;
}
@Override
public int getMinEvictableIdleTimeMillis() {
return minEvictableIdleTimeMillis;
}
@Override
public int getMinIdle() {
return minIdle;
}
@Override
public String getName() {
return name;
}
@Override
public int getNumTestsPerEvictionRun() {
return numTestsPerEvictionRun;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getPoolName() {
return getName();
}
@Override
public boolean isRemoveAbandoned() {
return removeAbandoned;
}
@Override
public int getRemoveAbandonedTimeout() {
return removeAbandonedTimeout;
}
@Override
public boolean isTestOnBorrow() {
return testOnBorrow;
}
@Override
public boolean isTestOnReturn() {
return testOnReturn;
}
@Override
public boolean isTestWhileIdle() {
return testWhileIdle;
}
@Override
public int getTimeBetweenEvictionRunsMillis() {
return timeBetweenEvictionRunsMillis;
}
@Override
public String getUrl() {
return url;
}
@Override
public String getUsername() {
return username;
}
@Override
public String getValidationQuery() {
return validationQuery;
}
@Override
public int getValidationQueryTimeout() {
return validationQueryTimeout;
}
@Override
public void setValidationQueryTimeout(int validationQueryTimeout) {
this.validationQueryTimeout = validationQueryTimeout;
}
@Override
public String getValidatorClassName() {
return validatorClassName;
}
@Override
public Validator getValidator() {
return validator;
}
@Override
public void setValidator(Validator validator) {
this.validator = validator;
if (validator != null) {
this.validatorClassName = validator.getClass().getName();
} else {
this.validatorClassName = null;
}
}
@Override
public long getValidationInterval() {
return validationInterval;
}
@Override
public String getInitSQL() {
return initSQL;
}
@Override
public boolean isTestOnConnect() {
return testOnConnect;
}
@Override
public String getJdbcInterceptors() {
return jdbcInterceptors;
}
/**
* {@inheritDoc}
*
* <p>The string is parsed on first use and the result cached. The check,
* the parse and the publish run inside this one synchronized method, under
* the same lock {@link #setJdbcInterceptors(String)} takes to clear the
* cache: a parse that read the previous value of the string can therefore
* never store its result back after a reconfiguration cleared it, which is
* what would leave every later caller on the discarded interceptor chain
* for good. The string is read once into a local so the parse works on a
* single snapshot. A malformed value throws before the cache is written, so
* a later call parses the then-current value again. The lock is never held
* over interceptor class loading: an {@link InterceptorDefinition} parsed
* from the string only keeps the class name, and those classes are loaded
* later by the pool, outside this lock.</p>
*/
@Override
public synchronized InterceptorDefinition[] getJdbcInterceptorsAsArray() {
if (interceptors == null) {
// One read of the reconfigurable string: the parse below must see
// a single value, not a fresh read per use.
String interceptorList = jdbcInterceptors;
if (interceptorList == null) {
interceptors = new InterceptorDefinition[0];
} else {
String[] interceptorValues = interceptorList.split(";");
InterceptorDefinition[] definitions = new InterceptorDefinition[interceptorValues.length + 1];
// Always add the trap interceptor to the mix
definitions[0] = new InterceptorDefinition(TrapException.class);
for (int i = 0; i < interceptorValues.length; i++) {
int propIndex = interceptorValues[i].indexOf('(');
int endIndex = interceptorValues[i].indexOf(')');
if (propIndex < 0 || endIndex < 0 || endIndex <= propIndex) {
definitions[i + 1] = new InterceptorDefinition(interceptorValues[i].trim());
} else {
String name = interceptorValues[i].substring(0, propIndex).trim();
definitions[i + 1] = new InterceptorDefinition(name);
String propsAsString = interceptorValues[i].substring(propIndex + 1, endIndex);
String[] props = propsAsString.split(",");
for (int j = 0; j < props.length; j++) {
String prop = props[j];
if (prop.trim().isEmpty()) {
// An empty segment comes from Name(), Name( )
// or a redundant separator: there is no property
// to add, so tolerate it rather than fail the
// whole pool start on a cosmetic spelling
continue;
}
int pidx = prop.indexOf('=');
if (pidx < 0) {
throw new IllegalArgumentException(MessageFactory.get(
"poolProperties.invalidInterceptorProperty", prop.trim(),
interceptorValues[i]));
}
String propName = prop.substring(0, pidx).trim();
String propValue = prop.substring(pidx + 1).trim();
definitions[i + 1].addProperty(new InterceptorProperty(propName, propValue));
}
}
}
interceptors = definitions;
}
}
return interceptors;
}
@Override
public void setAccessToUnderlyingConnectionAllowed(boolean accessToUnderlyingConnectionAllowed) {
// NOOP
}
/**
* Stores the connection properties string and parses the pairs into the
* database properties the pool connects with. The pairs contributed by the
* previous value of the string are removed first, so reconfiguring a running
* pool with a reduced or cleared string really stops sending the dropped
* properties to the driver instead of leaving them merged in. Entries the
* string never contributed, such as the {@code user} and {@code password}
* injected by {@link DataSourceFactory#parsePoolProperties(Properties)} or
* mirrored in by {@link DataSourceProxy}, are kept. A configuration whose
* database properties are {@code null} only stores the raw string, as there
* is no property collection to parse into. The remove and re-add run inside
* this one synchronized method; an unsynchronised reader of the database
* properties, which {@link #getDbProperties()} has always handed out live,
* can therefore observe the contributed keys briefly missing.
*
* @param connectionProperties the property string with semicolon-separated
* key=value pairs, or {@code null} to clear it
*/
@Override
public synchronized void setConnectionProperties(String connectionProperties) {
this.connectionProperties = connectionProperties;
Properties dbProps = getDbProperties();
if (dbProps == null) {
// The pairs have no collection to land in; nothing was contributed,
// so there is nothing to track for a later removal either.
connectionPropertyKeys = Collections.emptySet();
return;
}
// A configuration serialized before this field existed deserializes
// with it null, as field initializers do not run on deserialization.
if (connectionPropertyKeys != null) {
for (String key : connectionPropertyKeys) {
dbProps.remove(key);
}
}
// Parse into a fresh collection first: that is what tells apart the keys
// this string contributes from the ones dbProperties holds from elsewhere.
Properties parsed = getProperties(connectionProperties, new Properties());
Set<String> keys = new HashSet<>(parsed.size());
for (String key : parsed.stringPropertyNames()) {
dbProps.setProperty(key, parsed.getProperty(key));
keys.add(key);
}
connectionPropertyKeys = keys;
}
/**
* Replaces the database properties the pool connects with. The keys tracked
* from the last connection properties parse contributed to the previous
* collection, not to the replacement, so the tracking is reset: the caller
* owns whatever the new collection holds.
*
* @param dbProperties the new database properties, may be {@code null}
*/
@Override
public synchronized void setDbProperties(Properties dbProperties) {
this.dbProperties = dbProperties;
connectionPropertyKeys = Collections.emptySet();
}
@Override
public void setDefaultAutoCommit(Boolean defaultAutoCommit) {
this.defaultAutoCommit = defaultAutoCommit;
}
@Override
public void setDefaultCatalog(String defaultCatalog) {
this.defaultCatalog = defaultCatalog;
}
@Override
public void setDefaultReadOnly(Boolean defaultReadOnly) {
this.defaultReadOnly = defaultReadOnly;
}
@Override
public void setDefaultTransactionIsolation(int defaultTransactionIsolation) {
this.defaultTransactionIsolation = defaultTransactionIsolation;
}
@Override
public void setDriverClassName(String driverClassName) {
this.driverClassName = driverClassName;
}
@Override
public void setInitialSize(int initialSize) {
this.initialSize = initialSize;
}
@Override
public void setLogAbandoned(boolean logAbandoned) {
this.logAbandoned = logAbandoned;
}
@Override
public void setMaxActive(int maxActive) {
this.maxActive = maxActive;
}
@Override
public void setMaxIdle(int maxIdle) {
this.maxIdle = maxIdle;
}
@Override
public void setMaxWait(int maxWait) {
this.maxWait = maxWait;
}
@Override
public void setMinEvictableIdleTimeMillis(int minEvictableIdleTimeMillis) {
this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
}
@Override
public void setMinIdle(int minIdle) {
this.minIdle = minIdle;
}
@Override
public void setName(String name) {
this.name = name;
}
@Override
public void setNumTestsPerEvictionRun(int numTestsPerEvictionRun) {
this.numTestsPerEvictionRun = numTestsPerEvictionRun;
}
@Override
public void setPassword(String password) {
this.password = password;
}
@Override
public void setRemoveAbandoned(boolean removeAbandoned) {
this.removeAbandoned = removeAbandoned;
}
@Override
public void setRemoveAbandonedTimeout(int removeAbandonedTimeout) {
this.removeAbandonedTimeout = removeAbandonedTimeout;
}
@Override
public void setTestOnBorrow(boolean testOnBorrow) {
this.testOnBorrow = testOnBorrow;
}
@Override
public void setTestWhileIdle(boolean testWhileIdle) {
this.testWhileIdle = testWhileIdle;
}
@Override
public void setTestOnReturn(boolean testOnReturn) {
this.testOnReturn = testOnReturn;
}
@Override
public void setTimeBetweenEvictionRunsMillis(int timeBetweenEvictionRunsMillis) {
this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
}
@Override
public void setUrl(String url) {
this.url = url;
}
@Override
public void setUsername(String username) {
this.username = username;
}
@Override
public void setValidationInterval(long validationInterval) {
this.validationInterval = validationInterval;
}
@Override
public void setValidationQuery(String validationQuery) {
this.validationQuery = validationQuery;
}
@Override
public void setValidatorClassName(String className) {
this.validatorClassName = className;
validator = null;
if (className == null) {
return;
}
try {
@SuppressWarnings("unchecked")
Class<Validator> validatorClass = (Class<Validator>) ClassLoaderUtil.loadClass(className,
PoolProperties.class.getClassLoader(), Thread.currentThread().getContextClassLoader());
validator = validatorClass.getConstructor().newInstance();
} catch (ClassNotFoundException e) {
log.warn(MessageFactory.get("poolProperties.validatorClassNotFound", className), e);
} catch (ClassCastException e) {
log.warn(MessageFactory.get("poolProperties.validatorNotValidator", className), e);
} catch (IllegalAccessException e) {
log.warn(MessageFactory.get("poolProperties.validatorInaccessible", className), e);
} catch (ReflectiveOperationException | IllegalArgumentException | SecurityException e) {
log.warn(MessageFactory.get("poolProperties.validatorCannotInstantiate", className), e);
}
}
@Override
public void setInitSQL(String initSQL) {
this.initSQL = initSQL != null && initSQL.trim().length() > 0 ? initSQL : null;
}
@Override
public void setTestOnConnect(boolean testOnConnect) {
this.testOnConnect = testOnConnect;
}
/**
* Stores the interceptor configuration string and drops the parsed cache
* so the next {@link #getJdbcInterceptorsAsArray()} re-parses the new
* value. The clear runs under the same lock the parse publishes under, so
* a parse that started from the previous value of the string can never
* store its result back after this clear.
*
* @param jdbcInterceptors the semicolon separated interceptor configuration,
* may be {@code null} to use none
*/
@Override
public synchronized void setJdbcInterceptors(String jdbcInterceptors) {
this.jdbcInterceptors = jdbcInterceptors;
this.interceptors = null;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder("ConnectionPool[");
try {
String[] fields = DataSourceFactory.ALL_PROPERTIES;
for (String field : fields) {
final String[] prefix = new String[] { "get", "is" };
for (int j = 0; j < prefix.length; j++) {
String name = prefix[j] + field.substring(0, 1).toUpperCase(Locale.ENGLISH) + field.substring(1);
Method m = null;
try {
m = getClass().getMethod(name);
} catch (NoSuchMethodException nm) {
continue;
}
Object value = m.invoke(this, new Object[0]);
buf.append(field);
buf.append('=');
if (DataSourceFactory.PROP_PASSWORD.equals(field)) {
buf.append(PASSWORD_MASK);
} else if (DataSourceFactory.PROP_URL.equals(field)
|| DataSourceFactory.PROP_CONNECTIONPROPERTIES.equals(field)) {
// The same dump prints these raw configuration
// strings, so a credential typed into one of them
// has to be masked too, not just the password field.
buf.append(value == null ? "null" : maskCredentials((String) value));
} else {
buf.append(value);
}
buf.append("; ");
break;
}
}
buf.append(']');
} catch (Exception x) {
// Shouldn't happen
if (log.isDebugEnabled()) {
log.debug("toString() call failed", x);
}
}
return buf.toString();
}
/**
* Masks the value of credential-like {@code key=value} pairs embedded in a
* raw configuration string so that a password typed into the JDBC URL query
* string or the connection properties is not printed in cleartext the way
* the dedicated password field is masked in {@link #toString()}. The keys
* recognised, compared ignoring case, are {@code password}, {@code pass}
* and {@code pwd}. A key is honoured only at a segment boundary - the start
* of the string or one of {@code ;}, {@code &} or {@code ?} - so a key such
* as {@code compass} is left alone, and its value runs to the next
* {@code ;}, {@code &} or the end of the string.
* <p>
* Only the {@code key=value} shape is scrubbed. The
* {@code user:password@host} userinfo form, the {@code key:value} and
* {@code key value} forms that a {@link java.util.Properties} reader
* additionally accepts, and driver-specific secrets that use a key other
* than the three above are not recognised; recognising them would require
* a full parse of each driver's URL dialect.
*
* @param value the raw configuration string, may be {@code null}
*
* @return the string with every credential key's value replaced by the mask,
* or the string unchanged when it holds no recognised credential key
*/
private static String maskCredentials(String value) {
if (value == null || value.indexOf('=') < 0) {
return value;
}
StringBuilder result = new StringBuilder(value.length());
int i = 0;
while (i < value.length()) {
int equals = value.indexOf('=', i);
if (equals < 0) {
break;
}
int keyStart = equals;
while (keyStart > 0 && !isCredentialSegmentSeparator(value.charAt(keyStart - 1))) {
keyStart--;
}
String key = value.substring(keyStart, equals).trim();
int valueStart = equals + 1;
int valueEnd = value.length();
for (int k = valueStart; k < value.length(); k++) {
char c = value.charAt(k);
if (c == ';' || c == '&') {
valueEnd = k;
break;
}
}
result.append(value, i, equals);
result.append('=');
if (isCredentialKey(key)) {
result.append(PASSWORD_MASK);
} else {
result.append(value, valueStart, valueEnd);
}
// Resume after the value so a '=' inside a masked value cannot be
// mistaken for the start of a new key.
i = valueEnd;
}
result.append(value, i, value.length());
return result.toString();
}
/**
* Whether {@code c} ends one configuration segment and starts the next,
* covering the URL query separators and the semicolon used by the
* connection properties string.
*
* @param c the character to test
*
* @return {@code true} when the character is a segment boundary
*/
private static boolean isCredentialSegmentSeparator(char c) {
return c == ';' || c == '&' || c == '?';
}
/**
* Whether {@code key} names a credential, ignoring case and surrounding
* whitespace.
*
* @param key the extracted key
*
* @return {@code true} when the key is one of the recognised credential keys
*/
private static boolean isCredentialKey(String key) {
return DataSourceFactory.PROP_PASSWORD.equalsIgnoreCase(key)
|| "pass".equalsIgnoreCase(key)
|| "pwd".equalsIgnoreCase(key);
}
/**
* Returns the current pool counter value.
*
* @return the pool counter value
*/
public static int getPoolCounter() {
return poolCounter.get();
}
@Override
public boolean isJmxEnabled() {
return jmxEnabled;
}
@Override
public void setJmxEnabled(boolean jmxEnabled) {
this.jmxEnabled = jmxEnabled;
}
@Override
public Boolean getDefaultAutoCommit() {
return defaultAutoCommit;
}
@Override
public Boolean getDefaultReadOnly() {
return defaultReadOnly;
}
@Override
public int getSuspectTimeout() {
return this.suspectTimeout;
}
@Override
public void setSuspectTimeout(int seconds) {
this.suspectTimeout = seconds;
}
@Override
public boolean isPoolSweeperEnabled() {
boolean timer = getTimeBetweenEvictionRunsMillis() > 0;
boolean result = timer && (isRemoveAbandoned() && getRemoveAbandonedTimeout() > 0);
result = result || (timer && getSuspectTimeout() > 0);
result = result || (timer && isTestWhileIdle());
result = result || (timer && getMinEvictableIdleTimeMillis() > 0);
result = result || (timer && getMaxAge() > 0);
return result;
}
/**
* Definition of a JDBC interceptor with its configuration properties.
*/
public static class InterceptorDefinition implements Serializable {
private static final long serialVersionUID = 1L;
/**
* Interceptor class name.
*/
protected String className;
/**
* Map of interceptor properties.
*/
protected Map<String, InterceptorProperty> properties = new HashMap<>();
/**
* Cached interceptor class.
*/
protected volatile Class<?> clazz = null;
/**
* Constructs an InterceptorDefinition with the given class name.
*
* @param className the interceptor class name
*/
public InterceptorDefinition(String className) {
this.className = className;
}
/**
* Constructs an InterceptorDefinition with the given class.
*
* @param cl the interceptor class
*/
public InterceptorDefinition(Class<?> cl) {
this(cl.getName());
clazz = cl;
}
/**
* Returns the interceptor class name.
*
* @return the class name
*/
public String getClassName() {
return className;
}
/**
* Adds a property with the given name and value.
*
* @param name the property name
* @param value the property value
*/
public void addProperty(String name, String value) {
InterceptorProperty p = new InterceptorProperty(name, value);
addProperty(p);
}
/**
* Adds the given interceptor property.
*
* @param p the property to add
*/
public void addProperty(InterceptorProperty p) {
properties.put(p.getName(), p);
}
/**
* Returns the map of interceptor properties.
*
* @return the properties map
*/
public Map<String, InterceptorProperty> getProperties() {
return properties;
}
/**
* Returns the interceptor class, loading it if necessary.
*
* @return the interceptor class
*
* @throws ClassNotFoundException if the class cannot be found
*/
@SuppressWarnings("unchecked")
public Class<? extends JdbcInterceptor> getInterceptorClass() throws ClassNotFoundException {
if (clazz == null) {
if (getClassName().indexOf('.') < 0) {
if (log.isDebugEnabled()) {
log.debug("Loading interceptor class:" + PKG_PREFIX + getClassName());
}
clazz = ClassLoaderUtil.loadClass(PKG_PREFIX + getClassName(),
PoolProperties.class.getClassLoader(), Thread.currentThread().getContextClassLoader());
} else {
if (log.isDebugEnabled()) {
log.debug("Loading interceptor class:" + getClassName());
}
clazz = ClassLoaderUtil.loadClass(getClassName(), PoolProperties.class.getClassLoader(),
Thread.currentThread().getContextClassLoader());
}
}
return (Class<? extends JdbcInterceptor>) clazz;
}
}
/**
* Represents a property for a JDBC interceptor.
*/
public static class InterceptorProperty implements Serializable {
private static final long serialVersionUID = 1L;
/**
* Property name.
*/
String name;
/**
* Property value.
*/
String value;
/**
* Constructs an InterceptorProperty with the given name and value.
*
* @param name the property name
* @param value the property value
*/
public InterceptorProperty(String name, String value) {
assert (name != null);
this.name = name;
this.value = value;
}
/**
* Returns the property name.
*
* @return the property name
*/
public String getName() {
return name;
}
/**
* Returns the property value.
*
* @return the property value
*/
public String getValue() {
return value;
}
/**
* Returns the property value as a boolean.
*
* @param def the default value if the property is null or invalid
*
* @return the boolean value
*/
public boolean getValueAsBoolean(boolean def) {
if (value == null) {
return def;
}
if ("true".equals(value)) {
return true;
}
if ("false".equals(value)) {
return false;
}
return def;
}
/**
* Returns the property value as an int.
*
* @param def the default value if the property is null or invalid
*
* @return the int value
*/
public int getValueAsInt(int def) {
if (value == null) {
return def;
}
try {
int v = Integer.parseInt(value);
return v;
} catch (NumberFormatException nfe) {
return def;
}
}
/**
* Returns the property value as a long.
*
* @param def the default value if the property is null or invalid
*
* @return the long value
*/
public long getValueAsLong(long def) {
if (value == null) {
return def;
}
try {
return Long.parseLong(value);
} catch (NumberFormatException nfe) {
return def;
}
}
/**
* Returns the property value as a byte.
*
* @param def the default value if the property is null or invalid
*
* @return the byte value
*/
public byte getValueAsByte(byte def) {
if (value == null) {
return def;
}
try {
return Byte.parseByte(value);
} catch (NumberFormatException nfe) {
return def;
}
}
/**
* Returns the property value as a short.
*
* @param def the default value if the property is null or invalid
*
* @return the short value
*/
public short getValueAsShort(short def) {
if (value == null) {
return def;
}
try {
return Short.parseShort(value);
} catch (NumberFormatException nfe) {
return def;
}
}
/**
* Returns the property value as a float.
*
* @param def the default value if the property is null or invalid
*
* @return the float value
*/
public float getValueAsFloat(float def) {
if (value == null) {
return def;
}
try {
return Float.parseFloat(value);
} catch (NumberFormatException nfe) {
return def;
}
}
/**
* Returns the property value as a double.
*
* @param def the default value if the property is null or invalid
*
* @return the double value
*/
public double getValueAsDouble(double def) {
if (value == null) {
return def;
}
try {
return Double.parseDouble(value);
} catch (NumberFormatException nfe) {
return def;
}
}
/**
* Returns the property value as a char.
*
* @param def the default value if the property is null or invalid
*
* @return the char value
*/
public char getValueAschar(char def) {
if (value == null) {
return def;
}
try {
return value.charAt(0);
} catch (StringIndexOutOfBoundsException nfe) {
return def;
}
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (o instanceof InterceptorProperty) {
InterceptorProperty other = (InterceptorProperty) o;
return other.name.equals(this.name);
}
return false;
}
}
@Override
public boolean isUseEquals() {
return useEquals;
}
@Override
public void setUseEquals(boolean useEquals) {
this.useEquals = useEquals;
}
@Override
public long getMaxAge() {
return maxAge;
}
@Override
public void setMaxAge(long maxAge) {
this.maxAge = maxAge;
}
@Override
public boolean getUseLock() {
return useLock;
}
@Override
public void setUseLock(boolean useLock) {
this.useLock = useLock;
}
@Override
public void setDataSource(Object ds) {
if (ds instanceof DataSourceProxy) {
throw new IllegalArgumentException(MessageFactory.get("poolProperties.layeredPoolsNotAllowed"));
}
this.dataSource = ds;
}
@Override
public Object getDataSource() {
return dataSource;
}
@Override
public void setDataSourceJNDI(String jndiDS) {
this.dataSourceJNDI = jndiDS;
}
@Override
public String getDataSourceJNDI() {
return this.dataSourceJNDI;
}
/**
* Parses a property string into a Properties object.
*
* @param propText the property string with semicolon-separated key=value pairs
* @param props the Properties object to populate, or null to create a new one
*
* @return the populated Properties object
*/
public static Properties getProperties(String propText, Properties props) {
if (props == null) {
props = new Properties();
}
if (propText != null) {
try {
// Load through a Reader: Properties.load(InputStream) decodes as
// ISO-8859-1 while encoding the string to bytes uses the platform
// charset, silently corrupting any non-ASCII value on a
// UTF-8-default JVM. A Reader carries the exact characters, so no
// charset conversion takes place at all.
props.load(new StringReader(propText.replace(';', '\n')));
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
}
return props;
}
@Override
public boolean isAlternateUsernameAllowed() {
return alternateUsernameAllowed;
}
@Override
public void setAlternateUsernameAllowed(boolean alternateUsernameAllowed) {
this.alternateUsernameAllowed = alternateUsernameAllowed;
}
@Override
public void setCommitOnReturn(boolean commitOnReturn) {
this.commitOnReturn = commitOnReturn;
}
@Override
public boolean getCommitOnReturn() {
return this.commitOnReturn;
}
@Override
public void setRollbackOnReturn(boolean rollbackOnReturn) {
this.rollbackOnReturn = rollbackOnReturn;
}
@Override
public boolean getRollbackOnReturn() {
return this.rollbackOnReturn;
}
/**
* {@inheritDoc}
*
* @deprecated The disposable connection facade is always used, so this property no longer has any effect.
*/
@Override
@Deprecated
public void setUseDisposableConnectionFacade(boolean useDisposableConnectionFacade) {
this.useDisposableConnectionFacade = useDisposableConnectionFacade;
}
/**
* {@inheritDoc}
*
* @deprecated The disposable connection facade is always used, so this property no longer has any effect.
*/
@Override
@Deprecated
public boolean getUseDisposableConnectionFacade() {
return useDisposableConnectionFacade;
}
@Override
public void setLogValidationErrors(boolean logValidationErrors) {
this.logValidationErrors = logValidationErrors;
}
@Override
public boolean getLogValidationErrors() {
return this.logValidationErrors;
}
@Override
public boolean getPropagateInterruptState() {
return propagateInterruptState;
}
@Override
public void setPropagateInterruptState(boolean propagateInterruptState) {
this.propagateInterruptState = propagateInterruptState;
}
@Override
public boolean isIgnoreExceptionOnPreLoad() {
return ignoreExceptionOnPreLoad;
}
@Override
public void setIgnoreExceptionOnPreLoad(boolean ignoreExceptionOnPreLoad) {
this.ignoreExceptionOnPreLoad = ignoreExceptionOnPreLoad;
}
/**
* {@inheritDoc}
*
* @deprecated The statement facade is always used, so this property no longer has any effect.
*/
@Override
@Deprecated
public boolean getUseStatementFacade() {
return useStatementFacade;
}
/**
* {@inheritDoc}
*
* @deprecated The statement facade is always used, so this property no longer has any effect.
*/
@Override
@Deprecated
public void setUseStatementFacade(boolean useStatementFacade) {
this.useStatementFacade = useStatementFacade;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}