1
/*-------------------------------------------------------------------------
3
* Copyright (c) 2004-2005, PostgreSQL Global Development Group
6
* $PostgreSQL: pgjdbc/org/postgresql/ds/jdbc23/AbstractJdbc23PooledConnection.java,v 1.1 2006/11/29 04:03:48 jurka Exp $
8
*-------------------------------------------------------------------------
10
package org.postgresql.ds.jdbc23;
15
import java.lang.reflect.*;
16
import org.postgresql.PGConnection;
17
import org.postgresql.util.GT;
18
import org.postgresql.util.PSQLException;
19
import org.postgresql.util.PSQLState;
22
* PostgreSQL implementation of the PooledConnection interface. This shouldn't
23
* be used directly, as the pooling client should just interact with the
24
* ConnectionPool instead.
25
* @see org.postgresql.ds.PGConnectionPoolDataSource
27
* @author Aaron Mulder (ammulder@chariotsolutions.com)
28
* @author Csaba Nagy (ncsaba@yahoo.com)
30
public abstract class AbstractJdbc23PooledConnection
32
private List listeners = new LinkedList();
33
private Connection con;
34
private ConnectionHandler last;
35
private final boolean autoCommit;
36
private final boolean isXA;
39
* Creates a new PooledConnection representing the specified physical
42
public AbstractJdbc23PooledConnection(Connection con, boolean autoCommit, boolean isXA)
45
this.autoCommit = autoCommit;
50
* Adds a listener for close or fatal error events on the connection
51
* handed out to a client.
53
public void addConnectionEventListener(ConnectionEventListener connectionEventListener)
55
listeners.add(connectionEventListener);
59
* Removes a listener for close or fatal error events on the connection
60
* handed out to a client.
62
public void removeConnectionEventListener(ConnectionEventListener connectionEventListener)
64
listeners.remove(connectionEventListener);
68
* Closes the physical database connection represented by this
69
* PooledConnection. If any client has a connection based on
70
* this PooledConnection, it is forcibly closed as well.
72
public void close() throws SQLException
77
if (!con.getAutoCommit())
83
catch (SQLException e)
99
* Gets a handle for a client to use. This is a wrapper around the
100
* physical connection, so the client can call close and it will just
101
* return the connection to the pool without really closing the
102
* pgysical connection.
104
* <p>According to the JDBC 2.0 Optional Package spec (6.2.3), only one
105
* client may have an active handle to the connection at a time, so if
106
* there is a previous handle active when this is called, the previous
107
* one is forcibly closed and its work rolled back.</p>
109
public Connection getConnection() throws SQLException
113
// Before throwing the exception, let's notify the registered listeners about the error
114
PSQLException sqlException = new PSQLException(GT.tr("This PooledConnection has already been closed."),
115
PSQLState.CONNECTION_DOES_NOT_EXIST);
116
fireConnectionFatalError(sqlException);
119
// If any error occures while opening a new connection, the listeners
120
// have to be notified. This gives a chance to connection pools to
121
// elliminate bad pooled connections.
124
// Only one connection can be open at a time from this PooledConnection. See JDBC 2.0 Optional Package spec section 6.2.3
128
if (!con.getAutoCommit())
134
catch (SQLException e)
140
con.setAutoCommit(autoCommit);
142
catch (SQLException sqlException)
144
fireConnectionFatalError(sqlException);
145
throw (SQLException)sqlException.fillInStackTrace();
147
ConnectionHandler handler = new ConnectionHandler(con);
150
Connection proxyCon = (Connection)Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Connection.class, PGConnection.class}, handler);
151
last.setProxy(proxyCon);
156
* Used to fire a connection closed event to all listeners.
158
void fireConnectionClosed()
160
ConnectionEvent evt = null;
161
// Copy the listener list so the listener can remove itself during this method call
162
ConnectionEventListener[] local = (ConnectionEventListener[]) listeners.toArray(new ConnectionEventListener[listeners.size()]);
163
for (int i = 0; i < local.length; i++)
165
ConnectionEventListener listener = local[i];
168
evt = createConnectionEvent(null);
170
listener.connectionClosed(evt);
175
* Used to fire a connection error event to all listeners.
177
void fireConnectionFatalError(SQLException e)
179
ConnectionEvent evt = null;
180
// Copy the listener list so the listener can remove itself during this method call
181
ConnectionEventListener[] local = (ConnectionEventListener[])listeners.toArray(new ConnectionEventListener[listeners.size()]);
182
for (int i = 0; i < local.length; i++)
184
ConnectionEventListener listener = local[i];
187
evt = createConnectionEvent(e);
189
listener.connectionErrorOccurred(evt);
193
protected abstract ConnectionEvent createConnectionEvent(SQLException e);
195
// Classes we consider fatal.
196
private static String[] fatalClasses = {
197
"08", // connection error
198
"53", // insufficient resources
200
// nb: not just "57" as that includes query cancel which is nonfatal
201
"57P01", // admin shutdown
202
"57P02", // crash shutdown
203
"57P03", // cannot connect now
205
"58", // system error (backend)
206
"60", // system error (driver)
207
"99", // unexpected error
208
"F0", // configuration file error (backend)
209
"XX", // internal error (backend)
212
private static boolean isFatalState(String state) {
213
if (state == null) // no info, assume fatal
215
if (state.length() < 2) // no class info, assume fatal
218
for (int i = 0; i < fatalClasses.length; ++i)
219
if (state.startsWith(fatalClasses[i]))
220
return true; // fatal
226
* Fires a connection error event, but only if we
227
* think the exception is fatal.
229
* @param e the SQLException to consider
231
private void fireConnectionError(SQLException e)
233
if (!isFatalState(e.getSQLState()))
236
fireConnectionFatalError(e);
240
* Instead of declaring a class implementing Connection, which would have
241
* to be updated for every JDK rev, use a dynamic proxy to handle all
242
* calls through the Connection interface. This is the part that
243
* requires JDK 1.3 or higher, though JDK 1.2 could be supported with a
244
* 3rd-party proxy package.
246
private class ConnectionHandler implements InvocationHandler
248
private Connection con;
249
private Connection proxy; // the Connection the client is currently using, which is a proxy
250
private boolean automatic = false;
252
public ConnectionHandler(Connection con)
257
public Object invoke(Object proxy, Method method, Object[] args)
261
if (method.getDeclaringClass().getName().equals("java.lang.Object"))
263
if (method.getName().equals("toString"))
265
return "Pooled connection wrapping physical connection " + con;
267
if (method.getName().equals("hashCode"))
269
return new Integer(con.hashCode());
271
if (method.getName().equals("equals"))
275
return Boolean.FALSE;
279
return Proxy.isProxyClass(args[0].getClass()) && ((ConnectionHandler) Proxy.getInvocationHandler(args[0])).con == con ? Boolean.TRUE : Boolean.FALSE;
281
catch (ClassCastException e)
283
return Boolean.FALSE;
288
return method.invoke(con, args);
290
catch (InvocationTargetException e)
292
throw e.getTargetException();
295
// All the rest is from the Connection or PGConnection interface
296
if (method.getName().equals("isClosed"))
298
return con == null ? Boolean.TRUE : Boolean.FALSE;
300
if (con == null && !method.getName().equals("close"))
302
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."),
303
PSQLState.CONNECTION_DOES_NOT_EXIST);
305
if (method.getName().equals("close"))
307
// we are already closed and a double close
312
SQLException ex = null;
313
if (!isXA && !con.getAutoCommit())
319
catch (SQLException e)
328
fireConnectionClosed();
336
// From here on in, we invoke via reflection, catch exceptions,
337
// and check if they're fatal before rethrowing.
340
if (method.getName().equals("createStatement"))
342
Statement st = (Statement)method.invoke(con, args);
343
return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Statement.class, org.postgresql.PGStatement.class}, new StatementHandler(this, st));
345
else if (method.getName().equals("prepareCall"))
347
Statement st = (Statement)method.invoke(con, args);
348
return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{CallableStatement.class, org.postgresql.PGStatement.class}, new StatementHandler(this, st));
350
else if (method.getName().equals("prepareStatement"))
352
Statement st = (Statement)method.invoke(con, args);
353
return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{PreparedStatement.class, org.postgresql.PGStatement.class}, new StatementHandler(this, st));
357
return method.invoke(con, args);
359
} catch (InvocationTargetException e) {
360
Throwable te = e.getTargetException();
361
if (te instanceof SQLException)
362
fireConnectionError((SQLException)te); // Tell listeners about exception if it's fatal
367
Connection getProxy() {
371
void setProxy(Connection proxy) {
383
// No close event fired here: see JDBC 2.0 Optional Package spec section 6.3
386
public boolean isClosed() {
392
* Instead of declaring classes implementing Statement, PreparedStatement,
393
* and CallableStatement, which would have to be updated for every JDK rev,
394
* use a dynamic proxy to handle all calls through the Statement
395
* interfaces. This is the part that requires JDK 1.3 or higher, though
396
* JDK 1.2 could be supported with a 3rd-party proxy package.
398
* The StatementHandler is required in order to return the proper
399
* Connection proxy for the getConnection method.
401
private class StatementHandler implements InvocationHandler {
402
private AbstractJdbc23PooledConnection.ConnectionHandler con;
403
private Statement st;
405
public StatementHandler(AbstractJdbc23PooledConnection.ConnectionHandler con, Statement st) {
409
public Object invoke(Object proxy, Method method, Object[] args)
413
if (method.getDeclaringClass().getName().equals("java.lang.Object"))
415
if (method.getName().equals("toString"))
417
return "Pooled statement wrapping physical statement " + st;
419
if (method.getName().equals("hashCode"))
421
return new Integer(st.hashCode());
423
if (method.getName().equals("equals"))
427
return Boolean.FALSE;
431
return Proxy.isProxyClass(args[0].getClass()) && ((StatementHandler) Proxy.getInvocationHandler(args[0])).st == st ? Boolean.TRUE : Boolean.FALSE;
433
catch (ClassCastException e)
435
return Boolean.FALSE;
438
return method.invoke(st, args);
440
// All the rest is from the Statement interface
441
if (method.getName().equals("close"))
443
// closing an already closed object is a no-op
444
if (st == null || con.isClosed())
458
if (st == null || con.isClosed())
460
throw new PSQLException(GT.tr("Statement has been closed."),
461
PSQLState.OBJECT_NOT_IN_STATE);
464
if (method.getName().equals("getConnection"))
466
return con.getProxy(); // the proxied connection, not a physical connection
471
return method.invoke(st, args);
472
} catch (InvocationTargetException e) {
473
Throwable te = e.getTargetException();
474
if (te instanceof SQLException)
475
fireConnectionError((SQLException)te); // Tell listeners about exception if it's fatal