init
This commit is contained in:
348
java/org/apache/tomcat/dbcp/dbcp2/PoolableConnection.java
Normal file
348
java/org/apache/tomcat/dbcp/dbcp2/PoolableConnection.java
Normal file
@@ -0,0 +1,348 @@
|
||||
/*
|
||||
* 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.dbcp.dbcp2;
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Collection;
|
||||
|
||||
import javax.management.InstanceAlreadyExistsException;
|
||||
import javax.management.MBeanRegistrationException;
|
||||
import javax.management.MBeanServer;
|
||||
import javax.management.NotCompliantMBeanException;
|
||||
import javax.management.ObjectName;
|
||||
|
||||
import org.apache.tomcat.dbcp.pool2.ObjectPool;
|
||||
|
||||
/**
|
||||
* A delegating connection that, rather than closing the underlying connection, returns itself to an {@link ObjectPool}
|
||||
* when closed.
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
public class PoolableConnection extends DelegatingConnection<Connection> implements PoolableConnectionMXBean {
|
||||
|
||||
private static MBeanServer MBEAN_SERVER;
|
||||
|
||||
static {
|
||||
try {
|
||||
MBEAN_SERVER = ManagementFactory.getPlatformMBeanServer();
|
||||
} catch (NoClassDefFoundError | Exception ex) {
|
||||
// ignore - JMX not available
|
||||
}
|
||||
}
|
||||
|
||||
/** The pool to which I should return. */
|
||||
private final ObjectPool<PoolableConnection> pool;
|
||||
|
||||
private final ObjectNameWrapper jmxObjectName;
|
||||
|
||||
// Use a prepared statement for validation, retaining the last used SQL to
|
||||
// check if the validation query has changed.
|
||||
private PreparedStatement validationPreparedStatement;
|
||||
private String lastValidationSql;
|
||||
|
||||
/**
|
||||
* Indicate that unrecoverable SQLException was thrown when using this connection. Such a connection should be
|
||||
* considered broken and not pass validation in the future.
|
||||
*/
|
||||
private boolean fatalSqlExceptionThrown = false;
|
||||
|
||||
/**
|
||||
* SQL_STATE codes considered to signal fatal conditions. Overrides the defaults in
|
||||
* {@link Utils#DISCONNECTION_SQL_CODES} (plus anything starting with {@link Utils#DISCONNECTION_SQL_CODE_PREFIX}).
|
||||
*/
|
||||
private final Collection<String> disconnectionSqlCodes;
|
||||
|
||||
/** Whether or not to fast fail validation after fatal connection errors */
|
||||
private final boolean fastFailValidation;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param conn
|
||||
* my underlying connection
|
||||
* @param pool
|
||||
* the pool to which I should return when closed
|
||||
* @param jmxObjectName
|
||||
* JMX name
|
||||
* @param disconnectSqlCodes
|
||||
* SQL_STATE codes considered fatal disconnection errors
|
||||
* @param fastFailValidation
|
||||
* true means fatal disconnection errors cause subsequent validations to fail immediately (no attempt to
|
||||
* run query or isValid)
|
||||
*/
|
||||
public PoolableConnection(final Connection conn, final ObjectPool<PoolableConnection> pool,
|
||||
final ObjectName jmxObjectName, final Collection<String> disconnectSqlCodes,
|
||||
final boolean fastFailValidation) {
|
||||
super(conn);
|
||||
this.pool = pool;
|
||||
this.jmxObjectName = ObjectNameWrapper.wrap(jmxObjectName);
|
||||
this.disconnectionSqlCodes = disconnectSqlCodes;
|
||||
this.fastFailValidation = fastFailValidation;
|
||||
|
||||
if (jmxObjectName != null) {
|
||||
try {
|
||||
MBEAN_SERVER.registerMBean(this, jmxObjectName);
|
||||
} catch (InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException e) {
|
||||
// For now, simply skip registration
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param conn
|
||||
* my underlying connection
|
||||
* @param pool
|
||||
* the pool to which I should return when closed
|
||||
* @param jmxName
|
||||
* JMX name
|
||||
*/
|
||||
public PoolableConnection(final Connection conn, final ObjectPool<PoolableConnection> pool,
|
||||
final ObjectName jmxName) {
|
||||
this(conn, pool, jmxName, null, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void passivate() throws SQLException {
|
||||
super.passivate();
|
||||
setClosedInternal(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>
|
||||
* This method should not be used by a client to determine whether or not a connection should be return to the
|
||||
* connection pool (by calling {@link #close()}). Clients should always attempt to return a connection to the pool
|
||||
* once it is no longer required.
|
||||
*/
|
||||
@Override
|
||||
public boolean isClosed() throws SQLException {
|
||||
if (isClosedInternal()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (getDelegateInternal().isClosed()) {
|
||||
// Something has gone wrong. The underlying connection has been
|
||||
// closed without the connection being returned to the pool. Return
|
||||
// it now.
|
||||
close();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns me to my pool.
|
||||
*/
|
||||
@Override
|
||||
public synchronized void close() throws SQLException {
|
||||
if (isClosedInternal()) {
|
||||
// already closed
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isUnderlyingConnectionClosed;
|
||||
try {
|
||||
isUnderlyingConnectionClosed = getDelegateInternal().isClosed();
|
||||
} catch (final SQLException e) {
|
||||
try {
|
||||
pool.invalidateObject(this);
|
||||
} catch (final IllegalStateException ise) {
|
||||
// pool is closed, so close the connection
|
||||
passivate();
|
||||
getInnermostDelegate().close();
|
||||
} catch (final Exception ie) {
|
||||
// DO NOTHING the original exception will be rethrown
|
||||
}
|
||||
throw new SQLException("Cannot close connection (isClosed check failed)", e);
|
||||
}
|
||||
|
||||
/*
|
||||
* Can't set close before this code block since the connection needs to be open when validation runs. Can't set
|
||||
* close after this code block since by then the connection will have been returned to the pool and may have
|
||||
* been borrowed by another thread. Therefore, the close flag is set in passivate().
|
||||
*/
|
||||
if (isUnderlyingConnectionClosed) {
|
||||
// Abnormal close: underlying connection closed unexpectedly, so we
|
||||
// must destroy this proxy
|
||||
try {
|
||||
pool.invalidateObject(this);
|
||||
} catch (final IllegalStateException e) {
|
||||
// pool is closed, so close the connection
|
||||
passivate();
|
||||
getInnermostDelegate().close();
|
||||
} catch (final Exception e) {
|
||||
throw new SQLException("Cannot close connection (invalidating pooled object failed)", e);
|
||||
}
|
||||
} else {
|
||||
// Normal close: underlying connection is still open, so we
|
||||
// simply need to return this proxy to the pool
|
||||
try {
|
||||
pool.returnObject(this);
|
||||
} catch (final IllegalStateException e) {
|
||||
// pool is closed, so close the connection
|
||||
passivate();
|
||||
getInnermostDelegate().close();
|
||||
} catch (final SQLException e) {
|
||||
throw e;
|
||||
} catch (final RuntimeException e) {
|
||||
throw e;
|
||||
} catch (final Exception e) {
|
||||
throw new SQLException("Cannot close connection (return to pool failed)", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually close my underlying {@link Connection}.
|
||||
*/
|
||||
@Override
|
||||
public void reallyClose() throws SQLException {
|
||||
if (jmxObjectName != null) {
|
||||
jmxObjectName.unregisterMBean();
|
||||
}
|
||||
|
||||
if (validationPreparedStatement != null) {
|
||||
try {
|
||||
validationPreparedStatement.close();
|
||||
} catch (final SQLException sqle) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
super.closeInternal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Expose the {@link #toString()} method via a bean getter so it can be read as a property via JMX.
|
||||
*/
|
||||
@Override
|
||||
public String getToString() {
|
||||
return toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the connection, using the following algorithm:
|
||||
* <ol>
|
||||
* <li>If {@code fastFailValidation} (constructor argument) is {@code true} and this connection has previously
|
||||
* thrown a fatal disconnection exception, a {@code SQLException} is thrown.</li>
|
||||
* <li>If {@code sql} is null, the driver's #{@link Connection#isValid(int) isValid(timeout)} is called. If it
|
||||
* returns {@code false}, {@code SQLException} is thrown; otherwise, this method returns successfully.</li>
|
||||
* <li>If {@code sql} is not null, it is executed as a query and if the resulting {@code ResultSet} contains at
|
||||
* least one row, this method returns successfully. If not, {@code SQLException} is thrown.</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param sql
|
||||
* The validation SQL query.
|
||||
* @param timeoutSeconds
|
||||
* The validation timeout in seconds.
|
||||
* @throws SQLException
|
||||
* Thrown when validation fails or an SQLException occurs during validation
|
||||
*/
|
||||
public void validate(final String sql, int timeoutSeconds) throws SQLException {
|
||||
if (fastFailValidation && fatalSqlExceptionThrown) {
|
||||
throw new SQLException(Utils.getMessage("poolableConnection.validate.fastFail"));
|
||||
}
|
||||
|
||||
if (sql == null || sql.length() == 0) {
|
||||
if (timeoutSeconds < 0) {
|
||||
timeoutSeconds = 0;
|
||||
}
|
||||
if (!isValid(timeoutSeconds)) {
|
||||
throw new SQLException("isValid() returned false");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sql.equals(lastValidationSql)) {
|
||||
lastValidationSql = sql;
|
||||
// Has to be the innermost delegate else the prepared statement will
|
||||
// be closed when the pooled connection is passivated.
|
||||
validationPreparedStatement = getInnermostDelegateInternal().prepareStatement(sql);
|
||||
}
|
||||
|
||||
if (timeoutSeconds > 0) {
|
||||
validationPreparedStatement.setQueryTimeout(timeoutSeconds);
|
||||
}
|
||||
|
||||
try (ResultSet rs = validationPreparedStatement.executeQuery()) {
|
||||
if (!rs.next()) {
|
||||
throw new SQLException("validationQuery didn't return a row");
|
||||
}
|
||||
} catch (final SQLException sqle) {
|
||||
throw sqle;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the SQLState of the input exception and any nested SQLExceptions it wraps.
|
||||
* <p>
|
||||
* If {@link #disconnectionSqlCodes} has been set, sql states are compared to those in the
|
||||
* configured list of fatal exception codes. If this property is not set, codes are compared against the default
|
||||
* codes in {@link Utils#DISCONNECTION_SQL_CODES} and in this case anything starting with #{link
|
||||
* Utils.DISCONNECTION_SQL_CODE_PREFIX} is considered a disconnection.
|
||||
* </p>
|
||||
*
|
||||
* @param e
|
||||
* SQLException to be examined
|
||||
* @return true if the exception signals a disconnection
|
||||
*/
|
||||
private boolean isDisconnectionSqlException(final SQLException e) {
|
||||
boolean fatalException = false;
|
||||
final String sqlState = e.getSQLState();
|
||||
if (sqlState != null) {
|
||||
fatalException = disconnectionSqlCodes == null
|
||||
? sqlState.startsWith(Utils.DISCONNECTION_SQL_CODE_PREFIX)
|
||||
|| Utils.DISCONNECTION_SQL_CODES.contains(sqlState)
|
||||
: disconnectionSqlCodes.contains(sqlState);
|
||||
if (!fatalException) {
|
||||
final SQLException nextException = e.getNextException();
|
||||
if (nextException != null && nextException != e) {
|
||||
fatalException = isDisconnectionSqlException(e.getNextException());
|
||||
}
|
||||
}
|
||||
}
|
||||
return fatalException;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleException(final SQLException e) throws SQLException {
|
||||
fatalSqlExceptionThrown |= isDisconnectionSqlException(e);
|
||||
super.handleException(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The disconnection SQL codes.
|
||||
* @since 2.6.0
|
||||
*/
|
||||
public Collection<String> getDisconnectionSqlCodes() {
|
||||
return disconnectionSqlCodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether to fail-fast.
|
||||
* @since 2.6.0
|
||||
*/
|
||||
public boolean isFastFailValidation() {
|
||||
return fastFailValidation;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user