PoolableConnection.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.dbcp2;

  18. import java.lang.management.ManagementFactory;
  19. import java.sql.Connection;
  20. import java.sql.PreparedStatement;
  21. import java.sql.ResultSet;
  22. import java.sql.SQLException;
  23. import java.time.Duration;
  24. import java.util.Collection;
  25. import java.util.concurrent.Executor;
  26. import java.util.concurrent.locks.Lock;
  27. import java.util.concurrent.locks.ReentrantLock;

  28. import javax.management.InstanceAlreadyExistsException;
  29. import javax.management.MBeanRegistrationException;
  30. import javax.management.MBeanServer;
  31. import javax.management.NotCompliantMBeanException;
  32. import javax.management.ObjectName;

  33. import org.apache.commons.pool2.ObjectPool;
  34. import org.apache.commons.pool2.impl.GenericObjectPool;

  35. /**
  36.  * A delegating connection that, rather than closing the underlying connection, returns itself to an {@link ObjectPool}
  37.  * when closed.
  38.  *
  39.  * @since 2.0
  40.  */
  41. public class PoolableConnection extends DelegatingConnection<Connection> implements PoolableConnectionMXBean {

  42.     private static MBeanServer MBEAN_SERVER;

  43.     static {
  44.         try {
  45.             MBEAN_SERVER = ManagementFactory.getPlatformMBeanServer();
  46.         } catch (final NoClassDefFoundError | Exception ignored) {
  47.             // ignore - JMX not available
  48.         }
  49.     }

  50.     /** The pool to which I should return. */
  51.     private final ObjectPool<PoolableConnection> pool;

  52.     private final ObjectNameWrapper jmxObjectName;

  53.     // Use a prepared statement for validation, retaining the last used SQL to
  54.     // check if the validation query has changed.
  55.     private PreparedStatement validationPreparedStatement;
  56.     private String lastValidationSql;

  57.     /**
  58.      * Indicate that unrecoverable SQLException was thrown when using this connection. Such a connection should be
  59.      * considered broken and not pass validation in the future.
  60.      */
  61.     private boolean fatalSqlExceptionThrown;

  62.     /**
  63.      * SQL State codes considered to signal fatal conditions. Overrides the defaults in
  64.      * {@link Utils#getDisconnectionSqlCodes()} (plus anything starting with {@link Utils#DISCONNECTION_SQL_CODE_PREFIX}).
  65.      */
  66.     private final Collection<String> disconnectionSqlCodes;

  67.     /**
  68.      * A collection of SQL State codes that are not considered fatal disconnection codes.
  69.      *
  70.      * @since 2.13.0
  71.      */
  72.     private final Collection<String> disconnectionIgnoreSqlCodes;


  73.     /** Whether or not to fast fail validation after fatal connection errors */
  74.     private final boolean fastFailValidation;

  75.     private final Lock lock = new ReentrantLock();

  76.     /**
  77.      *
  78.      * @param conn
  79.      *            my underlying connection
  80.      * @param pool
  81.      *            the pool to which I should return when closed
  82.      * @param jmxName
  83.      *            JMX name
  84.      */
  85.     public PoolableConnection(final Connection conn, final ObjectPool<PoolableConnection> pool,
  86.             final ObjectName jmxName) {
  87.         this(conn, pool, jmxName, null, true);
  88.     }

  89.     /**
  90.      *
  91.      * @param conn
  92.      *            my underlying connection
  93.      * @param pool
  94.      *            the pool to which I should return when closed
  95.      * @param jmxObjectName
  96.      *            JMX name
  97.      * @param disconnectSqlCodes
  98.      *            SQL State codes considered fatal disconnection errors
  99.      * @param fastFailValidation
  100.      *            true means fatal disconnection errors cause subsequent validations to fail immediately (no attempt to
  101.      *            run query or isValid)
  102.      */
  103.     public PoolableConnection(final Connection conn, final ObjectPool<PoolableConnection> pool,
  104.                               final ObjectName jmxObjectName, final Collection<String> disconnectSqlCodes,
  105.                               final boolean fastFailValidation) {
  106.         this(conn, pool, jmxObjectName, disconnectSqlCodes, null, fastFailValidation);
  107.     }

  108.     /**
  109.      * Creates a new {@link PoolableConnection} instance.
  110.      *
  111.      * @param conn
  112.      *            my underlying connection
  113.      * @param pool
  114.      *            the pool to which I should return when closed
  115.      * @param jmxObjectName
  116.      *            JMX name
  117.      * @param disconnectSqlCodes
  118.      *            SQL State codes considered fatal disconnection errors
  119.      * @param disconnectionIgnoreSqlCodes
  120.      *            SQL State codes that should be ignored when determining fatal disconnection errors
  121.      * @param fastFailValidation
  122.      *            true means fatal disconnection errors cause subsequent validations to fail immediately (no attempt to
  123.      *            run query or isValid)
  124.      * @since 2.13.0
  125.      */
  126.     public PoolableConnection(final Connection conn, final ObjectPool<PoolableConnection> pool,
  127.             final ObjectName jmxObjectName, final Collection<String> disconnectSqlCodes,
  128.             final Collection<String> disconnectionIgnoreSqlCodes, final boolean fastFailValidation) {
  129.         super(conn);
  130.         this.pool = pool;
  131.         this.jmxObjectName = ObjectNameWrapper.wrap(jmxObjectName);
  132.         this.disconnectionSqlCodes = disconnectSqlCodes;
  133.         this.disconnectionIgnoreSqlCodes = disconnectionIgnoreSqlCodes;
  134.         this.fastFailValidation = fastFailValidation;

  135.         if (jmxObjectName != null) {
  136.             try {
  137.                 MBEAN_SERVER.registerMBean(this, jmxObjectName);
  138.             } catch (InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException ignored) {
  139.                 // For now, simply skip registration
  140.             }
  141.         }
  142.     }

  143.     /**
  144.      * Abort my underlying {@link Connection}.
  145.      *
  146.      * @since 2.9.0
  147.      */
  148.     @Override
  149.     public void abort(final Executor executor) throws SQLException {
  150.         if (jmxObjectName != null) {
  151.             jmxObjectName.unregisterMBean();
  152.         }
  153.         super.abort(executor);
  154.     }

  155.     /**
  156.      * Returns this instance to my containing pool.
  157.      */
  158.     @Override
  159.     public void close() throws SQLException {
  160.         lock.lock();
  161.         try {
  162.             if (isClosedInternal()) {
  163.                 // already closed
  164.                 return;
  165.             }

  166.             boolean isUnderlyingConnectionClosed;
  167.             try {
  168.                 isUnderlyingConnectionClosed = getDelegateInternal().isClosed();
  169.             } catch (final SQLException e) {
  170.                 try {
  171.                     pool.invalidateObject(this);
  172.                 } catch (final IllegalStateException ise) {
  173.                     // pool is closed, so close the connection
  174.                     passivate();
  175.                     getInnermostDelegate().close();
  176.                 } catch (final Exception ignored) {
  177.                     // DO NOTHING the original exception will be rethrown
  178.                 }
  179.                 throw new SQLException("Cannot close connection (isClosed check failed)", e);
  180.             }

  181.             /*
  182.              * Can't set close before this code block since the connection needs to be open when validation runs. Can't set
  183.              * close after this code block since by then the connection will have been returned to the pool and may have
  184.              * been borrowed by another thread. Therefore, the close flag is set in passivate().
  185.              */
  186.             if (isUnderlyingConnectionClosed) {
  187.                 // Abnormal close: underlying connection closed unexpectedly, so we
  188.                 // must destroy this proxy
  189.                 try {
  190.                     pool.invalidateObject(this);
  191.                 } catch (final IllegalStateException e) {
  192.                     // pool is closed, so close the connection
  193.                     passivate();
  194.                     getInnermostDelegate().close();
  195.                 } catch (final Exception e) {
  196.                     throw new SQLException("Cannot close connection (invalidating pooled object failed)", e);
  197.                 }
  198.             } else {
  199.                 // Normal close: underlying connection is still open, so we
  200.                 // simply need to return this proxy to the pool
  201.                 try {
  202.                     pool.returnObject(this);
  203.                 } catch (final IllegalStateException e) {
  204.                     // pool is closed, so close the connection
  205.                     passivate();
  206.                     getInnermostDelegate().close();
  207.                 } catch (final SQLException | RuntimeException e) {
  208.                     throw e;
  209.                 } catch (final Exception e) {
  210.                     throw new SQLException("Cannot close connection (return to pool failed)", e);
  211.                 }
  212.             }
  213.         } finally {
  214.             lock.unlock();
  215.         }
  216.     }

  217.     /**
  218.      * @return The disconnection SQL codes.
  219.      * @since 2.6.0
  220.      */
  221.     public Collection<String> getDisconnectionSqlCodes() {
  222.         return disconnectionSqlCodes;
  223.     }

  224.     /**
  225.      * Gets the value of the {@link #toString()} method via a bean getter, so it can be read as a property via JMX.
  226.      */
  227.     @Override
  228.     public String getToString() {
  229.         return toString();
  230.     }

  231.     @Override
  232.     protected void handleException(final SQLException e) throws SQLException {
  233.         fatalSqlExceptionThrown |= isFatalException(e);
  234.         super.handleException(e);
  235.     }

  236.     /**
  237.      * {@inheritDoc}
  238.      * <p>
  239.      * This method should not be used by a client to determine whether or not a connection should be return to the
  240.      * connection pool (by calling {@link #close()}). Clients should always attempt to return a connection to the pool
  241.      * once it is no longer required.
  242.      */
  243.     @Override
  244.     public boolean isClosed() throws SQLException {
  245.         if (isClosedInternal()) {
  246.             return true;
  247.         }

  248.         if (getDelegateInternal().isClosed()) {
  249.             // Something has gone wrong. The underlying connection has been
  250.             // closed without the connection being returned to the pool. Return
  251.             // it now.
  252.             close();
  253.             return true;
  254.         }

  255.         return false;
  256.     }

  257.     /**
  258.      * Checks the SQLState of the input exception.
  259.      * <p>
  260.      * If {@link #disconnectionSqlCodes} has been set, sql states are compared to those in the configured list of fatal
  261.      * exception codes. If this property is not set, codes are compared against the default codes in
  262.      * {@link Utils#getDisconnectionSqlCodes()} and in this case anything starting with #{link
  263.      * Utils.DISCONNECTION_SQL_CODE_PREFIX} is considered a disconnection. Additionally, any SQL state
  264.      * listed in {@link #disconnectionIgnoreSqlCodes} will be ignored and not treated as fatal.
  265.      * </p>
  266.      *
  267.      * @param e SQLException to be examined
  268.      * @return true if the exception signals a disconnection
  269.      */
  270.     boolean isDisconnectionSqlException(final SQLException e) {
  271.         boolean fatalException = false;
  272.         final String sqlState = e.getSQLState();
  273.         if (sqlState != null) {
  274.             if (disconnectionIgnoreSqlCodes != null && disconnectionIgnoreSqlCodes.contains(sqlState)) {
  275.                 return false;
  276.             }
  277.             fatalException = disconnectionSqlCodes == null
  278.                 ? sqlState.startsWith(Utils.DISCONNECTION_SQL_CODE_PREFIX) || Utils.isDisconnectionSqlCode(sqlState)
  279.                 : disconnectionSqlCodes.contains(sqlState);
  280.         }
  281.         return fatalException;
  282.     }

  283.     /**
  284.      * @return Whether to fail-fast.
  285.      * @since 2.6.0
  286.      */
  287.     public boolean isFastFailValidation() {
  288.         return fastFailValidation;
  289.     }

  290.     /**
  291.      * Checks the SQLState of the input exception and any nested SQLExceptions it wraps.
  292.      * <p>
  293.      * If {@link #disconnectionSqlCodes} has been set, sql states are compared to those in the
  294.      * configured list of fatal exception codes. If this property is not set, codes are compared against the default
  295.      * codes in {@link Utils#getDisconnectionSqlCodes()} and in this case anything starting with #{link
  296.      * Utils.DISCONNECTION_SQL_CODE_PREFIX} is considered a disconnection.
  297.      * </p>
  298.      *
  299.      * @param e
  300.      *            SQLException to be examined
  301.      * @return true if the exception signals a disconnection
  302.      */
  303.     boolean isFatalException(final SQLException e) {
  304.         boolean fatalException = isDisconnectionSqlException(e);
  305.         if (!fatalException) {
  306.             SQLException parentException = e;
  307.             SQLException nextException = e.getNextException();
  308.             while (nextException != null && nextException != parentException && !fatalException) {
  309.                 fatalException = isDisconnectionSqlException(nextException);
  310.                 parentException = nextException;
  311.                 nextException = parentException.getNextException();
  312.             }
  313.         }
  314.         return fatalException;
  315.     }

  316.     @Override
  317.     protected void passivate() throws SQLException {
  318.         super.passivate();
  319.         setClosedInternal(true);
  320.         if (getDelegateInternal() instanceof PoolingConnection) {
  321.             ((PoolingConnection) getDelegateInternal()).connectionReturnedToPool();
  322.         }
  323.     }

  324.     /**
  325.      * Closes the underlying {@link Connection}.
  326.      */
  327.     @Override
  328.     public void reallyClose() throws SQLException {
  329.         if (jmxObjectName != null) {
  330.             jmxObjectName.unregisterMBean();
  331.         }

  332.         if (validationPreparedStatement != null) {
  333.             Utils.closeQuietly((AutoCloseable) validationPreparedStatement);
  334.         }

  335.         super.closeInternal();
  336.     }

  337.     @Override
  338.     public void setLastUsed() {
  339.         super.setLastUsed();
  340.         if (pool instanceof GenericObjectPool<?>) {
  341.             final GenericObjectPool<PoolableConnection> gop = (GenericObjectPool<PoolableConnection>) pool;
  342.             if (gop.isAbandonedConfig()) {
  343.                 gop.use(this);
  344.             }
  345.         }
  346.     }

  347.     /**
  348.      * Validates the connection, using the following algorithm:
  349.      * <ol>
  350.      * <li>If {@code fastFailValidation} (constructor argument) is {@code true} and this connection has previously
  351.      * thrown a fatal disconnection exception, a {@code SQLException} is thrown.</li>
  352.      * <li>If {@code sql} is null, the driver's #{@link Connection#isValid(int) isValid(timeout)} is called. If it
  353.      * returns {@code false}, {@code SQLException} is thrown; otherwise, this method returns successfully.</li>
  354.      * <li>If {@code sql} is not null, it is executed as a query and if the resulting {@code ResultSet} contains at
  355.      * least one row, this method returns successfully. If not, {@code SQLException} is thrown.</li>
  356.      * </ol>
  357.      *
  358.      * @param sql
  359.      *            The validation SQL query.
  360.      * @param timeoutDuration
  361.      *            The validation timeout in seconds.
  362.      * @throws SQLException
  363.      *             Thrown when validation fails or an SQLException occurs during validation
  364.      * @since 2.10.0
  365.      */
  366.     public void validate(final String sql, Duration timeoutDuration) throws SQLException {
  367.         if (fastFailValidation && fatalSqlExceptionThrown) {
  368.             throw new SQLException(Utils.getMessage("poolableConnection.validate.fastFail"));
  369.         }

  370.         if (sql == null || sql.isEmpty()) {
  371.             if (timeoutDuration.isNegative()) {
  372.                 timeoutDuration = Duration.ZERO;
  373.             }
  374.             if (!isValid(timeoutDuration)) {
  375.                 throw new SQLException("isValid() returned false");
  376.             }
  377.             return;
  378.         }

  379.         if (!sql.equals(lastValidationSql)) {
  380.             lastValidationSql = sql;
  381.             // Has to be the innermost delegate else the prepared statement will
  382.             // be closed when the pooled connection is passivated.
  383.             validationPreparedStatement = getInnermostDelegateInternal().prepareStatement(sql);
  384.         }

  385.         if (timeoutDuration.compareTo(Duration.ZERO) > 0) {
  386.             validationPreparedStatement.setQueryTimeout((int) timeoutDuration.getSeconds());
  387.         }

  388.         try (ResultSet rs = validationPreparedStatement.executeQuery()) {
  389.             if (!rs.next()) {
  390.                 throw new SQLException("validationQuery didn't return a row");
  391.             }
  392.         } catch (final SQLException sqle) {
  393.             throw sqle;
  394.         }
  395.     }

  396.     /**
  397.      * Validates the connection, using the following algorithm:
  398.      * <ol>
  399.      * <li>If {@code fastFailValidation} (constructor argument) is {@code true} and this connection has previously
  400.      * thrown a fatal disconnection exception, a {@code SQLException} is thrown.</li>
  401.      * <li>If {@code sql} is null, the driver's #{@link Connection#isValid(int) isValid(timeout)} is called. If it
  402.      * returns {@code false}, {@code SQLException} is thrown; otherwise, this method returns successfully.</li>
  403.      * <li>If {@code sql} is not null, it is executed as a query and if the resulting {@code ResultSet} contains at
  404.      * least one row, this method returns successfully. If not, {@code SQLException} is thrown.</li>
  405.      * </ol>
  406.      *
  407.      * @param sql
  408.      *            The validation SQL query.
  409.      * @param timeoutSeconds
  410.      *            The validation timeout in seconds.
  411.      * @throws SQLException
  412.      *             Thrown when validation fails or an SQLException occurs during validation
  413.      * @deprecated Use {@link #validate(String, Duration)}.
  414.      */
  415.     @Deprecated
  416.     public void validate(final String sql, final int timeoutSeconds) throws SQLException {
  417.         validate(sql, Duration.ofSeconds(timeoutSeconds));
  418.     }
  419. }