ProxyConnection.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.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.XAConnection;
import org.apache.tomcat.jdbc.pool.util.MessageFactory;
/**
* A ProxyConnection object is the bottom most interceptor that wraps an object of type {@link PooledConnection}. The
* ProxyConnection intercepts, among the calls it forwards, the following methods:
* <ul>
* <li>{@link java.sql.Connection#close()} - returns the connection to the pool. May be called multiple times.</li>
* <li>{@link java.sql.Connection#abort(java.util.concurrent.Executor)} - delegates the abort to the physical
* connection and then ends the borrow: the session is taken out of service and the slot is freed. On a released
* handle it is the specification's no-op; on a handle whose session the pool discarded while borrowed it skips the
* driver and ends the borrow quietly.</li>
* <li>{@link java.lang.Object#toString()} - returns a custom string for this object</li>
* <li>{@link javax.sql.PooledConnection#getConnection()} - returns the underlying connection</li>
* <li>{@link java.lang.Object#equals(Object)} and {@link java.lang.Object#hashCode()} - answered with the identity of
* the connection handle, so that the {@code Object} contract holds even when no facade sits in front of this
* interceptor</li>
* </ul>
* By default method comparisons is done on a String reference level, unless the
* {@link PoolConfiguration#setUseEquals(boolean)} has been called with a <code>true</code> argument.
* <p>
* The connection handle the pool hands out implements {@link javax.sql.PooledConnection} (and
* {@link javax.sql.XAConnection} when the pool is backed by an {@link javax.sql.XADataSource}). The pool keeps no
* connection or statement event listeners of its own, so the four listener methods of
* {@code javax.sql.PooledConnection} - {@code addConnectionEventListener}, {@code removeConnectionEventListener},
* {@code addStatementEventListener} and {@code removeStatementEventListener} - are answered by the XA-family
* dispatch in {@link #invoke(Object, Method, Object[])}: where the handle is itself an {@code XAConnection} they
* reach the XA connection the driver created, whose implementation decides what registering them means; where it
* is not, the call has no dispatch target and each of the four fails with an {@link SQLException} naming the
* method. The refusal also stands on a pool backed by a {@link javax.sql.ConnectionPoolDataSource}: the pool does
* hold the driver's own pooled connection there, but it holds it for its own session management and does not
* route these calls to it. A handle whose session the pool discarded while it was still borrowed, or which its
* borrower has already closed, answers instead with the closed-connection failure the handle gives in those
* states. Either failure reaches a caller of the interface - the interface declares no checked exception - as an
* {@link java.lang.reflect.UndeclaredThrowableException} carrying it. That refusal is deliberate rather than
* unfinished work. The specification has the connection pool manager register through these methods, to be told
* to deactivate the logical handle and reuse or retire the physical session - decisions this pool makes and
* carries out itself: {@code close()} on the handle recycles a session that stays open and owned by the pool, and
* the next borrower is handed a new handle, so this handle is not the long-lived producer object whose listener
* list the specification describes; and where the pool does not observe statements - as it does not by default -
* a {@code statementClosed} event has no point from which it could be delivered. Accepting a registration it
* could not notify faithfully would only hide from the caller that no notification will ever arrive.
*/
public class ProxyConnection extends JdbcInterceptor {
/** The underlying pooled connection. */
protected PooledConnection connection = null;
/** The parent connection pool. */
protected ConnectionPool pool = null;
/**
* Returns the underlying pooled connection.
*
* @return the pooled connection
*/
public PooledConnection getConnection() {
return connection;
}
/**
* Sets the underlying pooled connection.
*
* @param connection the pooled connection
*/
public void setConnection(PooledConnection connection) {
this.connection = connection;
}
/**
* Returns the parent connection pool.
*
* @return the connection pool
*/
public ConnectionPool getPool() {
return pool;
}
/**
* Sets the parent connection pool.
*
* @param pool the connection pool
*/
public void setPool(ConnectionPool pool) {
this.pool = pool;
}
/**
* Creates a new ProxyConnection wrapping the given pooled connection.
*
* @param parent the parent connection pool
* @param con the pooled connection
* @param useEquals whether to use equals for method comparison
*/
protected ProxyConnection(ConnectionPool parent, PooledConnection con, boolean useEquals) {
pool = parent;
connection = con;
setUseEquals(useEquals);
}
@Override
public void reset(ConnectionPool parent, PooledConnection con) {
this.pool = parent;
this.connection = con;
}
/**
* Checks whether this interceptor wraps the given interface. This is the view from the interceptor itself; the
* contract of the connection handle the pool hands out - which also implements {@link java.sql.Connection} and
* {@link javax.sql.PooledConnection} (and {@link javax.sql.XAConnection} on XA pools) - is answered by the dispatch
* in {@link #invoke(Object, Method, Object[])}.
*
* @param iface the interface to check
*
* @return true if this interceptor wraps the given interface
*/
public boolean isWrapperFor(Class<?> iface) {
if (iface == PooledConnection.class) {
// The Wrapper answer must agree with what unwrap() hands out:
// for this class unwrap() returns the pooled connection this
// interceptor sits on, so the honest answer is whether one is
// still held. That object is pool-internal - see unwrap().
return connection != null;
}
if (iface == XAConnection.class && connection.getXAConnection() != null) {
return true;
}
Connection physical = connection.getConnection();
if (physical == null) {
// Released handle: there is nothing left below to be a wrapper
return false;
}
if (iface.isInstance(physical)) {
return true;
}
if (iface == XAConnection.class) {
// javax.sql.XAConnection is only ever handed out from the pooled
// connection's own XA handle - unwrap() says so - so the driver
// below is not asked about it
return false;
}
// The driver's own connection may itself wrap a further connection -
// a driver built around another driver's, like the AWS wrapper over
// the PostgreSQL one - and it, not the class of the object it
// presents, is what knows which interfaces it can unwrap to.
try {
return physical.isWrapperFor(iface);
} catch (SQLException x) {
// A driver that cannot answer the question is not a wrapper
// for the interface
return false;
}
}
/**
* Unwraps the connection to the given interface.
*
* @param iface the interface to unwrap to
*
* @return the unwrapped connection. For {@link PooledConnection} this is
* the pool's own pooled connection - a pool-internal object meant
* for trusted callers such as interceptors, not for application
* code, as it can close and discard the connection behind the
* pool's own accounting.
*
* @throws SQLException if the connection does not wrap the given interface
*/
public Object unwrap(Class<?> iface) throws SQLException {
if (iface == PooledConnection.class) {
if (connection == null) {
// The same not-a-wrapper failure the XA branch below gives
// for a missing XA connection: with none held there is
// nothing to hand out, and the silent null would contradict
// what isWrapperFor() says and turn into an unrelated
// NullPointerException at the caller's first use of the
// "unwrapped" value.
throw new SQLException(MessageFactory.get("proxyConnection.notAWrapper", iface.getName()));
}
return connection;
} else if (iface == XAConnection.class) {
javax.sql.XAConnection xa = connection.getXAConnection();
if (xa == null) {
// The Wrapper contract asks for a failure, not for a silent
// null that turns into an unrelated NullPointerException at
// the caller's first use of the "unwrapped" value.
throw new SQLException(MessageFactory.get("proxyConnection.notAWrapper", iface.getName()));
}
return xa;
} else if (iface.isInstance(connection.getConnection())) {
return connection.getConnection();
} else if (isWrapperFor(iface)) {
// The driver's connection is a wrapper further down, and
// unwrapping that far is its answer to give, not ours
return connection.getConnection().unwrap(iface);
} else {
throw new SQLException(MessageFactory.get("proxyConnection.notAWrapper", iface.getName()));
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (compare(ISCLOSED_VAL, method)) {
return Boolean.valueOf(isClosed());
}
if (compare(ISVALID_VAL, method) && isClosed()) {
// isValid() is the probe for liveness, and the JDBC contract
// answers a closed connection with false rather than with an
// exception. A session the pool discarded while its borrower
// still held the handle - an abandon, a forced pool close, an
// interceptor marking it discarded - does not seal the
// disposable facade, so without this branch the probe would
// reach the closed guard below and throw. The guard answers
// false, as the sealed facade does; a live handle falls through.
return Boolean.FALSE;
}
if (compare(EQUALS_VAL, method)) {
// With the DisposableConnectionFacade switched off, Object methods
// reach this interceptor, and the XA branch below would dispatch
// them at a null XA connection (or at the XA connection of another
// borrower) instead of answering them. Object.equals must answer
// false rather than throw for a null or foreign argument, and it
// must stay answerable on a released handle, so the identity of
// the handle itself is what compares - mirroring the semantics of
// the facade when it is enabled.
Object other = (args == null || args.length == 0) ? null : args[0];
return Boolean.valueOf(proxy == other);
}
if (compare(HASHCODE_VAL, method)) {
return Integer.valueOf(System.identityHashCode(proxy));
}
if (compare(CLOSE_VAL, method)) {
if (connection == null) {
return null; // No-op for already closed.
}
PooledConnection poolc = this.connection;
this.connection = null;
pool.returnConnection(poolc);
return null;
} else if (compare(ABORT_VAL, method)) {
// java.sql.Connection.abort(Executor). The specification has the
// call mark the connection closed, close the physical session and
// release its resources, and it makes the call on a closed
// connection a no-op. A handle its borrower has already released
// is answered by the disposable facade, which disposes of abort
// as it does of close and then answers it quietly; the guard here
// covers the same state reached without a facade. The pool and
// session fields are read once, into locals, for the whole branch:
// a concurrent disposal of the session - which a destroy or a
// factory close can trigger at any moment, from the finalizer
// thread - nulls them, and reading them again after the guard
// would either fail the branch with a null pointer or silently
// skip the return that frees the slot the abort is there to free.
ConnectionPool poolRef = this.pool;
PooledConnection poolc = this.connection;
if (poolc == null || poolRef == null) {
return null;
}
// The borrow ends here, ahead of the driver call, which the
// specification allows to run long: from now on every use of this
// handle fails the way it fails on a closed connection, and a
// concurrent close of the same handle finds it already ended.
this.connection = null;
try {
if (!poolc.isDiscarded()) {
// A session the pool marked to be taken out of service on
// its return while this handle was borrowed makes the
// handle report itself closed, and the no-op the
// specification prescribes for a closed connection asks
// nothing of the driver.
Connection physical = poolc.getConnection();
if (physical != null) {
// The driver's own answer - including the failure the
// specification prescribes for a null executor, and a
// refusal - reaches the caller.
method.invoke(physical, args);
}
}
} catch (Throwable t) {
if (t instanceof InvocationTargetException) {
throw t.getCause() != null ? t.getCause() : t;
} else {
throw t;
}
} finally {
// Whatever the driver made of the call, the session cannot go
// back to the pool: the application holding this handle now
// believes the specification, which says the connection is
// closed after abort, and will not return it - leaving it in
// busy would strand one maxActive slot per abort (bug 65929) -
// and where the driver ran the closing work asynchronously the
// session may die at any moment, so no other borrower may
// receive it either. Marking it discarded first, the return
// takes it out of service whatever the driver's isClosed()
// says, the same terminal step the pool takes on a connection
// whose state it cannot trust; it frees the slot and wakes the
// threads waiting for one. For a session already marked
// discarded it is that step run early: this handle is now
// disposed, so the close that was to complete the discard
// will not come.
poolc.setDiscarded(true);
poolRef.returnConnection(poolc);
}
return null;
} else if (compare(TOSTRING_VAL, method)) {
return this.toString();
} else if (compare(GETCONNECTION_VAL, method) && connection != null) {
return connection.getConnection();
} else if (method.getDeclaringClass() != Object.class &&
method.getDeclaringClass().isAssignableFrom(XAConnection.class) && connection != null) {
// The Object.class exclusion is defensive: the only Object
// methods java.lang.reflect.Proxy routes to an invocation
// handler are equals, hashCode and toString, and all three are
// answered above. Any other method the branch condition matches
// can only be answered by the XA connection wrapper - and that
// wrapper exists only when the pool is backed by an XA data
// source (and only while the physical connection is still
// connected). The condition also matches methods declared by
// the interfaces XAConnection extends, javax.sql.PooledConnection
// among them, so on a plain pool calls such as
// addConnectionEventListener reach this branch without a target;
// dispatching them at null would surface a raw
// NullPointerException to the application.
javax.sql.XAConnection xaConnection = connection.getXAConnection();
if (xaConnection == null) {
if (isClosed()) {
// The handle was disconnected while borrowed; report it
// the way every other method on this class does, rather
// than blaming the XA configuration.
throw new SQLException(MessageFactory.get("proxyConnection.connectionClosed"));
}
throw new SQLException(MessageFactory.get("proxyConnection.notXAConnection", method.getName()));
}
try {
return method.invoke(xaConnection, args);
} catch (Throwable t) {
if (t instanceof InvocationTargetException) {
throw t.getCause() != null ? t.getCause() : t;
} else {
throw t;
}
}
}
if (isClosed()) {
throw new SQLException(MessageFactory.get("proxyConnection.connectionClosed"));
}
if (compare(UNWRAP_VAL, method)) {
Class<?> iface = (Class<?>) args[0];
if (iface != XAConnection.class && iface.isInstance(proxy) &&
!iface.isInstance(connection.getConnection())) {
// The Wrapper contract: the handle is a wrapper of every
// interface it implements itself. Interfaces the physical
// connection behind it also provides - java.sql.Connection
// first of all - keep resolving to the physical connection,
// as they always have; javax.sql.XAConnection is excluded
// because unwrapping to it has always yielded the XA
// connection object where one exists, and the unwrap above
// reports the not-a-wrapper failure where none does.
return proxy;
}
return unwrap(iface);
} else if (compare(ISWRAPPERFOR_VAL, method)) {
Class<?> iface = (Class<?>) args[0];
// An object is always a wrapper of the interfaces it implements.
return Boolean.valueOf(iface.isInstance(proxy) || this.isWrapperFor(iface));
}
try {
PooledConnection poolc = connection;
if (poolc != null) {
return method.invoke(poolc.getConnection(), args);
} else {
throw new SQLException(MessageFactory.get("proxyConnection.connectionClosed"));
}
} catch (Throwable t) {
if (t instanceof InvocationTargetException) {
throw t.getCause() != null ? t.getCause() : t;
} else {
throw t;
}
}
}
/**
* Returns true if the underlying connection has been closed or discarded.
*
* @return true if closed or discarded
*/
public boolean isClosed() {
return connection == null || connection.isDiscarded();
}
/**
* Returns the delegate pooled connection.
*
* @return the delegate pooled connection
*/
public PooledConnection getDelegateConnection() {
return connection;
}
/**
* Returns the parent connection pool.
*
* @return the parent connection pool
*/
public ConnectionPool getParentPool() {
return pool;
}
@Override
public String toString() {
return "ProxyConnection[" + (connection != null ? connection.toString() : "null") + "]";
}
}