PooledConnection.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.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.management.ObjectName;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.jdbc.pool.interceptor.ConnectionState;
import org.apache.tomcat.jdbc.pool.jmx.JmxUtil;
import org.apache.tomcat.jdbc.pool.util.MessageFactory;
/**
* Represents a pooled connection and holds a reference to the {@link java.sql.Connection} object
*/
public class PooledConnection implements PooledConnectionMBean {
/**
* Logger
*/
private static final Log log = LogFactory.getLog(PooledConnection.class);
/**
* Attribute key for the username.
*/
public static final String PROP_USER = PoolUtilities.PROP_USER;
/**
* Attribute key for the password.
*/
public static final String PROP_PASSWORD = PoolUtilities.PROP_PASSWORD;
/**
* Validate when connection is borrowed flag
*/
public static final int VALIDATE_BORROW = 1;
/**
* Validate when connection is returned flag
*/
public static final int VALIDATE_RETURN = 2;
/**
* Validate when connection is idle flag
*/
public static final int VALIDATE_IDLE = 3;
/**
* Validate when connection is initialized flag
*/
public static final int VALIDATE_INIT = 4;
/**
* The properties for the connection pool
*/
protected PoolConfiguration poolProperties;
/**
* The underlying database connection
*/
private volatile java.sql.Connection connection;
/**
* If using a XAConnection underneath.
*/
protected volatile javax.sql.XAConnection xaConnection;
/**
* If using a ConnectionPoolDataSource underneath, this is the javax.sql.PooledConnection handle obtained from it.
* Per the JDBC specification, closing the logical connection obtained from that handle does not close the physical
* connection, so the handle itself must be kept in order to close the physical connection.
*/
private volatile javax.sql.PooledConnection cpdsConnection;
/**
* When we track abandon traces, this string holds the formatted thread dump
*/
private String abandonTrace = null;
/**
* When we track abandon traces, the frames of the borrow site as they were captured, until an abandoned-connection
* report reads the trace and they are formatted into {@link #abandonTrace}. A pool with logAbandoned borrows
* constantly and reports rarely, so the formatting cost is only paid when the trace is actually read.
*/
private StackTraceElement[] abandonTraceElements = null;
/**
* Timestamp the connection was last 'touched' by the pool
*/
private volatile long timestamp;
/**
* Lock for this connection only
*/
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(false);
/**
* Set to true if this connection has been discarded by the pool
*/
private volatile boolean discarded = false;
/**
* The Timestamp when the last time the connect() method was called successfully
*/
private volatile long lastConnected = -1;
/**
* timestamp to keep track of validation intervals
*/
private volatile long lastValidated = System.currentTimeMillis();
/**
* The parent
*/
protected ConnectionPool parent;
private HashMap<Object, Object> attributes = new HashMap<>();
private volatile long connectionVersion = 0;
private static final AtomicLong connectionIndex = new AtomicLong(0);
private ObjectName oname = null;
/**
* Weak reference to cache the list of interceptors for this connection so that we don't create a new list of
* interceptors each time we borrow the connection
*/
private volatile JdbcInterceptor handler = null;
private AtomicBoolean released = new AtomicBoolean(false);
private volatile boolean suspect = false;
private java.sql.Driver driver = null;
/**
* Constructor
*
* @param prop - pool properties
* @param parent - the parent connection pool
*/
public PooledConnection(PoolConfiguration prop, ConnectionPool parent) {
poolProperties = prop;
this.parent = parent;
connectionVersion = parent.getPoolVersion();
}
@Override
public long getConnectionVersion() {
return connectionVersion;
}
/**
* @deprecated use {@link #shouldForceReconnect(String, String)} method kept since it was public, to avoid changing
* interface.
*
* @param username The user name
* @param password The password
*
* @return <code>true</code>if the pool does not need to reconnect
*/
@Deprecated
public boolean checkUser(String username, String password) {
return !shouldForceReconnect(username, password);
}
/**
* Returns true if we must force reconnect based on credentials passed in. Returns false if
* {@link PoolConfiguration#isAlternateUsernameAllowed()} method returns false. Returns false if the
* username/password has not changed since this connection was connected
*
* @param username the username you wish to connect with, pass in null to accept the default username from
* {@link PoolConfiguration#getUsername()}
* @param password the password you wish to connect with, pass in null to accept the default username from
* {@link org.apache.tomcat.jdbc.pool.PoolConfiguration#getPassword()}
*
* @return true is the pool must reconnect
*/
public boolean shouldForceReconnect(String username, String password) {
if (!getPoolProperties().isAlternateUsernameAllowed()) {
return false;
}
if (username == null) {
username = poolProperties.getUsername();
}
if (password == null) {
password = poolProperties.getPassword();
}
String storedUsr = (String) getAttributes().get(PROP_USER);
String storedPwd = (String) getAttributes().get(PROP_PASSWORD);
boolean noChangeInCredentials = (username == null && storedUsr == null);
noChangeInCredentials = (noChangeInCredentials || (username != null && username.equals(storedUsr)));
noChangeInCredentials = noChangeInCredentials &&
((password == null && storedPwd == null) || (password != null && password.equals(storedPwd)));
if (username == null) {
getAttributes().remove(PROP_USER);
} else {
getAttributes().put(PROP_USER, username);
}
if (password == null) {
getAttributes().remove(PROP_PASSWORD);
} else {
getAttributes().put(PROP_PASSWORD, password);
}
return !noChangeInCredentials;
}
/**
* Connects the underlying connection to the database.
*
* @throws SQLException if the method {@link #release()} has been called.
* @throws SQLException if driver instantiation fails
* @throws SQLException if a call to {@link java.sql.Driver#connect(String, java.util.Properties)} fails.
* @throws SQLException if default properties are configured and a call to
* {@link java.sql.Connection#setAutoCommit(boolean)},
* {@link java.sql.Connection#setCatalog(String)},
* {@link java.sql.Connection#setTransactionIsolation(int)} or
* {@link java.sql.Connection#setReadOnly(boolean)} fails.
*/
public void connect() throws SQLException {
if (released.get()) {
throw new SQLException(MessageFactory.get("pooledConnection.cannotReestablish"));
}
if (connection != null) {
try {
this.disconnect(false);
} catch (Exception x) {
if (log.isDebugEnabled()) {
log.debug("Unable to disconnect previous connection.", x);
}
}
}
// if (poolProperties.getDataSource()==null && poolProperties.getDataSourceJNDI()!=null) {
// TODO lookup JNDI name
// }
if (poolProperties.getDataSource() != null) {
connectUsingDataSource();
} else {
connectUsingDriver();
}
// Set up the default state, unless we expect the interceptor to do it
if (!hasConnectionStateInterceptor()) {
if (poolProperties.getDefaultTransactionIsolation() != DataSourceFactory.UNKNOWN_TRANSACTIONISOLATION) {
connection.setTransactionIsolation(poolProperties.getDefaultTransactionIsolation());
}
if (poolProperties.getDefaultReadOnly() != null) {
connection.setReadOnly(poolProperties.getDefaultReadOnly().booleanValue());
}
if (poolProperties.getDefaultAutoCommit() != null) {
connection.setAutoCommit(poolProperties.getDefaultAutoCommit().booleanValue());
}
if (poolProperties.getDefaultCatalog() != null) {
connection.setCatalog(poolProperties.getDefaultCatalog());
}
}
this.discarded = false;
this.lastConnected = System.currentTimeMillis();
// The physical session is up: this is the point at which the
// connection can publish itself below the pool's MBean. Tying that to
// the first borrow instead left a connection the pool pre-created for
// its initial size, or one that is simply never borrowed, counted in
// the pool's size yet invisible to JMX for its whole life. This also
// covers every reconnect; a connection that is already registered
// keeps its registration across a reconnect, and createMBean() leaves
// such a call alone by its own guards. With JMX disabled the pool has
// no JMX object to register under, and the borrow path still calls the
// registration unconditionally, so it is skipped here rather than
// called only to do nothing.
if (parent.getJmxPool() != null) {
createMBean();
}
}
/**
* Returns <code>true</code> if the configured interceptor chain contains the {@link ConnectionState} interceptor
* or a subclass of it, which applies the configured default connection state itself every time the connection is
* borrowed.
*
* <p>The interceptor is recognized by its resolved class rather than by its name in the configuration string:
* the documented short name resolves to the same class as the fully qualified name, a subclass inherits the job
* whatever it is named, and an interceptor merely named after the state interceptor does not do its job.</p>
*
* @return <code>true</code> if a {@link ConnectionState} interceptor is configured
*/
private boolean hasConnectionStateInterceptor() {
for (PoolProperties.InterceptorDefinition definition : poolProperties.getJdbcInterceptorsAsArray()) {
try {
if (ConnectionState.class.isAssignableFrom(definition.getInterceptorClass())) {
return true;
}
} catch (ClassNotFoundException e) {
// An interceptor whose class cannot be loaded can never join the chain, so this definition cannot be
// the state interceptor; fall through to the next one.
}
}
return false;
}
/**
* Connects using the configured DataSource.
*
* @throws SQLException if unable to obtain a connection from the DataSource
*/
protected void connectUsingDataSource() throws SQLException {
String usr = null;
String pwd = null;
if (getAttributes().containsKey(PROP_USER)) {
usr = (String) getAttributes().get(PROP_USER);
} else {
usr = poolProperties.getUsername();
getAttributes().put(PROP_USER, usr);
}
if (getAttributes().containsKey(PROP_PASSWORD)) {
pwd = (String) getAttributes().get(PROP_PASSWORD);
} else {
pwd = poolProperties.getPassword();
getAttributes().put(PROP_PASSWORD, pwd);
}
if (poolProperties.getDataSource() instanceof javax.sql.XADataSource) {
javax.sql.XADataSource xds = (javax.sql.XADataSource) poolProperties.getDataSource();
javax.sql.XAConnection pooled = (usr != null && pwd != null) ? xds.getXAConnection(usr, pwd)
: xds.getXAConnection();
// Keep the handle before obtaining the logical connection: the
// physical connection is only closed when the handle is closed,
// so losing the handle here would leak the physical connection.
xaConnection = pooled;
SQLException failure = null;
RuntimeException failureRuntime = null;
try {
connection = pooled.getConnection();
} catch (SQLException e) {
failure = e;
} catch (RuntimeException e) {
failureRuntime = e;
}
if (connection == null && failure == null && failureRuntime == null) {
// Contract-violating provider: a handle without a logical
// connection is of no use either.
failure = new SQLException(
MessageFactory.get("pooledConnection.xaDataSourceNullConnection", xds));
}
if (failure != null || failureRuntime != null) {
// Do not strand the handle if the logical connection
// could not be obtained.
Exception origin = failure != null ? failure : failureRuntime;
try {
pooled.close();
} catch (SQLException | RuntimeException closeX) {
if (closeX != origin) {
origin.addSuppressed(closeX);
}
}
xaConnection = null;
connection = null;
if (failureRuntime != null) {
throw failureRuntime;
}
throw failure;
}
} else if (poolProperties.getDataSource() instanceof javax.sql.DataSource) {
javax.sql.DataSource ds = (javax.sql.DataSource) poolProperties.getDataSource();
if (usr != null && pwd != null) {
connection = ds.getConnection(usr, pwd);
} else {
connection = ds.getConnection();
}
if (connection == null) {
// The DataSource contract allows getConnection to return null
// when no connection can be provided; keeping that null would
// surface later as a misleading validation failure, or as a
// null pointer when the default connection state is applied.
throw new SQLException(MessageFactory.get("pooledConnection.dataSourceNullConnection", ds));
}
} else if (poolProperties.getDataSource() instanceof javax.sql.ConnectionPoolDataSource) {
javax.sql.ConnectionPoolDataSource ds = (javax.sql.ConnectionPoolDataSource) poolProperties.getDataSource();
javax.sql.PooledConnection pooled = (usr != null && pwd != null) ? ds.getPooledConnection(usr, pwd)
: ds.getPooledConnection();
// Keep the handle before obtaining the logical connection: the
// physical connection is only closed when the handle is closed,
// so losing the handle here would leak the physical connection.
cpdsConnection = pooled;
SQLException failure = null;
RuntimeException failureRuntime = null;
try {
connection = pooled.getConnection();
} catch (SQLException e) {
failure = e;
} catch (RuntimeException e) {
failureRuntime = e;
}
if (connection == null && failure == null && failureRuntime == null) {
// Contract-violating provider: a handle without a logical
// connection is of no use either.
failure = new SQLException(
MessageFactory.get("pooledConnection.connectionPoolDataSourceNullConnection", ds));
}
if (failure != null || failureRuntime != null) {
// Do not strand the handle if the logical connection
// could not be obtained.
Exception origin = failure != null ? failure : failureRuntime;
try {
pooled.close();
} catch (SQLException | RuntimeException closeX) {
if (closeX != origin) {
origin.addSuppressed(closeX);
}
}
cpdsConnection = null;
connection = null;
if (failureRuntime != null) {
throw failureRuntime;
}
throw failure;
}
} else {
throw new SQLException(MessageFactory.get("pooledConnection.unknownDataSource",
(poolProperties.getDataSource() != null ? poolProperties.getDataSource().getClass() : "null")));
}
}
/**
* Connects using the configured JDBC Driver class.
*
* @throws SQLException if unable to instantiate the driver or obtain a connection
*/
protected void connectUsingDriver() throws SQLException {
try {
if (driver == null) {
if (poolProperties.getDriverClassName() == null) {
// Rely on DriverManager
if (log.isDebugEnabled()) {
log.debug("No driverClassName is configured so the driver will be located by DriverManager.");
}
} else {
if (log.isDebugEnabled()) {
log.debug("Instantiating driver using class: " + poolProperties.getDriverClassName() +
" [url=" + poolProperties.getUrl() + "]");
}
driver = (java.sql.Driver) ClassLoaderUtil.loadClass(poolProperties.getDriverClassName(),
PooledConnection.class.getClassLoader(), Thread.currentThread().getContextClassLoader())
.getConstructor().newInstance();
}
}
} catch (Exception cn) {
if (log.isDebugEnabled()) {
log.debug("Unable to instantiate JDBC driver.", cn);
}
SQLException ex = new SQLException(cn.getMessage());
ex.initCause(cn);
throw ex;
}
String driverURL = poolProperties.getUrl();
String usr = null;
String pwd = null;
if (getAttributes().containsKey(PROP_USER)) {
usr = (String) getAttributes().get(PROP_USER);
} else {
usr = poolProperties.getUsername();
getAttributes().put(PROP_USER, usr);
}
if (getAttributes().containsKey(PROP_PASSWORD)) {
pwd = (String) getAttributes().get(PROP_PASSWORD);
} else {
pwd = poolProperties.getPassword();
getAttributes().put(PROP_PASSWORD, pwd);
}
Properties properties = PoolUtilities.clone(poolProperties.getDbProperties());
if (usr != null) {
properties.setProperty(PROP_USER, usr);
}
if (pwd != null) {
properties.setProperty(PROP_PASSWORD, pwd);
}
try {
if (driver == null) {
connection = DriverManager.getConnection(driverURL, properties);
} else {
connection = driver.connect(driverURL, properties);
}
} catch (Exception x) {
if (log.isDebugEnabled()) {
log.debug("Unable to connect to database.", x);
}
// Read the pool's JMX object only once: close() gives it up
// concurrently, and a second read can observe the null the first
// one did not
org.apache.tomcat.jdbc.pool.jmx.ConnectionPool jmx = parent.getJmxPool();
if (jmx != null) {
jmx.notify(org.apache.tomcat.jdbc.pool.jmx.ConnectionPool.NOTIFY_CONNECT,
ConnectionPool.getStackTrace(x));
}
if (x instanceof SQLException) {
throw (SQLException) x;
} else {
SQLException ex = new SQLException(x.getMessage());
ex.initCause(x);
throw ex;
}
}
if (connection == null) {
throw new SQLException(MessageFactory.get("pooledConnection.driverReturnedNull", driver, driverURL));
}
}
/**
* @return true if connect() was called successfully and disconnect has not yet been called
*/
@Override
public boolean isInitialized() {
return connection != null;
}
/**
* Returns true if the connection has been connected more than {@link PoolConfiguration#getMaxAge()} milliseconds.
* false otherwise.
*
* @return Returns true if the connection has been connected more than {@link PoolConfiguration#getMaxAge()}
* milliseconds. false otherwise.
*/
@Override
public boolean isMaxAgeExpired() {
if (getPoolProperties().getMaxAge() > 0) {
return (System.currentTimeMillis() - getLastConnected()) > getPoolProperties().getMaxAge();
} else {
return false;
}
}
/**
* Issues a call to {@link #disconnect(boolean)} with the argument false followed by a call to {@link #connect()}
*
* @throws SQLException if the call to {@link #connect()} fails.
*/
public void reconnect() throws SQLException {
this.disconnect(false);
this.connect();
}
/**
* Disconnects the connection. All exceptions are logged using debug level.
*
* @param finalize if set to true, a call to {@link ConnectionPool#finalize(PooledConnection)} is called.
*/
private void disconnect(boolean finalize) {
if (isDiscarded() && connection == null) {
return;
}
setDiscarded(true);
if (connection != null) {
try {
parent.disconnectEvent(this, finalize);
} catch (Throwable x) {
// The event itself isolates each link of the chain, so this
// catch only guards against a failure of the walk itself;
// an interceptor must not be able to keep the physical
// connection from being closed
log.warn(MessageFactory.get("pooledConnection.interceptorDisconnectedFailed"), x);
}
try {
if (cpdsConnection != null) {
// Closing the handle closes the physical connection
// and invalidates the logical connection; closing the
// logical connection alone would not.
cpdsConnection.close();
} else if (xaConnection == null) {
connection.close();
} else {
xaConnection.close();
}
} catch (Exception ignore) {
if (log.isDebugEnabled()) {
log.debug("Unable to close underlying SQL connection", ignore);
}
}
}
connection = null;
xaConnection = null;
cpdsConnection = null;
lastConnected = -1;
if (finalize) {
parent.finalize(this);
}
}
/**
* Returns abandon timeout in milliseconds
*
* @return abandon timeout in milliseconds
*/
public long getAbandonTimeout() {
if (poolProperties.getRemoveAbandonedTimeout() <= 0) {
return Long.MAX_VALUE;
} else {
return poolProperties.getRemoveAbandonedTimeout() * 1000L;
}
}
/**
* Returns <code>true</code> if the connection pool is configured to do validation for a certain action.
*
* @param action The validation action
*/
private boolean doValidate(int action) {
if (action == VALIDATE_BORROW && poolProperties.isTestOnBorrow()) {
return true;
} else if (action == VALIDATE_RETURN && poolProperties.isTestOnReturn()) {
return true;
} else if (action == VALIDATE_IDLE && poolProperties.isTestWhileIdle()) {
return true;
} else if (action == VALIDATE_INIT && poolProperties.isTestOnConnect()) {
return true;
} else if (action == VALIDATE_INIT && poolProperties.getInitSQL() != null) {
return true;
} else {
return false;
}
}
/**
* Returns <code>true</code> if the object is still valid. if not the pool will call the getExpiredAction() and
* follow up with one of the four expired methods
*
* @param validateAction The value
*
* @return <code>true</code> if the connection is valid
*/
public boolean validate(int validateAction) {
return validate(validateAction, null);
}
/**
* Validates a connection.
*
* @param validateAction the action used. One of {@link #VALIDATE_BORROW}, {@link #VALIDATE_IDLE},
* {@link #VALIDATE_INIT} or {@link #VALIDATE_RETURN}
* @param sql the SQL to be used during validation. If the {@link PoolConfiguration#setInitSQL(String)}
* has been called with a non null value and the action is {@link #VALIDATE_INIT} the init
* SQL will be used for validation.
*
* @return true if the connection was validated successfully. It returns true even if validation was not performed,
* such as when {@link PoolConfiguration#setValidationInterval(long)} has been called with a positive
* value.
* <p>
* false if the validation failed. The caller should close the connection if false is returned since a
* session could have been left in an unknown state during initialization. A connection that was never
* connected, or has already been disconnected, is always reported as invalid.
* <p>
* A validation that runs its built-in check against the database also ends any transaction that is open
* on the session when the check completes - a pool configured with a custom {@link Validator} is not,
* as the validator replaces the check and owns the session state it observes. The transaction ended is
* the one the check itself may have started, or one a previous borrower left open. A read-only check is
* ended with a rollback, never with a commit: a pool health check must not commit application work the
* application itself never committed. The one exception is the {@link #VALIDATE_INIT} execution of the
* configured {@link PoolConfiguration#getInitSQL() init SQL}: that query is not a check but the pool's
* own initialization of the session it has just established, so its transaction is ended with a commit
* and the initialization persists for the connection's lifetime. When the transaction cannot be ended -
* by the commit or by the rollback - the session state is unknown, so the validation is reported as
* failed and the pool takes the connection out of service.
*/
public boolean validate(int validateAction, String sql) {
if (connection == null) {
// Never connected - a wake-up placeholder that no borrower took
// - or already disconnected; there is nothing to validate and it
// cannot be handed out
return false;
}
if (this.isDiscarded()) {
return false;
}
if (!doValidate(validateAction)) {
// No validation required, no init sql and props not set
return true;
}
// Don't bother validating if already have recently enough
long now = System.currentTimeMillis();
if (validateAction != VALIDATE_INIT && poolProperties.getValidationInterval() > 0 &&
(now - this.lastValidated) < poolProperties.getValidationInterval()) {
return true;
}
if (poolProperties.getValidator() != null) {
boolean valid;
try {
valid = poolProperties.getValidator().validate(connection, validateAction);
} catch (RuntimeException x) {
// The SPI declares no checked exception, so throwing is the
// only way an implementation can report its own failure; the
// pool must treat that as a failed validation, because a
// throw escaping from here unwinds the borrow or return path
// between the idle and busy queues and strands the pooled
// connection - permanently shrinking the pool
if (getPoolProperties().getLogValidationErrors()) {
log.error(MessageFactory.get("pooledConnection.customValidationThrew",
poolProperties.getValidator()), x);
} else if (log.isDebugEnabled()) {
log.debug("Custom validation threw an exception.", x);
}
return false;
}
if (valid) {
this.lastValidated = now;
return true;
} else {
if (getPoolProperties().getLogValidationErrors()) {
log.error(MessageFactory.get("pooledConnection.customValidationFailed",
poolProperties.getValidator()));
}
return false;
}
}
String query = sql;
// The init SQL is not a health check: it is the pool deliberately
// initializing the physical session it has just established, and its
// documented purpose is to leave effects that persist for the
// connection's lifetime. The pool only ever runs the init action on a
// session that connect() established moments ago, so the transaction
// this execution opens is the pool's own initialization work, and
// nothing a borrower could have left behind on it.
boolean executingInitSql = validateAction == VALIDATE_INIT && poolProperties.getInitSQL() != null;
if (executingInitSql) {
query = poolProperties.getInitSQL();
}
if (query == null) {
query = poolProperties.getValidationQuery();
}
if (query == null) {
boolean transactionTerminated = false;
int validationQueryTimeout = poolProperties.getValidationQueryTimeout();
if (validationQueryTimeout < 0) {
validationQueryTimeout = 0;
}
try {
if (connection.isValid(validationQueryTimeout)) {
this.lastValidated = now;
// Terminate - by rollback, never by commit - any
// transaction the isValid() call opened, or that a
// previous borrower left open: a read-only health check
// must not commit application work. A session whose
// transaction cannot be terminated is in an unknown
// state, so it fails the validation and the pool takes
// the connection out of service.
transactionTerminated = silentlyRollbackTransactionIfNeeded();
return transactionTerminated;
} else {
if (getPoolProperties().getLogValidationErrors()) {
log.error(MessageFactory.get("pooledConnection.isValidReturnedFalse"));
}
return false;
}
} catch (SQLException | RuntimeException e) {
// A driver or connection wrapper can also fail isValid()
// with an unchecked exception, which likewise means the
// session cannot be vouched for; like the checked failure
// it is reported as a failed validation, because a throw
// escaping from here unwinds the borrow path between the
// idle and busy queues and strands the pooled connection -
// permanently shrinking the pool
if (getPoolProperties().getLogValidationErrors()) {
log.error(MessageFactory.get("pooledConnection.isValidFailed"), e);
} else if (e instanceof RuntimeException) {
// Unlike the checked failure this anomaly would once
// have escaped as the driver's own throw; the caller
// is handed only a generic validation SQLException that
// says nothing about it, so it is logged at warn rather
// than debug - otherwise at the default log level a
// broken driver would fail every validation with no
// trace anywhere
log.warn(MessageFactory.get("pooledConnection.isValidFailed"), e);
} else if (log.isDebugEnabled()) {
log.debug("isValid() failed.", e);
}
return false;
} finally {
if (!transactionTerminated) {
silentlyRollbackTransactionIfNeeded();
}
}
}
boolean transactionTerminated = false;
Statement stmt = null;
try {
stmt = connection.createStatement();
int validationQueryTimeout = poolProperties.getValidationQueryTimeout();
if (validationQueryTimeout > 0) {
stmt.setQueryTimeout(validationQueryTimeout);
}
stmt.execute(query);
stmt.close();
this.lastValidated = now;
if (executingInitSql) {
// Terminate the transaction the init SQL executed into by
// committing it: this is the pool's own initialization of a
// freshly established session, and its effects are meant to
// persist (Bugzilla 56586). A commit the driver refuses
// leaves the session in an unknown state, so - like a failed
// rollback - it fails the validation and the pool takes the
// connection out of service, with the finally below rolling
// the session back to a clean state before it goes.
transactionTerminated = silentlyCommitTransactionIfNeeded();
} else {
// Terminate - by rollback, never by commit - any transaction
// the validation query opened, or that a previous borrower
// left open: a read-only health check must not commit
// application work. A session whose transaction cannot be
// terminated is in an unknown state, so it fails the
// validation and the pool takes the connection out of
// service.
transactionTerminated = silentlyRollbackTransactionIfNeeded();
}
return transactionTerminated;
} catch (Exception ex) {
if (getPoolProperties().getLogValidationErrors()) {
log.error(MessageFactory.get("pooledConnection.sqlValidationError"), ex);
} else if (log.isDebugEnabled()) {
log.debug("Unable to validate object:", ex);
}
if (stmt != null) {
try {
stmt.close();
} catch (Exception ignore2) {
/* NOOP */}
}
} finally {
if (!transactionTerminated) {
silentlyRollbackTransactionIfNeeded();
}
}
return false;
}
/**
* Terminates a transaction open on the session by committing it, for the
* one case where the pending transaction is the pool's own deliberate
* initialization work: the init SQL the init-time validation has just
* executed on a freshly established session. A failure to commit -
* checked or unchecked - means the session cannot be vouched for, so it
* is reported as false and the caller treats the validation as failed;
* an unchecked failure must not escape through the caller's finally
* block, outside the catch that converts it, and strand the pooled
* connection between the idle and busy queues.
*/
private boolean silentlyCommitTransactionIfNeeded() {
try {
if (!connection.getAutoCommit()) {
connection.commit();
}
return true;
} catch (SQLException e) {
log.debug("Failed to commit transaction", e);
} catch (RuntimeException e) {
if (getPoolProperties().getLogValidationErrors()) {
log.error(MessageFactory.get("pooledConnection.commitFailed"), e);
} else {
// The unchecked variant is logged at warn rather than debug,
// like the rollback helper's: the caller surfaces it only as
// the generic validation failure, so at the default log level
// a driver that fails to commit would otherwise destroy
// connection after connection with no trace anywhere
log.warn(MessageFactory.get("pooledConnection.commitFailed"), e);
}
}
return false;
}
private boolean silentlyRollbackTransactionIfNeeded() {
try {
if (!connection.getAutoCommit()) {
connection.rollback();
}
return true;
} catch (SQLException e) {
log.debug("Failed to rollback transaction", e);
} catch (RuntimeException e) {
// A failure to end the transaction is a failure whether the
// driver reports it checked or unchecked: the caller reads the
// false as a failed validation and takes the session out of
// service, while a throw escaping from here would escape even
// through a caller's finally block, outside the catch that
// converts it, and strand the pooled connection between the
// idle and busy queues
if (getPoolProperties().getLogValidationErrors()) {
log.error(MessageFactory.get("pooledConnection.rollbackFailed"), e);
} else {
// The unchecked variant is an anomaly of the same kind as
// a throwing custom Validator, and unlike the checked
// rollback failure the pool surfaces it only as the same
// generic validation failure as any other, so it is logged
// at warn rather than debug: otherwise at the default log
// level a driver that fails to end a transaction unchecked
// would destroy connection after connection with no trace
// anywhere
log.warn(MessageFactory.get("pooledConnection.rollbackFailed"), e);
}
}
return false;
}
/**
* The time limit for how long the object can remain unused before it is released
*
* @return {@link PoolConfiguration#getMinEvictableIdleTimeMillis()}
*/
public long getReleaseTime() {
return this.poolProperties.getMinEvictableIdleTimeMillis();
}
/**
* This method is called if (Now - timeCheckedIn > getReleaseTime()) This method disconnects the connection, logs
* an error in debug mode if it happens then sets the {@link #released} flag to false. Any attempts to connect this
* cached object again will fail per {@link #connect()} The connection pool uses the atomic return value to
* decrement the pool size counter.
*
* @return true if this is the first time this method has been called. false if this method has been called before.
*/
public boolean release() {
try {
disconnect(true);
} catch (Exception x) {
if (log.isDebugEnabled()) {
log.debug("Unable to close SQL connection", x);
}
}
if (oname != null) {
JmxUtil.unregisterJmx(oname);
oname = null;
}
return released.compareAndSet(false, true);
}
/**
* The pool will set the stack trace when it is check out and checked in
*
* @param trace the stack trace for this connection
*/
public void setStackTrace(String trace) {
abandonTrace = trace;
abandonTraceElements = null;
}
/**
* The pool will set the borrow-site frames when it hands the connection out; they are formatted into a trace only
* if an abandoned-connection report reads it back. The pool writes and reads the trace under the lock of this
* connection.
*
* @param trace the borrow-site frames for this connection
*/
void setStackTraceElements(StackTraceElement[] trace) {
abandonTraceElements = trace;
abandonTrace = null;
}
/**
* Returns the stack trace from when this connection was borrowed. Can return null if no stack trace was set.
*
* @return the stack trace or null of no trace was set
*/
public String getStackTrace() {
String trace = abandonTrace;
if (trace == null) {
// Format the deferred borrow site, if one is pending, and cache
// the result: the abandon path logs the trace and notifies of it,
// and both readings should pay for a single formatting
StackTraceElement[] elements = abandonTraceElements;
if (elements != null) {
Exception x = new Exception();
x.setStackTrace(elements);
trace = ConnectionPool.getStackTrace(x);
abandonTrace = trace;
abandonTraceElements = null;
}
}
return trace;
}
/**
* Sets a timestamp on this connection. A timestamp usually means that some operation performed successfully.
*
* @param timestamp the timestamp as defined by {@link System#currentTimeMillis()}
*/
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
setSuspect(false);
}
@Override
public boolean isSuspect() {
return suspect;
}
/**
* Sets the suspect flag for this connection.
*
* @param suspect true if the connection is suspected to be invalid
*/
public void setSuspect(boolean suspect) {
this.suspect = suspect;
}
/**
* An interceptor can call this method with the value true, and the connection will be closed when it is returned to
* the pool. An {@link java.sql.Connection#abort(java.util.concurrent.Executor) abort()} on a handle to this
* connection completes the discard immediately rather than waiting for the return.
*
* @param discarded - only valid value is true
*
* @throws IllegalStateException if this method is called with the value false and the value true has already been
* set.
*/
public void setDiscarded(boolean discarded) {
if (this.discarded && !discarded) {
throw new IllegalStateException(MessageFactory.get("pooledConnection.discardedStateChange"));
}
this.discarded = discarded;
}
/**
* Set the timestamp the connection was last validated. This flag is used to keep track when we are using a
* {@link PoolConfiguration#setValidationInterval(long) validation-interval}.
*
* @param lastValidated a timestamp as defined by {@link System#currentTimeMillis()}
*/
public void setLastValidated(long lastValidated) {
this.lastValidated = lastValidated;
}
/**
* Sets the pool configuration for this connection and connection pool. Object is shared with the
* {@link ConnectionPool}
*
* @param poolProperties The pool properties
*/
public void setPoolProperties(PoolConfiguration poolProperties) {
this.poolProperties = poolProperties;
}
/**
* Return the timestamps of last pool action. Timestamps are typically set when connections are borrowed from the
* pool. It is used to keep track of {@link PoolConfiguration#setRemoveAbandonedTimeout(int) abandon-timeouts}. This
* timestamp can also be reset by the
* {@link org.apache.tomcat.jdbc.pool.interceptor.ResetAbandonedTimer#invoke(Object, java.lang.reflect.Method, Object[])}
*
* @return the timestamp of the last pool action as defined by {@link System#currentTimeMillis()}
*/
@Override
public long getTimestamp() {
return timestamp;
}
/**
* Returns the discarded flag.
*
* @return the discarded flag. If the value is true, either {@link #disconnect(boolean)} has been called or it will
* be called when the connection is returned to the pool.
*/
@Override
public boolean isDiscarded() {
return discarded;
}
/**
* Returns the timestamp of the last successful validation query execution.
*
* @return the timestamp of the last successful validation query execution as defined by
* {@link System#currentTimeMillis()}
*/
@Override
public long getLastValidated() {
return lastValidated;
}
/**
* Returns the configuration for this connection and pool
*
* @return the configuration for this connection and pool
*/
public PoolConfiguration getPoolProperties() {
return poolProperties;
}
/**
* Locks the connection only if either {@link PoolConfiguration#isPoolSweeperEnabled()} or
* {@link PoolConfiguration#getUseLock()} return true. The per connection lock ensures thread safety if multiple
* threads are performing operations on the connection. Otherwise this is a noop for performance
*/
public void lock() {
if (poolProperties.getUseLock() || this.poolProperties.isPoolSweeperEnabled()) {
// Optimized, only use a lock when there is concurrency
lock.writeLock().lock();
}
}
/**
* Unlocks the connection if, and only if, this thread currently holds the lock. The release decision is based on
* the lock state rather than on the configuration that {@link #lock()} consulted, because that configuration is
* mutable (it can be changed at runtime through JMX) and may differ between the two calls: deciding by
* configuration would either leave the lock write-locked forever (enabled at lock() time, disabled at unlock()
* time) or throw IllegalMonitorStateException (disabled at lock() time, enabled at unlock() time).
*/
public void unlock() {
if (lock.isWriteLockedByCurrentThread()) {
// Release only what this thread actually acquired
lock.writeLock().unlock();
}
}
/**
* Returns the underlying connection
*
* @return the underlying JDBC connection as it was returned from the JDBC driver
*
* @see javax.sql.PooledConnection#getConnection()
*/
public java.sql.Connection getConnection() {
return this.connection;
}
/**
* Returns the underlying XA connection
*
* @return the underlying XA connection as it was returned from the Datasource
*/
public javax.sql.XAConnection getXAConnection() {
return this.xaConnection;
}
/**
* Returns the timestamp of when the connection was last connected to the database. ie, a successful call to
* {@link java.sql.Driver#connect(String, java.util.Properties)}.
*
* @return the timestamp when this connection was created as defined by {@link System#currentTimeMillis()}
*/
@Override
public long getLastConnected() {
return lastConnected;
}
/**
* Returns the first handler in the interceptor chain
*
* @return the first interceptor for this connection
*/
public JdbcInterceptor getHandler() {
return handler;
}
/**
* Sets the interceptor chain handler for this connection.
*
* @param handler the first interceptor in the chain
*/
public void setHandler(JdbcInterceptor handler) {
try {
if (this.handler != null && this.handler != handler) {
JdbcInterceptor interceptor = this.handler;
while (interceptor != null) {
// Read the next link before the callback, as the callback
// is allowed to modify it
JdbcInterceptor next = interceptor.getNext();
try {
interceptor.reset(null, null);
} catch (Throwable t) {
// One broken interceptor must not stop the rest of the
// chain from being reset
log.warn(MessageFactory.get("pooledConnection.interceptorResetFailed",
interceptor.getClass().getName()), t);
}
interceptor = next;
}
}
} finally {
// A released connection must never keep a stale chain,
// even if resetting the old one failed
this.handler = handler;
}
}
@Override
public String toString() {
return "PooledConnection[" + (connection != null ? connection.toString() : "null") + "]";
}
/**
* Returns true if this connection has been released and wont be reused.
*
* @return true if the method {@link #release()} has been called
*/
@Override
public boolean isReleased() {
return released.get();
}
/**
* Returns the attributes map for this connection.
*
* @return the attributes map
*/
public HashMap<Object, Object> getAttributes() {
return attributes;
}
/**
* Creates and registers an MBean for this pooled connection with JMX. The pool's own MBean is the registration
* base; once close() has given it up - which a borrow can race - there is no base to register under, and the
* registration is skipped. It is skipped as well for a connection that is already registered - the pool reaches a
* live connection from the connect path, from the first-borrow path and from the sweep it runs when its own MBean
* is published - and for a released one, which would leave a registration of a dead connection behind with nothing
* left to unregister it. The check and the publish are one synchronized step because those paths can meet on the
* same connection and must never publish it twice: only one of the registrations would ever be unregistered.
*/
public synchronized void createMBean() {
if (oname != null || released.get()) {
return;
}
org.apache.tomcat.jdbc.pool.jmx.ConnectionPool jmx = parent.getJmxPool();
if (jmx == null) {
return;
}
String keyprop = ",connections=PooledConnection[" + connectionIndex.getAndIncrement() + "]";
oname = JmxUtil.registerJmx(jmx.getObjectName(), keyprop, this);
}
/**
* Returns the JMX ObjectName for this pooled connection.
*
* @return the JMX ObjectName or null if not registered
*/
public ObjectName getObjectName() {
return oname;
}
/**
* Returns the physical connection behind this pooled connection. The connection field is transiently null while a
* reconnect is in flight (and after a failed one), during which the per-connection MBean stays registered, so the
* JMX-exposed delegation methods must report that state rather than fail with a {@link NullPointerException}.
*
* @return the live physical connection
*
* @throws SQLException if this pooled connection currently has no physical connection
*/
private java.sql.Connection requireConnection() throws SQLException {
java.sql.Connection c = connection;
if (c == null) {
throw new SQLException(MessageFactory.get("pooledConnection.disconnected"));
}
return c;
}
/**
* Clears the warnings on the physical connection. A no-op while this pooled connection has no physical connection,
* as there is nothing to clear warnings on. A driver failure to clear the warnings is logged and swallowed rather
* than propagated; the operation is JMX-exposed and therefore declares no exception.
*/
@Override
public void clearWarnings() {
java.sql.Connection c = connection;
if (c == null) {
return;
}
try {
c.clearWarnings();
} catch (SQLException e) {
log.warn(MessageFactory.get("pooledConnection.unableToClearWarnings"), e);
}
}
/**
* Returns true if this pooled connection is closed: either it currently has no physical connection or that
* connection is closed.
*
* @return true if disconnected or the physical connection is closed
*
* @throws SQLException if the physical connection reports a failure
*/
@Override
public boolean isClosed() throws SQLException {
java.sql.Connection c = connection;
return c == null || c.isClosed();
}
@Override
public boolean getAutoCommit() throws SQLException {
return requireConnection().getAutoCommit();
}
@Override
public String getCatalog() throws SQLException {
return requireConnection().getCatalog();
}
@Override
public int getHoldability() throws SQLException {
return requireConnection().getHoldability();
}
@Override
public boolean isReadOnly() throws SQLException {
return requireConnection().isReadOnly();
}
@Override
public String getSchema() throws SQLException {
return requireConnection().getSchema();
}
@Override
public int getTransactionIsolation() throws SQLException {
return requireConnection().getTransactionIsolation();
}
}