/**
   * Creates a new {@link PooledConnectionAndInfo} from the given {@link UserPassKey}.
   *
   * @param upkey {@link UserPassKey} containing user credentials
   * @throws SQLException if the connection could not be created.
   * @see org.apache.tomcat.dbcp.pool2.KeyedPooledObjectFactory#makeObject(java.lang.Object)
   */
  @Override
  public synchronized PooledObject<PooledConnectionAndInfo> makeObject(UserPassKey upkey)
      throws Exception {
    PooledConnectionAndInfo pci = null;

    PooledConnection pc = null;
    String username = upkey.getUsername();
    String password = upkey.getPassword();
    if (username == null) {
      pc = _cpds.getPooledConnection();
    } else {
      pc = _cpds.getPooledConnection(username, password);
    }

    if (pc == null) {
      throw new IllegalStateException(
          "Connection pool data source returned null from getPooledConnection");
    }

    // should we add this object as a listener or the pool.
    // consider the validateObject method in decision
    pc.addConnectionEventListener(this);
    pci = new PooledConnectionAndInfo(pc, username, password);
    pcMap.put(pc, pci);

    return new DefaultPooledObject<>(pci);
  }
  /**
   * Attempt to retrieve a database connection using {@link #getPooledConnectionAndInfo(String,
   * String)} with the provided username and password. The password on the {@link
   * PooledConnectionAndInfo} instance returned by <code>getPooledConnectionAndInfo</code> is
   * compared to the <code>password</code> parameter. If the comparison fails, a database connection
   * using the supplied username and password is attempted. If the connection attempt fails, an
   * SQLException is thrown, indicating that the given password did not match the password used to
   * create the pooled connection. If the connection attempt succeeds, this means that the database
   * password has been changed. In this case, the <code>PooledConnectionAndInfo</code> instance
   * retrieved with the old password is destroyed and the <code>getPooledConnectionAndInfo</code> is
   * repeatedly invoked until a <code>PooledConnectionAndInfo</code> instance with the new password
   * is returned.
   */
  @Override
  public Connection getConnection(String username, String password) throws SQLException {
    if (instanceKey == null) {
      throw new SQLException(
          "Must set the ConnectionPoolDataSource "
              + "through setDataSourceName or setConnectionPoolDataSource"
              + " before calling getConnection.");
    }
    getConnectionCalled = true;
    PooledConnectionAndInfo info = null;
    try {
      info = getPooledConnectionAndInfo(username, password);
    } catch (NoSuchElementException e) {
      closeDueToException(info);
      throw new SQLException("Cannot borrow connection from pool", e);
    } catch (RuntimeException e) {
      closeDueToException(info);
      throw e;
    } catch (SQLException e) {
      closeDueToException(info);
      throw e;
    } catch (Exception e) {
      closeDueToException(info);
      throw new SQLException("Cannot borrow connection from pool", e);
    }

    if (!(null == password
        ? null == info.getPassword()
        : password.equals(
            info.getPassword()))) { // Password on PooledConnectionAndInfo does not match
      try { // See if password has changed by attempting connection
        testCPDS(username, password);
      } catch (SQLException ex) {
        // Password has not changed, so refuse client, but return connection to the pool
        closeDueToException(info);
        throw new SQLException(
            "Given password did not match password used" + " to create the PooledConnection.", ex);
      } catch (javax.naming.NamingException ne) {
        throw new SQLException("NamingException encountered connecting to database", ne);
      }
      /*
       * Password must have changed -> destroy connection and keep retrying until we get a new, good one,
       * destroying any idle connections with the old passowrd as we pull them from the pool.
       */
      final UserPassKey upkey = info.getUserPassKey();
      final PooledConnectionManager manager = getConnectionManager(upkey);
      manager.invalidate(info.getPooledConnection()); // Destroy and remove from pool
      manager.setPassword(
          upkey.getPassword()); // Reset the password on the factory if using CPDSConnectionFactory
      info = null;
      for (int i = 0;
          i < 10;
          i++) { // Bound the number of retries - only needed if bad instances return
        try {
          info = getPooledConnectionAndInfo(username, password);
        } catch (NoSuchElementException e) {
          closeDueToException(info);
          throw new SQLException("Cannot borrow connection from pool", e);
        } catch (RuntimeException e) {
          closeDueToException(info);
          throw e;
        } catch (SQLException e) {
          closeDueToException(info);
          throw e;
        } catch (Exception e) {
          closeDueToException(info);
          throw new SQLException("Cannot borrow connection from pool", e);
        }
        if (info != null && password != null && password.equals(info.getPassword())) {
          break;
        }
        if (info != null) {
          manager.invalidate(info.getPooledConnection());
        }
        info = null;
      }
      if (info == null) {
        throw new SQLException("Cannot borrow connection from pool - password change failure.");
      }
    }

    Connection con = info.getPooledConnection().getConnection();
    try {
      setupDefaults(con, username);
      con.clearWarnings();
      return con;
    } catch (SQLException ex) {
      try {
        con.close();
      } catch (Exception exc) {
        getLogWriter().println("ignoring exception during close: " + exc);
      }
      throw ex;
    }
  }