~ubuntu-branches/ubuntu/wily/libpgjava/wily

« back to all changes in this revision

Viewing changes to org/postgresql/ds/jdbc23/AbstractJdbc23PooledConnection.java

  • Committer: Bazaar Package Importer
  • Author(s): Michael Koch
  • Date: 2008-04-26 22:01:11 UTC
  • mfrom: (3.1.4 gutsy)
  • Revision ID: james.westby@ubuntu.com-20080426220111-yasgxtas5smx2qm3
Tags: 8.2-504-2
* Updated description to mention PostgreSQL 8.3 as supported.
  Closes: #398348
* Removed libpgjava transitional package. Closes: #477557
* Moved debhelper and cdbs from Build-Depends-Indep to Build-Depends.
* Added Homepage, Vcs-Svn and Vcs-Browser fields.
* Added watch file.
* Added myself to Uploaders.
* Removed Stafan and Wolfgang from Uploaders.
* Updated Standards-Version to 3.7.3
* Updated debhelper level to 5.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*-------------------------------------------------------------------------
 
2
*
 
3
* Copyright (c) 2004-2005, PostgreSQL Global Development Group
 
4
*
 
5
* IDENTIFICATION
 
6
*   $PostgreSQL: pgjdbc/org/postgresql/ds/jdbc23/AbstractJdbc23PooledConnection.java,v 1.1 2006/11/29 04:03:48 jurka Exp $
 
7
*
 
8
*-------------------------------------------------------------------------
 
9
*/
 
10
package org.postgresql.ds.jdbc23;
 
11
 
 
12
import javax.sql.*;
 
13
import java.sql.*;
 
14
import java.util.*;
 
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;
 
20
 
 
21
/**
 
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
 
26
 *
 
27
 * @author Aaron Mulder (ammulder@chariotsolutions.com)
 
28
 * @author Csaba Nagy (ncsaba@yahoo.com)
 
29
 */
 
30
public abstract class AbstractJdbc23PooledConnection
 
31
{
 
32
    private List listeners = new LinkedList();
 
33
    private Connection con;
 
34
    private ConnectionHandler last;
 
35
    private final boolean autoCommit;
 
36
    private final boolean isXA;
 
37
 
 
38
    /**
 
39
     * Creates a new PooledConnection representing the specified physical
 
40
     * connection.
 
41
     */
 
42
    public AbstractJdbc23PooledConnection(Connection con, boolean autoCommit, boolean isXA)
 
43
    {
 
44
        this.con = con;
 
45
        this.autoCommit = autoCommit;
 
46
        this.isXA = isXA;
 
47
    }
 
48
 
 
49
    /**
 
50
     * Adds a listener for close or fatal error events on the connection
 
51
     * handed out to a client.
 
52
     */
 
53
    public void addConnectionEventListener(ConnectionEventListener connectionEventListener)
 
54
    {
 
55
        listeners.add(connectionEventListener);
 
56
    }
 
57
 
 
58
    /**
 
59
     * Removes a listener for close or fatal error events on the connection
 
60
     * handed out to a client.
 
61
     */
 
62
    public void removeConnectionEventListener(ConnectionEventListener connectionEventListener)
 
63
    {
 
64
        listeners.remove(connectionEventListener);
 
65
    }
 
66
 
 
67
    /**
 
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.
 
71
     */
 
72
    public void close() throws SQLException
 
73
    {
 
74
        if (last != null)
 
75
        {
 
76
            last.close();
 
77
            if (!con.getAutoCommit())
 
78
            {
 
79
                try
 
80
                {
 
81
                    con.rollback();
 
82
                }
 
83
                catch (SQLException e)
 
84
                {
 
85
                }
 
86
            }
 
87
        }
 
88
        try
 
89
        {
 
90
            con.close();
 
91
        }
 
92
        finally
 
93
        {
 
94
            con = null;
 
95
        }
 
96
    }
 
97
 
 
98
    /**
 
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.
 
103
     *
 
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>
 
108
     */
 
109
    public Connection getConnection() throws SQLException
 
110
    {
 
111
        if (con == null)
 
112
        {
 
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);
 
117
            throw sqlException;
 
118
        }
 
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.
 
122
        try
 
123
        {
 
124
            // Only one connection can be open at a time from this PooledConnection.  See JDBC 2.0 Optional Package spec section 6.2.3
 
125
            if (last != null)
 
126
            {
 
127
                last.close();
 
128
                if (!con.getAutoCommit())
 
129
                {
 
130
                    try
 
131
                    {
 
132
                        con.rollback();
 
133
                    }
 
134
                    catch (SQLException e)
 
135
                    {
 
136
                    }
 
137
                }
 
138
                con.clearWarnings();
 
139
            }
 
140
            con.setAutoCommit(autoCommit);
 
141
        }
 
142
        catch (SQLException sqlException)
 
143
        {
 
144
            fireConnectionFatalError(sqlException);
 
145
            throw (SQLException)sqlException.fillInStackTrace();
 
146
        }
 
147
        ConnectionHandler handler = new ConnectionHandler(con);
 
148
        last = handler;
 
149
 
 
150
        Connection proxyCon = (Connection)Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Connection.class, PGConnection.class}, handler);
 
151
        last.setProxy(proxyCon);
 
152
        return proxyCon;
 
153
    }
 
154
 
 
155
    /**
 
156
     * Used to fire a connection closed event to all listeners.
 
157
     */
 
158
    void fireConnectionClosed()
 
159
    {
 
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++)
 
164
        {
 
165
            ConnectionEventListener listener = local[i];
 
166
            if (evt == null)
 
167
            {
 
168
                evt = createConnectionEvent(null);
 
169
            }
 
170
            listener.connectionClosed(evt);
 
171
        }
 
172
    }
 
173
 
 
174
    /**
 
175
     * Used to fire a connection error event to all listeners.
 
176
     */
 
177
    void fireConnectionFatalError(SQLException e)
 
178
    {
 
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++)
 
183
        {
 
184
            ConnectionEventListener listener = local[i];
 
185
            if (evt == null)
 
186
            {
 
187
                evt = createConnectionEvent(e);
 
188
            }
 
189
            listener.connectionErrorOccurred(evt);
 
190
        }
 
191
    }
 
192
 
 
193
    protected abstract ConnectionEvent createConnectionEvent(SQLException e);
 
194
 
 
195
    // Classes we consider fatal.
 
196
    private static String[] fatalClasses = {
 
197
        "08",  // connection error
 
198
        "53",  // insufficient resources
 
199
 
 
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
 
204
 
 
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)
 
210
    };
 
211
 
 
212
    private static boolean isFatalState(String state) {
 
213
        if (state == null)      // no info, assume fatal
 
214
            return true;
 
215
        if (state.length() < 2) // no class info, assume fatal
 
216
            return true;
 
217
 
 
218
        for (int i = 0; i < fatalClasses.length; ++i)
 
219
            if (state.startsWith(fatalClasses[i]))
 
220
                return true; // fatal
 
221
 
 
222
        return false;
 
223
    }
 
224
 
 
225
    /**
 
226
     * Fires a connection error event, but only if we
 
227
     * think the exception is fatal.
 
228
     *
 
229
     * @param e the SQLException to consider
 
230
     */    
 
231
    private void fireConnectionError(SQLException e) 
 
232
    {
 
233
        if (!isFatalState(e.getSQLState()))
 
234
            return;
 
235
 
 
236
        fireConnectionFatalError(e);
 
237
    }
 
238
 
 
239
    /**
 
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.
 
245
     */
 
246
    private class ConnectionHandler implements InvocationHandler
 
247
    {
 
248
        private Connection con;
 
249
        private Connection proxy; // the Connection the client is currently using, which is a proxy
 
250
        private boolean automatic = false;
 
251
 
 
252
        public ConnectionHandler(Connection con)
 
253
        {
 
254
            this.con = con;
 
255
        }
 
256
 
 
257
        public Object invoke(Object proxy, Method method, Object[] args)
 
258
        throws Throwable
 
259
        {
 
260
            // From Object
 
261
            if (method.getDeclaringClass().getName().equals("java.lang.Object"))
 
262
            {
 
263
                if (method.getName().equals("toString"))
 
264
                {
 
265
                    return "Pooled connection wrapping physical connection " + con;
 
266
                }
 
267
                if (method.getName().equals("hashCode"))
 
268
                {
 
269
                    return new Integer(con.hashCode());
 
270
                }
 
271
                if (method.getName().equals("equals"))
 
272
                {
 
273
                    if (args[0] == null)
 
274
                    {
 
275
                        return Boolean.FALSE;
 
276
                    }
 
277
                    try
 
278
                    {
 
279
                        return Proxy.isProxyClass(args[0].getClass()) && ((ConnectionHandler) Proxy.getInvocationHandler(args[0])).con == con ? Boolean.TRUE : Boolean.FALSE;
 
280
                    }
 
281
                    catch (ClassCastException e)
 
282
                    {
 
283
                        return Boolean.FALSE;
 
284
                    }
 
285
                }
 
286
                try
 
287
                {
 
288
                    return method.invoke(con, args);
 
289
                }
 
290
                catch (InvocationTargetException e)
 
291
                {
 
292
                    throw e.getTargetException();
 
293
                }
 
294
            }
 
295
            // All the rest is from the Connection or PGConnection interface
 
296
            if (method.getName().equals("isClosed"))
 
297
            {
 
298
                return con == null ? Boolean.TRUE : Boolean.FALSE;
 
299
            }
 
300
            if (con == null && !method.getName().equals("close"))
 
301
            {
 
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);
 
304
            }
 
305
            if (method.getName().equals("close"))
 
306
            {
 
307
                // we are already closed and a double close
 
308
                // is not an error.
 
309
                if (con == null)
 
310
                    return null;
 
311
 
 
312
                SQLException ex = null;
 
313
                if (!isXA && !con.getAutoCommit())
 
314
                {
 
315
                    try
 
316
                    {
 
317
                        con.rollback();
 
318
                    }
 
319
                    catch (SQLException e)
 
320
                    {
 
321
                        ex = e;
 
322
                    }
 
323
                }
 
324
                con.clearWarnings();
 
325
                con = null;
 
326
                proxy = null;
 
327
                last = null;
 
328
                fireConnectionClosed();
 
329
                if (ex != null)
 
330
                {
 
331
                    throw ex;
 
332
                }
 
333
                return null;
 
334
            }
 
335
            
 
336
            // From here on in, we invoke via reflection, catch exceptions,
 
337
            // and check if they're fatal before rethrowing.
 
338
 
 
339
            try {            
 
340
                if (method.getName().equals("createStatement"))
 
341
                {
 
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));
 
344
                }
 
345
                else if (method.getName().equals("prepareCall"))
 
346
                {
 
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));
 
349
                }
 
350
                else if (method.getName().equals("prepareStatement"))
 
351
                {
 
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));
 
354
                }
 
355
                else
 
356
                {
 
357
                    return method.invoke(con, args);
 
358
                }
 
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
 
363
                throw te;
 
364
            }
 
365
        }
 
366
 
 
367
        Connection getProxy() {
 
368
            return proxy;
 
369
        }
 
370
 
 
371
        void setProxy(Connection proxy) {
 
372
            this.proxy = proxy;
 
373
        }
 
374
 
 
375
        public void close()
 
376
        {
 
377
            if (con != null)
 
378
            {
 
379
                automatic = true;
 
380
            }
 
381
            con = null;
 
382
            proxy = null;
 
383
            // No close event fired here: see JDBC 2.0 Optional Package spec section 6.3
 
384
        }
 
385
 
 
386
        public boolean isClosed() {
 
387
            return con == null;
 
388
        }
 
389
    }
 
390
 
 
391
    /**
 
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.
 
397
     *
 
398
     * The StatementHandler is required in order to return the proper
 
399
     * Connection proxy for the getConnection method.
 
400
     */
 
401
    private class StatementHandler implements InvocationHandler {
 
402
        private AbstractJdbc23PooledConnection.ConnectionHandler con;
 
403
        private Statement st;
 
404
 
 
405
        public StatementHandler(AbstractJdbc23PooledConnection.ConnectionHandler con, Statement st) {
 
406
            this.con = con;
 
407
            this.st = st;
 
408
        }
 
409
        public Object invoke(Object proxy, Method method, Object[] args)
 
410
        throws Throwable
 
411
        {
 
412
            // From Object
 
413
            if (method.getDeclaringClass().getName().equals("java.lang.Object"))
 
414
            {
 
415
                if (method.getName().equals("toString"))
 
416
                {
 
417
                    return "Pooled statement wrapping physical statement " + st;
 
418
                }
 
419
                if (method.getName().equals("hashCode"))
 
420
                {
 
421
                    return new Integer(st.hashCode());
 
422
                }
 
423
                if (method.getName().equals("equals"))
 
424
                {
 
425
                    if (args[0] == null)
 
426
                    {
 
427
                        return Boolean.FALSE;
 
428
                    }
 
429
                    try
 
430
                    {
 
431
                        return Proxy.isProxyClass(args[0].getClass()) && ((StatementHandler) Proxy.getInvocationHandler(args[0])).st == st ? Boolean.TRUE : Boolean.FALSE;
 
432
                    }
 
433
                    catch (ClassCastException e)
 
434
                    {
 
435
                        return Boolean.FALSE;
 
436
                    }
 
437
                }
 
438
                return method.invoke(st, args);
 
439
            }
 
440
            // All the rest is from the Statement interface
 
441
            if (method.getName().equals("close"))
 
442
            {
 
443
                // closing an already closed object is a no-op
 
444
                if (st == null || con.isClosed())
 
445
                    return null;
 
446
 
 
447
                try
 
448
                {
 
449
                    st.close();
 
450
                }
 
451
                finally
 
452
                {
 
453
                    con = null;
 
454
                    st = null;
 
455
                }
 
456
                return null;
 
457
            }
 
458
            if (st == null || con.isClosed())
 
459
            {
 
460
                throw new PSQLException(GT.tr("Statement has been closed."),
 
461
                                        PSQLState.OBJECT_NOT_IN_STATE);
 
462
            }
 
463
            
 
464
            if (method.getName().equals("getConnection"))
 
465
            {
 
466
                return con.getProxy(); // the proxied connection, not a physical connection
 
467
            }
 
468
 
 
469
            try
 
470
            {
 
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
 
476
                throw te;
 
477
            }
 
478
        }
 
479
    }
 
480
}