CPDSConnectionFactory.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.datasources;

  18. import java.sql.Connection;
  19. import java.sql.ResultSet;
  20. import java.sql.SQLException;
  21. import java.sql.Statement;
  22. import java.time.Duration;
  23. import java.util.Collections;
  24. import java.util.Map;
  25. import java.util.Set;
  26. import java.util.concurrent.ConcurrentHashMap;

  27. import javax.sql.ConnectionEvent;
  28. import javax.sql.ConnectionEventListener;
  29. import javax.sql.ConnectionPoolDataSource;
  30. import javax.sql.PooledConnection;

  31. import org.apache.commons.dbcp2.Utils;
  32. import org.apache.commons.pool2.ObjectPool;
  33. import org.apache.commons.pool2.PooledObject;
  34. import org.apache.commons.pool2.PooledObjectFactory;
  35. import org.apache.commons.pool2.impl.DefaultPooledObject;

  36. /**
  37.  * A {@link PooledObjectFactory} that creates {@link org.apache.commons.dbcp2.PoolableConnection PoolableConnection}s.
  38.  *
  39.  * @since 2.0
  40.  */
  41. final class CPDSConnectionFactory
  42.         implements PooledObjectFactory<PooledConnectionAndInfo>, ConnectionEventListener, PooledConnectionManager {

  43.     private static final String NO_KEY_MESSAGE = "close() was called on a Connection, but I have no record of the underlying PooledConnection.";

  44.     private final ConnectionPoolDataSource cpds;
  45.     private final String validationQuery;
  46.     private final Duration validationQueryTimeoutDuration;
  47.     private final boolean rollbackAfterValidation;
  48.     private ObjectPool<PooledConnectionAndInfo> pool;
  49.     private UserPassKey userPassKey;
  50.     private Duration maxConnDuration = Duration.ofMillis(-1);

  51.     /**
  52.      * Map of PooledConnections for which close events are ignored. Connections are muted when they are being validated.
  53.      */
  54.     private final Set<PooledConnection> validatingSet = Collections.newSetFromMap(new ConcurrentHashMap<>());

  55.     /**
  56.      * Map of PooledConnectionAndInfo instances
  57.      */
  58.     private final Map<PooledConnection, PooledConnectionAndInfo> pcMap = new ConcurrentHashMap<>();

  59.     /**
  60.      * Creates a new {@code PoolableConnectionFactory}.
  61.      *
  62.      * @param cpds
  63.      *            the ConnectionPoolDataSource from which to obtain PooledConnection's
  64.      * @param validationQuery
  65.      *            a query to use to {@link #validateObject validate} {@link Connection}s. Should return at least one
  66.      *            row. May be {@code null} in which case {@link Connection#isValid(int)} will be used to validate
  67.      *            connections.
  68.      * @param validationQueryTimeoutDuration
  69.      *            Timeout Duration before validation fails
  70.      * @param rollbackAfterValidation
  71.      *            whether a rollback should be issued after {@link #validateObject validating} {@link Connection}s.
  72.      * @param userName
  73.      *            The user name to use to create connections
  74.      * @param userPassword
  75.      *            The password to use to create connections
  76.      * @since 2.10.0
  77.      */
  78.     public CPDSConnectionFactory(final ConnectionPoolDataSource cpds, final String validationQuery,
  79.             final Duration validationQueryTimeoutDuration, final boolean rollbackAfterValidation, final String userName,
  80.         final char[] userPassword) {
  81.         this.cpds = cpds;
  82.         this.validationQuery = validationQuery;
  83.         this.validationQueryTimeoutDuration = validationQueryTimeoutDuration;
  84.         this.userPassKey = new UserPassKey(userName, userPassword);
  85.         this.rollbackAfterValidation = rollbackAfterValidation;
  86.     }

  87.     /**
  88.      * Creates a new {@code PoolableConnectionFactory}.
  89.      *
  90.      * @param cpds
  91.      *            the ConnectionPoolDataSource from which to obtain PooledConnection's
  92.      * @param validationQuery
  93.      *            a query to use to {@link #validateObject validate} {@link Connection}s. Should return at least one
  94.      *            row. May be {@code null} in which case {@link Connection#isValid(int)} will be used to validate
  95.      *            connections.
  96.      * @param validationQueryTimeoutDuration
  97.      *            Timeout in seconds before validation fails
  98.      * @param rollbackAfterValidation
  99.      *            whether a rollback should be issued after {@link #validateObject validating} {@link Connection}s.
  100.      * @param userName
  101.      *            The user name to use to create connections
  102.      * @param userPassword
  103.      *            The password to use to create connections
  104.      * @since 2.10.0
  105.      */
  106.     public CPDSConnectionFactory(final ConnectionPoolDataSource cpds, final String validationQuery, final Duration validationQueryTimeoutDuration,
  107.         final boolean rollbackAfterValidation, final String userName, final String userPassword) {
  108.         this(cpds, validationQuery, validationQueryTimeoutDuration, rollbackAfterValidation, userName, Utils.toCharArray(userPassword));
  109.     }

  110.     /**
  111.      * Creates a new {@code PoolableConnectionFactory}.
  112.      *
  113.      * @param cpds
  114.      *            the ConnectionPoolDataSource from which to obtain PooledConnection's
  115.      * @param validationQuery
  116.      *            a query to use to {@link #validateObject validate} {@link Connection}s. Should return at least one
  117.      *            row. May be {@code null} in which case {@link Connection#isValid(int)} will be used to validate
  118.      *            connections.
  119.      * @param validationQueryTimeoutSeconds
  120.      *            Timeout in seconds before validation fails
  121.      * @param rollbackAfterValidation
  122.      *            whether a rollback should be issued after {@link #validateObject validating} {@link Connection}s.
  123.      * @param userName
  124.      *            The user name to use to create connections
  125.      * @param userPassword
  126.      *            The password to use to create connections
  127.      * @since 2.4.0
  128.      * @deprecated Use {@link #CPDSConnectionFactory(ConnectionPoolDataSource, String, Duration, boolean, String, char[])}.
  129.      */
  130.     @Deprecated
  131.     public CPDSConnectionFactory(final ConnectionPoolDataSource cpds, final String validationQuery,
  132.             final int validationQueryTimeoutSeconds, final boolean rollbackAfterValidation, final String userName,
  133.         final char[] userPassword) {
  134.         this.cpds = cpds;
  135.         this.validationQuery = validationQuery;
  136.         this.validationQueryTimeoutDuration = Duration.ofSeconds(validationQueryTimeoutSeconds);
  137.         this.userPassKey = new UserPassKey(userName, userPassword);
  138.         this.rollbackAfterValidation = rollbackAfterValidation;
  139.     }

  140.     /**
  141.      * Creates a new {@code PoolableConnectionFactory}.
  142.      *
  143.      * @param cpds
  144.      *            the ConnectionPoolDataSource from which to obtain PooledConnection's
  145.      * @param validationQuery
  146.      *            a query to use to {@link #validateObject validate} {@link Connection}s. Should return at least one
  147.      *            row. May be {@code null} in which case {@link Connection#isValid(int)} will be used to validate
  148.      *            connections.
  149.      * @param validationQueryTimeoutSeconds
  150.      *            Timeout in seconds before validation fails
  151.      * @param rollbackAfterValidation
  152.      *            whether a rollback should be issued after {@link #validateObject validating} {@link Connection}s.
  153.      * @param userName
  154.      *            The user name to use to create connections
  155.      * @param userPassword
  156.      *            The password to use to create connections
  157.      * @deprecated Use {@link #CPDSConnectionFactory(ConnectionPoolDataSource, String, Duration, boolean, String, String)}.
  158.      */
  159.     @Deprecated
  160.     public CPDSConnectionFactory(final ConnectionPoolDataSource cpds, final String validationQuery, final int validationQueryTimeoutSeconds,
  161.             final boolean rollbackAfterValidation, final String userName, final String userPassword) {
  162.         this(cpds, validationQuery, validationQueryTimeoutSeconds, rollbackAfterValidation, userName, Utils.toCharArray(userPassword));
  163.     }

  164.     @Override
  165.     public void activateObject(final PooledObject<PooledConnectionAndInfo> p) throws SQLException {
  166.         validateLifetime(p);
  167.     }

  168.     /**
  169.      * Verifies that the user name matches the user whose connections are being managed by this factory and closes the
  170.      * pool if this is the case; otherwise does nothing.
  171.      */
  172.     @Override
  173.     public void closePool(final String userName) throws SQLException {
  174.         synchronized (this) {
  175.             if (userName == null || !userName.equals(this.userPassKey.getUserName())) {
  176.                 return;
  177.             }
  178.         }
  179.         try {
  180.             pool.close();
  181.         } catch (final Exception ex) {
  182.             throw new SQLException("Error closing connection pool", ex);
  183.         }
  184.     }

  185.     /**
  186.      * This will be called if the Connection returned by the getConnection method came from a PooledConnection, and the
  187.      * user calls the close() method of this connection object. What we need to do here is to release this
  188.      * PooledConnection from our pool...
  189.      */
  190.     @Override
  191.     public void connectionClosed(final ConnectionEvent event) {
  192.         final PooledConnection pc = (PooledConnection) event.getSource();
  193.         // if this event occurred because we were validating, ignore it
  194.         // otherwise return the connection to the pool.
  195.         if (!validatingSet.contains(pc)) {
  196.             final PooledConnectionAndInfo pci = pcMap.get(pc);
  197.             if (pci == null) {
  198.                 throw new IllegalStateException(NO_KEY_MESSAGE);
  199.             }

  200.             try {
  201.                 pool.returnObject(pci);
  202.             } catch (final Exception e) {
  203.                 System.err.println("CLOSING DOWN CONNECTION AS IT COULD " + "NOT BE RETURNED TO THE POOL");
  204.                 pc.removeConnectionEventListener(this);
  205.                 try {
  206.                     doDestroyObject(pci);
  207.                 } catch (final Exception e2) {
  208.                     System.err.println("EXCEPTION WHILE DESTROYING OBJECT " + pci);
  209.                     e2.printStackTrace();
  210.                 }
  211.             }
  212.         }
  213.     }

  214.     /**
  215.      * If a fatal error occurs, close the underlying physical connection so as not to be returned in the future
  216.      */
  217.     @Override
  218.     public void connectionErrorOccurred(final ConnectionEvent event) {
  219.         final PooledConnection pc = (PooledConnection) event.getSource();
  220.         if (null != event.getSQLException()) {
  221.             System.err.println("CLOSING DOWN CONNECTION DUE TO INTERNAL ERROR (" + event.getSQLException() + ")");
  222.         }
  223.         pc.removeConnectionEventListener(this);

  224.         final PooledConnectionAndInfo pci = pcMap.get(pc);
  225.         if (pci == null) {
  226.             throw new IllegalStateException(NO_KEY_MESSAGE);
  227.         }
  228.         try {
  229.             pool.invalidateObject(pci);
  230.         } catch (final Exception e) {
  231.             System.err.println("EXCEPTION WHILE DESTROYING OBJECT " + pci);
  232.             e.printStackTrace();
  233.         }
  234.     }

  235.     /**
  236.      * Closes the PooledConnection and stops listening for events from it.
  237.      */
  238.     @Override
  239.     public void destroyObject(final PooledObject<PooledConnectionAndInfo> p) throws SQLException {
  240.         doDestroyObject(p.getObject());
  241.     }

  242.     private void doDestroyObject(final PooledConnectionAndInfo pci) throws SQLException {
  243.         final PooledConnection pc = pci.getPooledConnection();
  244.         pc.removeConnectionEventListener(this);
  245.         pcMap.remove(pc);
  246.         pc.close();
  247.     }

  248.     /**
  249.      * (Testing API) Gets the value of password for the default user.
  250.      *
  251.      * @return value of password.
  252.      */
  253.     char[] getPasswordCharArray() {
  254.         return userPassKey.getPasswordCharArray();
  255.     }

  256.     /**
  257.      * Returns the object pool used to pool connections created by this factory.
  258.      *
  259.      * @return ObjectPool managing pooled connections
  260.      */
  261.     public ObjectPool<PooledConnectionAndInfo> getPool() {
  262.         return pool;
  263.     }

  264.     /**
  265.      * Invalidates the PooledConnection in the pool. The CPDSConnectionFactory closes the connection and pool counters
  266.      * are updated appropriately. Also closes the pool. This ensures that all idle connections are closed and
  267.      * connections that are checked out are closed on return.
  268.      */
  269.     @Override
  270.     public void invalidate(final PooledConnection pc) throws SQLException {
  271.         final PooledConnectionAndInfo pci = pcMap.get(pc);
  272.         if (pci == null) {
  273.             throw new IllegalStateException(NO_KEY_MESSAGE);
  274.         }
  275.         try {
  276.             pool.invalidateObject(pci); // Destroy instance and update pool counters
  277.             pool.close(); // Clear any other instances in this pool and kill others as they come back
  278.         } catch (final Exception ex) {
  279.             throw new SQLException("Error invalidating connection", ex);
  280.         }
  281.     }

  282.     @Override
  283.     public synchronized PooledObject<PooledConnectionAndInfo> makeObject() throws SQLException {
  284.         PooledConnection pc = null;
  285.         if (userPassKey.getUserName() == null) {
  286.             pc = cpds.getPooledConnection();
  287.         } else {
  288.             pc = cpds.getPooledConnection(userPassKey.getUserName(), userPassKey.getPassword());
  289.         }
  290.         if (pc == null) {
  291.             throw new IllegalStateException("Connection pool data source returned null from getPooledConnection");
  292.         }
  293.         // should we add this object as a listener or the pool.
  294.         // consider the validateObject method in decision
  295.         pc.addConnectionEventListener(this);
  296.         final PooledConnectionAndInfo pci = new PooledConnectionAndInfo(pc, userPassKey);
  297.         pcMap.put(pc, pci);
  298.         return new DefaultPooledObject<>(pci);
  299.     }

  300.     @Override
  301.     public void passivateObject(final PooledObject<PooledConnectionAndInfo> p) throws SQLException {
  302.         validateLifetime(p);
  303.     }

  304.     /**
  305.      * Sets the maximum Duration of a connection after which the connection will always fail activation,
  306.      * passivation and validation.
  307.      *
  308.      * @param maxConnDuration
  309.      *            A value of zero or less indicates an infinite lifetime. The default value is -1 milliseconds.
  310.      * @since 2.10.0
  311.      */
  312.     public void setMaxConn(final Duration maxConnDuration) {
  313.         this.maxConnDuration = maxConnDuration;
  314.     }

  315.     /**
  316.      * Sets the maximum lifetime in milliseconds of a connection after which the connection will always fail activation,
  317.      * passivation and validation.
  318.      *
  319.      * @param maxConnDuration
  320.      *            A value of zero or less indicates an infinite lifetime. The default value is -1 milliseconds.
  321.      * @since 2.9.0
  322.      * @deprecated Use {@link #setMaxConn(Duration)}.
  323.      */
  324.     @Deprecated
  325.     public void setMaxConnLifetime(final Duration maxConnDuration) {
  326.         this.maxConnDuration = maxConnDuration;
  327.     }

  328.     /**
  329.      * Sets the maximum lifetime in milliseconds of a connection after which the connection will always fail activation,
  330.      * passivation and validation.
  331.      *
  332.      * @param maxConnLifetimeMillis
  333.      *            A value of zero or less indicates an infinite lifetime. The default value is -1.
  334.      * @deprecated Use {@link #setMaxConn(Duration)}.
  335.      */
  336.     @Deprecated
  337.     public void setMaxConnLifetimeMillis(final long maxConnLifetimeMillis) {
  338.         setMaxConnLifetime(Duration.ofMillis(maxConnLifetimeMillis));
  339.     }

  340.     /**
  341.      * Sets the database password used when creating new connections.
  342.      *
  343.      * @param userPassword
  344.      *            new password
  345.      */
  346.     public synchronized void setPassword(final char[] userPassword) {
  347.         this.userPassKey = new UserPassKey(userPassKey.getUserName(), userPassword);
  348.     }

  349.     /**
  350.      * Sets the database password used when creating new connections.
  351.      *
  352.      * @param userPassword
  353.      *            new password
  354.      */
  355.     @Override
  356.     public synchronized void setPassword(final String userPassword) {
  357.         this.userPassKey = new UserPassKey(userPassKey.getUserName(), userPassword);
  358.     }

  359.     /**
  360.      *
  361.      * @param pool
  362.      *            the {@link ObjectPool} in which to pool those {@link Connection}s
  363.      */
  364.     public void setPool(final ObjectPool<PooledConnectionAndInfo> pool) {
  365.         this.pool = pool;
  366.     }

  367.     /**
  368.      * @since 2.6.0
  369.      */
  370.     @Override
  371.     public synchronized String toString() {
  372.         final StringBuilder builder = new StringBuilder(super.toString());
  373.         builder.append("[cpds=");
  374.         builder.append(cpds);
  375.         builder.append(", validationQuery=");
  376.         builder.append(validationQuery);
  377.         builder.append(", validationQueryTimeoutDuration=");
  378.         builder.append(validationQueryTimeoutDuration);
  379.         builder.append(", rollbackAfterValidation=");
  380.         builder.append(rollbackAfterValidation);
  381.         builder.append(", pool=");
  382.         builder.append(pool);
  383.         builder.append(", maxConnDuration=");
  384.         builder.append(maxConnDuration);
  385.         builder.append(", validatingSet=");
  386.         builder.append(validatingSet);
  387.         builder.append(", pcMap=");
  388.         builder.append(pcMap);
  389.         builder.append("]");
  390.         return builder.toString();
  391.     }

  392.     private void validateLifetime(final PooledObject<PooledConnectionAndInfo> p) throws SQLException {
  393.         Utils.validateLifetime(p, maxConnDuration);
  394.     }

  395.     @Override
  396.     public boolean validateObject(final PooledObject<PooledConnectionAndInfo> p) {
  397.         try {
  398.             validateLifetime(p);
  399.         } catch (final Exception e) {
  400.             return false;
  401.         }
  402.         boolean valid = false;
  403.         final PooledConnection pconn = p.getObject().getPooledConnection();
  404.         Connection conn = null;
  405.         validatingSet.add(pconn);
  406.         if (null == validationQuery) {
  407.             Duration timeoutDuration = validationQueryTimeoutDuration;
  408.             if (timeoutDuration.isNegative()) {
  409.                 timeoutDuration = Duration.ZERO;
  410.             }
  411.             try {
  412.                 conn = pconn.getConnection();
  413.                 valid = conn.isValid((int) timeoutDuration.getSeconds());
  414.             } catch (final SQLException e) {
  415.                 valid = false;
  416.             } finally {
  417.                 Utils.closeQuietly((AutoCloseable) conn);
  418.                 validatingSet.remove(pconn);
  419.             }
  420.         } else {
  421.             Statement stmt = null;
  422.             ResultSet rset = null;
  423.             // logical Connection from the PooledConnection must be closed
  424.             // before another one can be requested and closing it will
  425.             // generate an event. Keep track so we know not to return
  426.             // the PooledConnection
  427.             validatingSet.add(pconn);
  428.             try {
  429.                 conn = pconn.getConnection();
  430.                 stmt = conn.createStatement();
  431.                 rset = stmt.executeQuery(validationQuery);
  432.                 valid = rset.next();
  433.                 if (rollbackAfterValidation) {
  434.                     conn.rollback();
  435.                 }
  436.             } catch (final Exception e) {
  437.                 valid = false;
  438.             } finally {
  439.                 Utils.closeQuietly((AutoCloseable) rset);
  440.                 Utils.closeQuietly((AutoCloseable) stmt);
  441.                 Utils.closeQuietly((AutoCloseable) conn);
  442.                 validatingSet.remove(pconn);
  443.             }
  444.         }
  445.         return valid;
  446.     }
  447. }