~ubuntu-branches/ubuntu/hardy/libpgjava/hardy

« back to all changes in this revision

Viewing changes to src/interfaces/jdbc/org/postgresql/jdbc2/optional/PooledConnectionImpl.java

  • Committer: Bazaar Package Importer
  • Author(s): Arnaud Vandyck
  • Date: 2005-04-21 14:25:11 UTC
  • mfrom: (1.2.1 upstream) (2.1.1 warty)
  • Revision ID: james.westby@ubuntu.com-20050421142511-wibh5vc31fkrorx7
Tags: 7.4.7-3
Built with sources...

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
package org.postgresql.jdbc2.optional;
 
2
 
 
3
import javax.sql.*;
 
4
import java.sql.*;
 
5
import java.util.*;
 
6
import java.lang.reflect.*;
 
7
import org.postgresql.PGConnection;
 
8
 
 
9
/**
 
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.
 
13
 * @see ConnectionPool
 
14
 *
 
15
 * @author Aaron Mulder (ammulder@chariotsolutions.com)
 
16
 * @author Csaba Nagy (ncsaba@yahoo.com)
 
17
 * @version $Revision: 1.7.4.3 $
 
18
 */
 
19
public class PooledConnectionImpl implements PooledConnection
 
20
{
 
21
        private List listeners = new LinkedList();
 
22
        private Connection con;
 
23
        private ConnectionHandler last;
 
24
        private boolean autoCommit;
 
25
 
 
26
        /**
 
27
         * Creates a new PooledConnection representing the specified physical
 
28
         * connection.
 
29
         */
 
30
        protected PooledConnectionImpl(Connection con, boolean autoCommit)
 
31
        {
 
32
                this.con = con;
 
33
                this.autoCommit = autoCommit;
 
34
        }
 
35
 
 
36
        /**
 
37
         * Adds a listener for close or fatal error events on the connection
 
38
         * handed out to a client.
 
39
         */
 
40
        public void addConnectionEventListener(ConnectionEventListener connectionEventListener)
 
41
        {
 
42
                listeners.add(connectionEventListener);
 
43
        }
 
44
 
 
45
        /**
 
46
         * Removes a listener for close or fatal error events on the connection
 
47
         * handed out to a client.
 
48
         */
 
49
        public void removeConnectionEventListener(ConnectionEventListener connectionEventListener)
 
50
        {
 
51
                listeners.remove(connectionEventListener);
 
52
        }
 
53
 
 
54
        /**
 
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.
 
58
         */
 
59
        public void close() throws SQLException
 
60
        {
 
61
                if (last != null)
 
62
                {
 
63
                        last.close();
 
64
                        if (!con.getAutoCommit())
 
65
                        {
 
66
                                try
 
67
                                {
 
68
                                        con.rollback();
 
69
                                }
 
70
                                catch (SQLException e)
 
71
                                {}
 
72
                        }
 
73
                }
 
74
                try
 
75
                {
 
76
                        con.close();
 
77
                }
 
78
                finally
 
79
                {
 
80
                        con = null;
 
81
                }
 
82
        }
 
83
 
 
84
        /**
 
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.
 
89
         *
 
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>
 
94
         */
 
95
        public Connection getConnection() throws SQLException
 
96
        {
 
97
                if (con == null)
 
98
                {
 
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);
 
102
                        throw sqlException;
 
103
                }
 
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.
 
107
                try
 
108
                {
 
109
                        // Only one connection can be open at a time from this PooledConnection.  See JDBC 2.0 Optional Package spec section 6.2.3
 
110
                        if (last != null)
 
111
                        {
 
112
                                last.close();
 
113
                                if (!con.getAutoCommit())
 
114
                                {
 
115
                                        try
 
116
                                        {
 
117
                                                con.rollback();
 
118
                                        }
 
119
                                        catch (SQLException e)
 
120
                                        {}
 
121
                                }
 
122
                                con.clearWarnings();
 
123
                        }
 
124
                        con.setAutoCommit(autoCommit);
 
125
                }
 
126
                catch (SQLException sqlException)
 
127
                {
 
128
                        fireConnectionFatalError(sqlException);
 
129
                        throw (SQLException)sqlException.fillInStackTrace();
 
130
                }
 
131
                ConnectionHandler handler = new ConnectionHandler(con);
 
132
                last = handler;
 
133
                Connection con = (Connection)Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Connection.class, PGConnection.class}, handler);
 
134
                last.setProxy(con);
 
135
                return con;
 
136
        }
 
137
 
 
138
        /**
 
139
         * Used to fire a connection closed event to all listeners.
 
140
         */
 
141
        void fireConnectionClosed()
 
142
        {
 
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++)
 
147
                {
 
148
                        ConnectionEventListener listener = local[i];
 
149
                        if (evt == null)
 
150
                        {
 
151
                                evt = new ConnectionEvent(this);
 
152
                        }
 
153
                        listener.connectionClosed(evt);
 
154
                }
 
155
        }
 
156
 
 
157
        /**
 
158
         * Used to fire a connection error event to all listeners.
 
159
         */
 
160
        void fireConnectionFatalError(SQLException e)
 
161
        {
 
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++)
 
166
                {
 
167
                        ConnectionEventListener listener = local[i];
 
168
                        if (evt == null)
 
169
                        {
 
170
                                evt = new ConnectionEvent(this, e);
 
171
                        }
 
172
                        listener.connectionErrorOccurred(evt);
 
173
                }
 
174
        }
 
175
 
 
176
        /**
 
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.
 
182
         */
 
183
        private class ConnectionHandler implements InvocationHandler
 
184
        {
 
185
                private Connection con;
 
186
        private Connection proxy; // the Connection the client is currently using, which is a proxy
 
187
                private boolean automatic = false;
 
188
 
 
189
                public ConnectionHandler(Connection con)
 
190
                {
 
191
                        this.con = con;
 
192
                }
 
193
 
 
194
                public Object invoke(Object proxy, Method method, Object[] args)
 
195
                throws Throwable
 
196
                {
 
197
                        // From Object
 
198
                        if (method.getDeclaringClass().getName().equals("java.lang.Object"))
 
199
                        {
 
200
                                if (method.getName().equals("toString"))
 
201
                                {
 
202
                                        return "Pooled connection wrapping physical connection " + con;
 
203
                                }
 
204
                                if (method.getName().equals("hashCode"))
 
205
                                {
 
206
                                        return new Integer(con.hashCode());
 
207
                                }
 
208
                                if (method.getName().equals("equals"))
 
209
                                {
 
210
                                        if (args[0] == null)
 
211
                                        {
 
212
                                                return Boolean.FALSE;
 
213
                                        }
 
214
                                        try
 
215
                                        {
 
216
                                                return Proxy.isProxyClass(args[0].getClass()) && ((ConnectionHandler) Proxy.getInvocationHandler(args[0])).con == con ? Boolean.TRUE : Boolean.FALSE;
 
217
                                        }
 
218
                                        catch (ClassCastException e)
 
219
                                        {
 
220
                                                return Boolean.FALSE;
 
221
                                        }
 
222
                                }
 
223
                                try
 
224
                                {
 
225
                                    return method.invoke(con, args);
 
226
                                }
 
227
                                catch (InvocationTargetException e)
 
228
                                {
 
229
                                    throw e.getTargetException();
 
230
                                }
 
231
                        }
 
232
                        // All the rest is from the Connection or PGConnection interface
 
233
                        if (method.getName().equals("isClosed"))
 
234
                        {
 
235
                                return con == null ? Boolean.TRUE : Boolean.FALSE;
 
236
                        }
 
237
                        if (con == null && !method.getName().equals("close"))
 
238
                        {
 
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");
 
240
                        }
 
241
                        if (method.getName().equals("close"))
 
242
                        {
 
243
                                // we are already closed and a double close
 
244
                                // is not an error.
 
245
                                if (con == null)
 
246
                                        return null;
 
247
 
 
248
                                SQLException ex = null;
 
249
                                if (!con.getAutoCommit())
 
250
                                {
 
251
                                        try
 
252
                                        {
 
253
                                                con.rollback();
 
254
                                        }
 
255
                                        catch (SQLException e)
 
256
                                        {
 
257
                                                ex = e;
 
258
                                        }
 
259
                                }
 
260
                                con.clearWarnings();
 
261
                                con = null;
 
262
                proxy = null;
 
263
                                last = null;
 
264
                                fireConnectionClosed();
 
265
                                if (ex != null)
 
266
                                {
 
267
                                        throw ex;
 
268
                                }
 
269
                                return null;
 
270
                        }
 
271
            else if(method.getName().equals("createStatement"))
 
272
            {
 
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));
 
275
            }
 
276
            else if(method.getName().equals("prepareCall"))
 
277
            {
 
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));
 
280
            }
 
281
            else if(method.getName().equals("prepareStatement"))
 
282
            {
 
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));
 
285
            }
 
286
                        else
 
287
                        {
 
288
                                return method.invoke(con, args);
 
289
                        }
 
290
                }
 
291
 
 
292
        Connection getProxy() {
 
293
            return proxy;
 
294
        }
 
295
 
 
296
        void setProxy(Connection proxy) {
 
297
            this.proxy = proxy;
 
298
        }
 
299
 
 
300
                public void close()
 
301
                {
 
302
                        if (con != null)
 
303
                        {
 
304
                                automatic = true;
 
305
                        }
 
306
                        con = null;
 
307
            proxy = null;
 
308
                        // No close event fired here: see JDBC 2.0 Optional Package spec section 6.3
 
309
                }
 
310
 
 
311
        public boolean isClosed() {
 
312
            return con == null;
 
313
        }
 
314
        }
 
315
 
 
316
    /**
 
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.
 
322
     *
 
323
     * The StatementHandler is required in order to return the proper
 
324
     * Connection proxy for the getConnection method.
 
325
     */
 
326
    private static class StatementHandler implements InvocationHandler {
 
327
        private ConnectionHandler con;
 
328
        private Statement st;
 
329
 
 
330
        public StatementHandler(ConnectionHandler con, Statement st) {
 
331
            this.con = con;
 
332
            this.st = st;
 
333
        }
 
334
        public Object invoke(Object proxy, Method method, Object[] args)
 
335
        throws Throwable
 
336
        {
 
337
            // From Object
 
338
            if (method.getDeclaringClass().getName().equals("java.lang.Object"))
 
339
            {
 
340
                if (method.getName().equals("toString"))
 
341
                {
 
342
                    return "Pooled statement wrapping physical statement " + st;
 
343
                }
 
344
                if (method.getName().equals("hashCode"))
 
345
                {
 
346
                    return new Integer(st.hashCode());
 
347
                }
 
348
                if (method.getName().equals("equals"))
 
349
                {
 
350
                    if (args[0] == null)
 
351
                    {
 
352
                        return Boolean.FALSE;
 
353
                    }
 
354
                    try
 
355
                    {
 
356
                        return Proxy.isProxyClass(args[0].getClass()) && ((StatementHandler) Proxy.getInvocationHandler(args[0])).st == st ? Boolean.TRUE : Boolean.FALSE;
 
357
                    }
 
358
                    catch (ClassCastException e)
 
359
                    {
 
360
                        return Boolean.FALSE;
 
361
                    }
 
362
                }
 
363
                return method.invoke(st, args);
 
364
            }
 
365
            // All the rest is from the Statement interface
 
366
            if (method.getName().equals("close"))
 
367
            {
 
368
                // closing an already closed object is a no-op
 
369
                if (st == null || con.isClosed())
 
370
                    return null;
 
371
 
 
372
                try {
 
373
                    st.close();
 
374
                } finally {
 
375
                    con = null;
 
376
                    st = null;
 
377
                }
 
378
                return null;
 
379
            }
 
380
            if (st == null || con.isClosed())
 
381
            {
 
382
                throw new SQLException("Statement has been closed");
 
383
            }
 
384
            else if (method.getName().equals("getConnection"))
 
385
            {
 
386
                return con.getProxy(); // the proxied connection, not a physical connection
 
387
            }
 
388
            else
 
389
            {
 
390
                try
 
391
                {
 
392
                    return method.invoke(st, args);
 
393
                }
 
394
                catch (InvocationTargetException e)
 
395
                {
 
396
                    throw e.getTargetException();
 
397
                }
 
398
            }
 
399
        }
 
400
    }
 
401
}