All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.postgresql.ds.PGPooledConnection Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2004, PostgreSQL Global Development Group.
 * See the LICENSE file in the project root for more information.
 */

package org.postgresql.ds;

import static org.postgresql.util.internal.Nullness.castNonNull;

import org.postgresql.PGConnection;
import org.postgresql.util.GT;
import org.postgresql.util.PSQLException;
import org.postgresql.util.PSQLState;

import org.checkerframework.checker.nullness.qual.Nullable;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.LinkedList;
import java.util.List;

import javax.sql.ConnectionEvent;
import javax.sql.ConnectionEventListener;
import javax.sql.PooledConnection;
import javax.sql.StatementEventListener;

/**
 * PostgreSQL implementation of the PooledConnection interface. This shouldn't be used directly, as
 * the pooling client should just interact with the ConnectionPool instead.
 *
 * @author Aaron Mulder ([email protected])
 * @author Csaba Nagy ([email protected])
 * @see org.postgresql.ds.PGConnectionPoolDataSource
 */
public class PGPooledConnection implements PooledConnection {
  private final List listeners = new LinkedList();
  private @Nullable Connection con;
  private @Nullable ConnectionHandler last;
  private final boolean autoCommit;
  private final boolean isXA;

  /**
   * Creates a new PooledConnection representing the specified physical connection.
   *
   * @param con connection
   * @param autoCommit whether to autocommit
   * @param isXA whether connection is a XA connection
   */
  public PGPooledConnection(Connection con, boolean autoCommit, boolean isXA) {
    this.con = con;
    this.autoCommit = autoCommit;
    this.isXA = isXA;
  }

  public PGPooledConnection(Connection con, boolean autoCommit) {
    this(con, autoCommit, false);
  }

  /**
   * Adds a listener for close or fatal error events on the connection handed out to a client.
   */
  @Override
  public void addConnectionEventListener(ConnectionEventListener connectionEventListener) {
    listeners.add(connectionEventListener);
  }

  /**
   * Removes a listener for close or fatal error events on the connection handed out to a client.
   */
  @Override
  public void removeConnectionEventListener(ConnectionEventListener connectionEventListener) {
    listeners.remove(connectionEventListener);
  }

  /**
   * Closes the physical database connection represented by this PooledConnection. If any client has
   * a connection based on this PooledConnection, it is forcibly closed as well.
   */
  @Override
  public void close() throws SQLException {
    if (last != null) {
      last.close();
      if (con != null && !con.isClosed()) {
        if (!con.getAutoCommit()) {
          try {
            con.rollback();
          } catch (SQLException ignored) {
          }
        }
      }
    }
    if (con == null) {
      return;
    }
    try {
      con.close();
    } finally {
      con = null;
    }
  }

  /**
   * Gets a handle for a client to use. This is a wrapper around the physical connection, so the
   * client can call close and it will just return the connection to the pool without really closing
   * the pgysical connection.
   *
   * 

* According to the JDBC 2.0 Optional Package spec (6.2.3), only one client may have an active * handle to the connection at a time, so if there is a previous handle active when this is * called, the previous one is forcibly closed and its work rolled back. *

*/ @Override public Connection getConnection() throws SQLException { if (con == null) { // Before throwing the exception, let's notify the registered listeners about the error PSQLException sqlException = new PSQLException(GT.tr("This PooledConnection has already been closed."), PSQLState.CONNECTION_DOES_NOT_EXIST); fireConnectionFatalError(sqlException); throw sqlException; } // If any error occurs while opening a new connection, the listeners // have to be notified. This gives a chance to connection pools to // eliminate bad pooled connections. try { // Only one connection can be open at a time from this PooledConnection. See JDBC 2.0 Optional // Package spec section 6.2.3 if (last != null) { last.close(); if (con != null) { if (!con.getAutoCommit()) { try { con.rollback(); } catch (SQLException ignored) { } } con.clearWarnings(); } } /* * In XA-mode, autocommit is handled in PGXAConnection, because it depends on whether an * XA-transaction is open or not */ if (!isXA && con != null) { con.setAutoCommit(autoCommit); } } catch (SQLException sqlException) { fireConnectionFatalError(sqlException); throw (SQLException) sqlException.fillInStackTrace(); } ConnectionHandler handler = new ConnectionHandler(castNonNull(con)); last = handler; Connection proxyCon = (Connection) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Connection.class, PGConnection.class}, handler); handler.setProxy(proxyCon); return proxyCon; } /** * Used to fire a connection closed event to all listeners. */ void fireConnectionClosed() { ConnectionEvent evt = null; // Copy the listener list so the listener can remove itself during this method call ConnectionEventListener[] local = listeners.toArray(new ConnectionEventListener[0]); for (ConnectionEventListener listener : local) { if (evt == null) { evt = createConnectionEvent(null); } listener.connectionClosed(evt); } } /** * Used to fire a connection error event to all listeners. */ void fireConnectionFatalError(SQLException e) { ConnectionEvent evt = null; // Copy the listener list so the listener can remove itself during this method call ConnectionEventListener[] local = listeners.toArray(new ConnectionEventListener[0]); for (ConnectionEventListener listener : local) { if (evt == null) { evt = createConnectionEvent(e); } listener.connectionErrorOccurred(evt); } } protected ConnectionEvent createConnectionEvent(@Nullable SQLException e) { return e == null ? new ConnectionEvent(this) : new ConnectionEvent(this, e); } // Classes we consider fatal. private static final String[] fatalClasses = { "08", // connection error "53", // insufficient resources // nb: not just "57" as that includes query cancel which is nonfatal "57P01", // admin shutdown "57P02", // crash shutdown "57P03", // cannot connect now "58", // system error (backend) "60", // system error (driver) "99", // unexpected error "F0", // configuration file error (backend) "XX", // internal error (backend) }; private static boolean isFatalState(@Nullable String state) { if (state == null) { // no info, assume fatal return true; } if (state.length() < 2) { // no class info, assume fatal return true; } for (String fatalClass : fatalClasses) { if (state.startsWith(fatalClass)) { return true; // fatal } } return false; } /** * Fires a connection error event, but only if we think the exception is fatal. * * @param e the SQLException to consider */ private void fireConnectionError(SQLException e) { if (!isFatalState(e.getSQLState())) { return; } fireConnectionFatalError(e); } /** * Instead of declaring a class implementing Connection, which would have to be updated for every * JDK rev, use a dynamic proxy to handle all calls through the Connection interface. This is the * part that requires JDK 1.3 or higher, though JDK 1.2 could be supported with a 3rd-party proxy * package. */ private class ConnectionHandler implements InvocationHandler { private @Nullable Connection con; private @Nullable Connection proxy; // the Connection the client is currently using, which is a proxy private boolean automatic = false; ConnectionHandler(Connection con) { this.con = con; } @Override @SuppressWarnings("throwing.nullable") public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable { final String methodName = method.getName(); // From Object if (method.getDeclaringClass() == Object.class) { if (methodName.equals("toString")) { return "Pooled connection wrapping physical connection " + con; } if (methodName.equals("equals")) { return proxy == args[0]; } if (methodName.equals("hashCode")) { return System.identityHashCode(proxy); } try { return method.invoke(con, args); } catch (InvocationTargetException e) { // throwing.nullable throw e.getTargetException(); } } // All the rest is from the Connection or PGConnection interface if (methodName.equals("isClosed")) { return con == null || con.isClosed(); } if (methodName.equals("close")) { // we are already closed and a double close // is not an error. if (con == null) { return null; } SQLException ex = null; if (!con.isClosed()) { if (!isXA && !con.getAutoCommit()) { try { con.rollback(); } catch (SQLException e) { ex = e; } } con.clearWarnings(); } con = null; this.proxy = null; last = null; fireConnectionClosed(); if (ex != null) { throw ex; } return null; } if (con == null || con.isClosed()) { throw new PSQLException(automatic ? GT.tr( "Connection has been closed automatically because a new connection was opened for the same PooledConnection or the PooledConnection has been closed.") : GT.tr("Connection has been closed."), PSQLState.CONNECTION_DOES_NOT_EXIST); } // From here on in, we invoke via reflection, catch exceptions, // and check if they're fatal before rethrowing. try { if (methodName.equals("createStatement")) { Statement st = castNonNull((Statement) method.invoke(con, args)); return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Statement.class, org.postgresql.PGStatement.class}, new StatementHandler(this, st)); } else if (methodName.equals("prepareCall")) { Statement st = castNonNull((Statement) method.invoke(con, args)); return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{CallableStatement.class, org.postgresql.PGStatement.class}, new StatementHandler(this, st)); } else if (methodName.equals("prepareStatement")) { Statement st = castNonNull((Statement) method.invoke(con, args)); return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{PreparedStatement.class, org.postgresql.PGStatement.class}, new StatementHandler(this, st)); } else { return method.invoke(con, args); } } catch (final InvocationTargetException ite) { final Throwable te = ite.getTargetException(); if (te instanceof SQLException) { fireConnectionError((SQLException) te); // Tell listeners about exception if it's fatal } throw te; } } Connection getProxy() { return castNonNull(proxy); } void setProxy(Connection proxy) { this.proxy = proxy; } public void close() { if (con != null) { automatic = true; } con = null; proxy = null; // No close event fired here: see JDBC 2.0 Optional Package spec section 6.3 } public boolean isClosed() { return con == null; } } /** *

Instead of declaring classes implementing Statement, PreparedStatement, and CallableStatement, * which would have to be updated for every JDK rev, use a dynamic proxy to handle all calls * through the Statement interfaces. This is the part that requires JDK 1.3 or higher, though JDK * 1.2 could be supported with a 3rd-party proxy package.

* *

The StatementHandler is required in order to return the proper Connection proxy for the * getConnection method.

*/ private class StatementHandler implements InvocationHandler { private @Nullable ConnectionHandler con; private @Nullable Statement st; StatementHandler(ConnectionHandler con, Statement st) { this.con = con; this.st = st; } @Override @SuppressWarnings("throwing.nullable") public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable { final String methodName = method.getName(); // From Object if (method.getDeclaringClass() == Object.class) { if (methodName.equals("toString")) { return "Pooled statement wrapping physical statement " + st; } if (methodName.equals("hashCode")) { return System.identityHashCode(proxy); } if (methodName.equals("equals")) { return proxy == args[0]; } return method.invoke(st, args); } // All the rest is from the Statement interface if (methodName.equals("isClosed")) { return st == null || st.isClosed(); } if (methodName.equals("close")) { if (st == null || st.isClosed()) { return null; } con = null; final Statement oldSt = st; st = null; oldSt.close(); return null; } if (st == null || st.isClosed()) { throw new PSQLException(GT.tr("Statement has been closed."), PSQLState.OBJECT_NOT_IN_STATE); } if (methodName.equals("getConnection")) { return castNonNull(con).getProxy(); // the proxied connection, not a physical connection } // Delegate the call to the proxied Statement. try { return method.invoke(st, args); } catch (final InvocationTargetException ite) { final Throwable te = ite.getTargetException(); if (te instanceof SQLException) { fireConnectionError((SQLException) te); // Tell listeners about exception if it's fatal } throw te; } } } @Override public void removeStatementEventListener(StatementEventListener listener) { } @Override public void addStatementEventListener(StatementEventListener listener) { } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy