package org.postgresql.jdbc2.optional; import javax.sql.*; import java.sql.*; import java.util.*; import java.lang.reflect.*; import org.postgresql.PGConnection; /** * PostgreSQL implementation of the PooledConnection interface. This shouldn't * be used directly, as the pooling client should just interact with the * ConnectionPool instead. * @see ConnectionPool * * @author Aaron Mulder (ammulder@chariotsolutions.com) * @author Csaba Nagy (ncsaba@yahoo.com) * @version $Revision: 1.8 $ */ public class PooledConnectionImpl implements PooledConnection { private List listeners = new LinkedList(); private Connection con; private ConnectionHandler last; private boolean autoCommit; /** * Creates a new PooledConnection representing the specified physical * connection. */ protected PooledConnectionImpl(Connection con, boolean autoCommit) { this.con = con; this.autoCommit = autoCommit; } /** * Adds a listener for close or fatal error events on the connection * handed out to a client. */ 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. */ 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. */ public void close() throws SQLException { if (last != null) { last.close(); if (!con.getAutoCommit()) { try { con.rollback(); } catch (SQLException e) {} } } 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.
*/ public Connection getConnection() throws SQLException { if (con == null) { // Before throwing the exception, let's notify the registered listeners about the error final SQLException sqlException = new SQLException("This PooledConnection has already been closed!"); fireConnectionFatalError(sqlException); throw sqlException; } // If any error occures while opening a new connection, the listeners // have to be notified. This gives a chance to connection pools to // elliminate 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.getAutoCommit()) { try { con.rollback(); } catch (SQLException e) {} } con.clearWarnings(); } con.setAutoCommit(autoCommit); } catch (SQLException sqlException) { fireConnectionFatalError(sqlException); throw (SQLException)sqlException.fillInStackTrace(); } ConnectionHandler handler = new ConnectionHandler(con); last = handler; Connection con = (Connection)Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Connection.class, PGConnection.class}, handler); last.setProxy(con); return con; } /** * 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 = (ConnectionEventListener[]) listeners.toArray(new ConnectionEventListener[listeners.size()]); for (int i = 0; i < local.length; i++) { ConnectionEventListener listener = local[i]; if (evt == null) { evt = new ConnectionEvent(this); } 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 = (ConnectionEventListener[])listeners.toArray(new ConnectionEventListener[listeners.size()]); for (int i = 0; i < local.length; i++) { ConnectionEventListener listener = local[i]; if (evt == null) { evt = new ConnectionEvent(this, e); } listener.connectionErrorOccurred(evt); } } /** * 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 Connection con; private Connection proxy; // the Connection the client is currently using, which is a proxy private boolean automatic = false; public ConnectionHandler(Connection con) { this.con = con; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // From Object if (method.getDeclaringClass().getName().equals("java.lang.Object")) { if (method.getName().equals("toString")) { return "Pooled connection wrapping physical connection " + con; } if (method.getName().equals("hashCode")) { return new Integer(con.hashCode()); } if (method.getName().equals("equals")) { if (args[0] == null) { return Boolean.FALSE; } try { return Proxy.isProxyClass(args[0].getClass()) && ((ConnectionHandler) Proxy.getInvocationHandler(args[0])).con == con ? Boolean.TRUE : Boolean.FALSE; } catch (ClassCastException e) { return Boolean.FALSE; } } try { return method.invoke(con, args); } catch (InvocationTargetException e) { throw e.getTargetException(); } } // All the rest is from the Connection or PGConnection interface if (method.getName().equals("isClosed")) { return con == null ? Boolean.TRUE : Boolean.FALSE; } if (con == null) { throw new SQLException(automatic ? "Connection has been closed automatically because a new connection was opened for the same PooledConnection or the PooledConnection has been closed" : "Connection has been closed"); } if (method.getName().equals("close")) { SQLException ex = null; if (!con.getAutoCommit()) { try { con.rollback(); } catch (SQLException e) { ex = e; } } con.clearWarnings(); con = null; proxy = null; last = null; fireConnectionClosed(); if (ex != null) { throw ex; } return null; } else if(method.getName().equals("createStatement")) { Statement st = (Statement)method.invoke(con, args); return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Statement.class, org.postgresql.PGStatement.class}, new StatementHandler(this, st)); } else if(method.getName().equals("prepareCall")) { Statement st = (Statement)method.invoke(con, args); return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{CallableStatement.class, org.postgresql.PGStatement.class}, new StatementHandler(this, st)); } else if(method.getName().equals("prepareStatement")) { Statement st = (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); } } Connection getProxy() { return 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 static class StatementHandler implements InvocationHandler { private ConnectionHandler con; private Statement st; public StatementHandler(ConnectionHandler con, Statement st) { this.con = con; this.st = st; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // From Object if (method.getDeclaringClass().getName().equals("java.lang.Object")) { if (method.getName().equals("toString")) { return "Pooled statement wrapping physical statement " + st; } if (method.getName().equals("hashCode")) { return new Integer(st.hashCode()); } if (method.getName().equals("equals")) { if (args[0] == null) { return Boolean.FALSE; } try { return Proxy.isProxyClass(args[0].getClass()) && ((StatementHandler) Proxy.getInvocationHandler(args[0])).st == st ? Boolean.TRUE : Boolean.FALSE; } catch (ClassCastException e) { return Boolean.FALSE; } } return method.invoke(st, args); } // All the rest is from the Statement interface if (st == null || con.isClosed()) { throw new SQLException("Statement has been closed"); } if (method.getName().equals("close")) { try { st.close(); } finally { con = null; st = null; return null; } } else if (method.getName().equals("getConnection")) { return con.getProxy(); // the proxied connection, not a physical connection } else { try { return method.invoke(st, args); } catch (InvocationTargetException e) { throw e.getTargetException(); } } } } }