1
package org.postgresql.jdbc2.optional;
6
import java.lang.reflect.*;
7
import org.postgresql.PGConnection;
10
* PostgreSQL implementation of the PooledConnection interface. This shouldn't
11
* be used directly, as the pooling client should just interact with the
12
* ConnectionPool instead.
15
* @author Aaron Mulder (ammulder@chariotsolutions.com)
16
* @author Csaba Nagy (ncsaba@yahoo.com)
17
* @version $Revision: 1.7.4.3 $
19
public class PooledConnectionImpl implements PooledConnection
21
private List listeners = new LinkedList();
22
private Connection con;
23
private ConnectionHandler last;
24
private boolean autoCommit;
27
* Creates a new PooledConnection representing the specified physical
30
protected PooledConnectionImpl(Connection con, boolean autoCommit)
33
this.autoCommit = autoCommit;
37
* Adds a listener for close or fatal error events on the connection
38
* handed out to a client.
40
public void addConnectionEventListener(ConnectionEventListener connectionEventListener)
42
listeners.add(connectionEventListener);
46
* Removes a listener for close or fatal error events on the connection
47
* handed out to a client.
49
public void removeConnectionEventListener(ConnectionEventListener connectionEventListener)
51
listeners.remove(connectionEventListener);
55
* Closes the physical database connection represented by this
56
* PooledConnection. If any client has a connection based on
57
* this PooledConnection, it is forcibly closed as well.
59
public void close() throws SQLException
64
if (!con.getAutoCommit())
70
catch (SQLException e)
85
* Gets a handle for a client to use. This is a wrapper around the
86
* physical connection, so the client can call close and it will just
87
* return the connection to the pool without really closing the
88
* pgysical connection.
90
* <p>According to the JDBC 2.0 Optional Package spec (6.2.3), only one
91
* client may have an active handle to the connection at a time, so if
92
* there is a previous handle active when this is called, the previous
93
* one is forcibly closed and its work rolled back.</p>
95
public Connection getConnection() throws SQLException
99
// Before throwing the exception, let's notify the registered listeners about the error
100
final SQLException sqlException = new SQLException("This PooledConnection has already been closed!");
101
fireConnectionFatalError(sqlException);
104
// If any error occures while opening a new connection, the listeners
105
// have to be notified. This gives a chance to connection pools to
106
// elliminate bad pooled connections.
109
// Only one connection can be open at a time from this PooledConnection. See JDBC 2.0 Optional Package spec section 6.2.3
113
if (!con.getAutoCommit())
119
catch (SQLException e)
124
con.setAutoCommit(autoCommit);
126
catch (SQLException sqlException)
128
fireConnectionFatalError(sqlException);
129
throw (SQLException)sqlException.fillInStackTrace();
131
ConnectionHandler handler = new ConnectionHandler(con);
133
Connection con = (Connection)Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Connection.class, PGConnection.class}, handler);
139
* Used to fire a connection closed event to all listeners.
141
void fireConnectionClosed()
143
ConnectionEvent evt = null;
144
// Copy the listener list so the listener can remove itself during this method call
145
ConnectionEventListener[] local = (ConnectionEventListener[]) listeners.toArray(new ConnectionEventListener[listeners.size()]);
146
for (int i = 0; i < local.length; i++)
148
ConnectionEventListener listener = local[i];
151
evt = new ConnectionEvent(this);
153
listener.connectionClosed(evt);
158
* Used to fire a connection error event to all listeners.
160
void fireConnectionFatalError(SQLException e)
162
ConnectionEvent evt = null;
163
// Copy the listener list so the listener can remove itself during this method call
164
ConnectionEventListener[] local = (ConnectionEventListener[])listeners.toArray(new ConnectionEventListener[listeners.size()]);
165
for (int i = 0; i < local.length; i++)
167
ConnectionEventListener listener = local[i];
170
evt = new ConnectionEvent(this, e);
172
listener.connectionErrorOccurred(evt);
177
* Instead of declaring a class implementing Connection, which would have
178
* to be updated for every JDK rev, use a dynamic proxy to handle all
179
* calls through the Connection interface. This is the part that
180
* requires JDK 1.3 or higher, though JDK 1.2 could be supported with a
181
* 3rd-party proxy package.
183
private class ConnectionHandler implements InvocationHandler
185
private Connection con;
186
private Connection proxy; // the Connection the client is currently using, which is a proxy
187
private boolean automatic = false;
189
public ConnectionHandler(Connection con)
194
public Object invoke(Object proxy, Method method, Object[] args)
198
if (method.getDeclaringClass().getName().equals("java.lang.Object"))
200
if (method.getName().equals("toString"))
202
return "Pooled connection wrapping physical connection " + con;
204
if (method.getName().equals("hashCode"))
206
return new Integer(con.hashCode());
208
if (method.getName().equals("equals"))
212
return Boolean.FALSE;
216
return Proxy.isProxyClass(args[0].getClass()) && ((ConnectionHandler) Proxy.getInvocationHandler(args[0])).con == con ? Boolean.TRUE : Boolean.FALSE;
218
catch (ClassCastException e)
220
return Boolean.FALSE;
225
return method.invoke(con, args);
227
catch (InvocationTargetException e)
229
throw e.getTargetException();
232
// All the rest is from the Connection or PGConnection interface
233
if (method.getName().equals("isClosed"))
235
return con == null ? Boolean.TRUE : Boolean.FALSE;
237
if (con == null && !method.getName().equals("close"))
239
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");
241
if (method.getName().equals("close"))
243
// we are already closed and a double close
248
SQLException ex = null;
249
if (!con.getAutoCommit())
255
catch (SQLException e)
264
fireConnectionClosed();
271
else if(method.getName().equals("createStatement"))
273
Statement st = (Statement)method.invoke(con, args);
274
return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Statement.class, org.postgresql.PGStatement.class}, new StatementHandler(this, st));
276
else if(method.getName().equals("prepareCall"))
278
Statement st = (Statement)method.invoke(con, args);
279
return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{CallableStatement.class, org.postgresql.PGStatement.class}, new StatementHandler(this, st));
281
else if(method.getName().equals("prepareStatement"))
283
Statement st = (Statement)method.invoke(con, args);
284
return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{PreparedStatement.class, org.postgresql.PGStatement.class}, new StatementHandler(this, st));
288
return method.invoke(con, args);
292
Connection getProxy() {
296
void setProxy(Connection proxy) {
308
// No close event fired here: see JDBC 2.0 Optional Package spec section 6.3
311
public boolean isClosed() {
317
* Instead of declaring classes implementing Statement, PreparedStatement,
318
* and CallableStatement, which would have to be updated for every JDK rev,
319
* use a dynamic proxy to handle all calls through the Statement
320
* interfaces. This is the part that requires JDK 1.3 or higher, though
321
* JDK 1.2 could be supported with a 3rd-party proxy package.
323
* The StatementHandler is required in order to return the proper
324
* Connection proxy for the getConnection method.
326
private static class StatementHandler implements InvocationHandler {
327
private ConnectionHandler con;
328
private Statement st;
330
public StatementHandler(ConnectionHandler con, Statement st) {
334
public Object invoke(Object proxy, Method method, Object[] args)
338
if (method.getDeclaringClass().getName().equals("java.lang.Object"))
340
if (method.getName().equals("toString"))
342
return "Pooled statement wrapping physical statement " + st;
344
if (method.getName().equals("hashCode"))
346
return new Integer(st.hashCode());
348
if (method.getName().equals("equals"))
352
return Boolean.FALSE;
356
return Proxy.isProxyClass(args[0].getClass()) && ((StatementHandler) Proxy.getInvocationHandler(args[0])).st == st ? Boolean.TRUE : Boolean.FALSE;
358
catch (ClassCastException e)
360
return Boolean.FALSE;
363
return method.invoke(st, args);
365
// All the rest is from the Statement interface
366
if (method.getName().equals("close"))
368
// closing an already closed object is a no-op
369
if (st == null || con.isClosed())
380
if (st == null || con.isClosed())
382
throw new SQLException("Statement has been closed");
384
else if (method.getName().equals("getConnection"))
386
return con.getProxy(); // the proxied connection, not a physical connection
392
return method.invoke(st, args);
394
catch (InvocationTargetException e)
396
throw e.getTargetException();