~ubuntu-branches/ubuntu/oneiric/libpgjava/oneiric

« back to all changes in this revision

Viewing changes to src/interfaces/jdbc/org/postgresql/jdbc2/optional/PoolingDataSource.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 javax.naming.*;
 
5
import java.util.*;
 
6
import java.sql.Connection;
 
7
import java.sql.SQLException;
 
8
 
 
9
/**
 
10
 * DataSource which uses connection pooling.  <font color="red">Don't use this if
 
11
 * your server/middleware vendor provides a connection pooling implementation
 
12
 * which interfaces with the PostgreSQL ConnectionPoolDataSource implementation!</font>
 
13
 * This class is provided as a convenience, but the JDBC Driver is really not
 
14
 * supposed to handle the connection pooling algorithm.  Instead, the server or
 
15
 * middleware product is supposed to handle the mechanics of connection pooling,
 
16
 * and use the PostgreSQL implementation of ConnectionPoolDataSource to provide
 
17
 * the connections to pool.
 
18
 *
 
19
 * <p>If you're sure you want to use this, then you must set the properties
 
20
 * dataSourceName, databaseName, user, and password (if required for the user).
 
21
 * The settings for serverName, portNumber, initialConnections, and
 
22
 * maxConnections are optional.  Note that <i>only connections
 
23
 * for the default user will be pooled!</i>  Connections for other users will
 
24
 * be normal non-pooled connections, and will not count against the maximum pool
 
25
 * size limit.</p>
 
26
 *
 
27
 * <p>If you put this DataSource in JNDI, and access it from different JVMs (or
 
28
 * otherwise load this class from different ClassLoaders), you'll end up with one
 
29
 * pool per ClassLoader or VM.  This is another area where a server-specific
 
30
 * implementation may provide advanced features, such as using a single pool
 
31
 * across all VMs in a cluster.</p>
 
32
 *
 
33
 * <p>This implementation supports JDK 1.3 and higher.</p>
 
34
 *
 
35
 * @author Aaron Mulder (ammulder@chariotsolutions.com)
 
36
 * @version $Revision: 1.3 $
 
37
 */
 
38
public class PoolingDataSource extends BaseDataSource implements DataSource
 
39
{
 
40
        private static Map dataSources = new HashMap();
 
41
 
 
42
        static PoolingDataSource getDataSource(String name)
 
43
        {
 
44
                return (PoolingDataSource)dataSources.get(name);
 
45
        }
 
46
 
 
47
        // Additional Data Source properties
 
48
        protected String dataSourceName;  // Must be protected for subclasses to sync updates to it
 
49
        private int initialConnections = 0;
 
50
        private int maxConnections = 0;
 
51
        // State variables
 
52
        private boolean initialized = false;
 
53
        private Stack available = new Stack();
 
54
        private Stack used = new Stack();
 
55
        private Object lock = new Object();
 
56
        private ConnectionPool source;
 
57
 
 
58
        /**
 
59
         * Gets a description of this DataSource.
 
60
         */
 
61
        public String getDescription()
 
62
        {
 
63
                return "Pooling DataSource '" + dataSourceName + " from " + org.postgresql.Driver.getVersion();
 
64
        }
 
65
 
 
66
        /**
 
67
         * Ensures the DataSource properties are not changed after the DataSource has
 
68
         * been used.
 
69
         *
 
70
         * @throws java.lang.IllegalStateException
 
71
         *                 The Server Name cannot be changed after the DataSource has been
 
72
         *                 used.
 
73
         */
 
74
        public void setServerName(String serverName)
 
75
        {
 
76
                if (initialized)
 
77
                {
 
78
                        throw new IllegalStateException("Cannot set Data Source properties after DataSource has been used");
 
79
                }
 
80
                super.setServerName(serverName);
 
81
        }
 
82
 
 
83
        /**
 
84
         * Ensures the DataSource properties are not changed after the DataSource has
 
85
         * been used.
 
86
         *
 
87
         * @throws java.lang.IllegalStateException
 
88
         *                 The Database Name cannot be changed after the DataSource has been
 
89
         *                 used.
 
90
         */
 
91
        public void setDatabaseName(String databaseName)
 
92
        {
 
93
                if (initialized)
 
94
                {
 
95
                        throw new IllegalStateException("Cannot set Data Source properties after DataSource has been used");
 
96
                }
 
97
                super.setDatabaseName(databaseName);
 
98
        }
 
99
 
 
100
        /**
 
101
         * Ensures the DataSource properties are not changed after the DataSource has
 
102
         * been used.
 
103
         *
 
104
         * @throws java.lang.IllegalStateException
 
105
         *                 The User cannot be changed after the DataSource has been
 
106
         *                 used.
 
107
         */
 
108
        public void setUser(String user)
 
109
        {
 
110
                if (initialized)
 
111
                {
 
112
                        throw new IllegalStateException("Cannot set Data Source properties after DataSource has been used");
 
113
                }
 
114
                super.setUser(user);
 
115
        }
 
116
 
 
117
        /**
 
118
         * Ensures the DataSource properties are not changed after the DataSource has
 
119
         * been used.
 
120
         *
 
121
         * @throws java.lang.IllegalStateException
 
122
         *                 The Password cannot be changed after the DataSource has been
 
123
         *                 used.
 
124
         */
 
125
        public void setPassword(String password)
 
126
        {
 
127
                if (initialized)
 
128
                {
 
129
                        throw new IllegalStateException("Cannot set Data Source properties after DataSource has been used");
 
130
                }
 
131
                super.setPassword(password);
 
132
        }
 
133
 
 
134
        /**
 
135
         * Ensures the DataSource properties are not changed after the DataSource has
 
136
         * been used.
 
137
         *
 
138
         * @throws java.lang.IllegalStateException
 
139
         *                 The Port Number cannot be changed after the DataSource has been
 
140
         *                 used.
 
141
         */
 
142
        public void setPortNumber(int portNumber)
 
143
        {
 
144
                if (initialized)
 
145
                {
 
146
                        throw new IllegalStateException("Cannot set Data Source properties after DataSource has been used");
 
147
                }
 
148
                super.setPortNumber(portNumber);
 
149
        }
 
150
 
 
151
        /**
 
152
         * Gets the number of connections that will be created when this DataSource
 
153
         * is initialized.      If you do not call initialize explicitly, it will be
 
154
         * initialized the first time a connection is drawn from it.
 
155
         */
 
156
        public int getInitialConnections()
 
157
        {
 
158
                return initialConnections;
 
159
        }
 
160
 
 
161
        /**
 
162
         * Sets the number of connections that will be created when this DataSource
 
163
         * is initialized.      If you do not call initialize explicitly, it will be
 
164
         * initialized the first time a connection is drawn from it.
 
165
         *
 
166
         * @throws java.lang.IllegalStateException
 
167
         *                 The Initial Connections cannot be changed after the DataSource has been
 
168
         *                 used.
 
169
         */
 
170
        public void setInitialConnections(int initialConnections)
 
171
        {
 
172
                if (initialized)
 
173
                {
 
174
                        throw new IllegalStateException("Cannot set Data Source properties after DataSource has been used");
 
175
                }
 
176
                this.initialConnections = initialConnections;
 
177
        }
 
178
 
 
179
        /**
 
180
         * Gets the maximum number of connections that the pool will allow.  If a request
 
181
         * comes in and this many connections are in use, the request will block until a
 
182
         * connection is available.  Note that connections for a user other than the
 
183
         * default user will not be pooled and don't count against this limit.
 
184
         *
 
185
         * @return The maximum number of pooled connection allowed, or 0 for no maximum.
 
186
         */
 
187
        public int getMaxConnections()
 
188
        {
 
189
                return maxConnections;
 
190
        }
 
191
 
 
192
        /**
 
193
         * Sets the maximum number of connections that the pool will allow.  If a request
 
194
         * comes in and this many connections are in use, the request will block until a
 
195
         * connection is available.  Note that connections for a user other than the
 
196
         * default user will not be pooled and don't count against this limit.
 
197
         *
 
198
         * @param maxConnections The maximum number of pooled connection to allow, or
 
199
         *                0 for no maximum.
 
200
         *
 
201
         * @throws java.lang.IllegalStateException
 
202
         *                 The Maximum Connections cannot be changed after the DataSource has been
 
203
         *                 used.
 
204
         */
 
205
        public void setMaxConnections(int maxConnections)
 
206
        {
 
207
                if (initialized)
 
208
                {
 
209
                        throw new IllegalStateException("Cannot set Data Source properties after DataSource has been used");
 
210
                }
 
211
                this.maxConnections = maxConnections;
 
212
        }
 
213
 
 
214
        /**
 
215
         * Gets the name of this DataSource.  This uniquely identifies the DataSource.
 
216
         * You cannot use more than one DataSource in the same VM with the same name.
 
217
         */
 
218
        public String getDataSourceName()
 
219
        {
 
220
                return dataSourceName;
 
221
        }
 
222
 
 
223
        /**
 
224
         * Sets the name of this DataSource.  This is required, and uniquely identifies
 
225
         * the DataSource.      You cannot create or use more than one DataSource in the
 
226
         * same VM with the same name.
 
227
         *
 
228
         * @throws java.lang.IllegalStateException
 
229
         *                 The Data Source Name cannot be changed after the DataSource has been
 
230
         *                 used.
 
231
         * @throws java.lang.IllegalArgumentException
 
232
         *                 Another PoolingDataSource with the same dataSourceName already
 
233
         *                 exists.
 
234
         */
 
235
        public void setDataSourceName(String dataSourceName)
 
236
        {
 
237
                if (initialized)
 
238
                {
 
239
                        throw new IllegalStateException("Cannot set Data Source properties after DataSource has been used");
 
240
                }
 
241
                if (this.dataSourceName != null && dataSourceName != null && dataSourceName.equals(this.dataSourceName))
 
242
                {
 
243
                        return ;
 
244
                }
 
245
                synchronized (dataSources)
 
246
                {
 
247
                        if (getDataSource(dataSourceName) != null)
 
248
                        {
 
249
                                throw new IllegalArgumentException("DataSource with name '" + dataSourceName + "' already exists!");
 
250
                        }
 
251
                        if (this.dataSourceName != null)
 
252
                        {
 
253
                                dataSources.remove(this.dataSourceName);
 
254
                        }
 
255
                        this.dataSourceName = dataSourceName;
 
256
                        dataSources.put(dataSourceName, this);
 
257
                }
 
258
        }
 
259
 
 
260
        /**
 
261
         * Initializes this DataSource.  If the initialConnections is greater than zero,
 
262
         * that number of connections will be created.  After this method is called,
 
263
         * the DataSource properties cannot be changed.  If you do not call this
 
264
         * explicitly, it will be called the first time you get a connection from the
 
265
         * DataSource.
 
266
         * @throws java.sql.SQLException
 
267
         *                 Occurs when the initialConnections is greater than zero, but the
 
268
         *                 DataSource is not able to create enough physical connections.
 
269
         */
 
270
        public void initialize() throws SQLException
 
271
        {
 
272
                synchronized (lock)
 
273
                {
 
274
                        source = createConnectionPool();
 
275
                        source.setDatabaseName(getDatabaseName());
 
276
                        source.setPassword(getPassword());
 
277
                        source.setPortNumber(getPortNumber());
 
278
                        source.setServerName(getServerName());
 
279
                        source.setUser(getUser());
 
280
                        while (available.size() < initialConnections)
 
281
                        {
 
282
                                available.push(source.getPooledConnection());
 
283
                        }
 
284
                        initialized = true;
 
285
                }
 
286
        }
 
287
 
 
288
    protected boolean isInitialized() {
 
289
        return initialized;
 
290
    }
 
291
 
 
292
    /**
 
293
     * Creates the appropriate ConnectionPool to use for this DataSource.
 
294
     */
 
295
    protected ConnectionPool createConnectionPool() {
 
296
        return new ConnectionPool();
 
297
    }
 
298
 
 
299
        /**
 
300
         * Gets a <b>non-pooled</b> connection, unless the user and password are the
 
301
         * same as the default values for this connection pool.
 
302
         *
 
303
         * @return A pooled connection.
 
304
         * @throws SQLException
 
305
         *                 Occurs when no pooled connection is available, and a new physical
 
306
         *                 connection cannot be created.
 
307
         */
 
308
        public Connection getConnection(String user, String password) throws SQLException
 
309
        {
 
310
                // If this is for the default user/password, use a pooled connection
 
311
                if (user == null ||
 
312
                                (user.equals(getUser()) && ((password == null && getPassword() == null) || (password != null && password.equals(getPassword())))))
 
313
                {
 
314
                        return getConnection();
 
315
                }
 
316
                // Otherwise, use a non-pooled connection
 
317
                if (!initialized)
 
318
                {
 
319
                        initialize();
 
320
                }
 
321
                return super.getConnection(user, password);
 
322
        }
 
323
 
 
324
        /**
 
325
         * Gets a connection from the connection pool.
 
326
         *
 
327
         * @return A pooled connection.
 
328
         * @throws SQLException
 
329
         *                 Occurs when no pooled connection is available, and a new physical
 
330
         *                 connection cannot be created.
 
331
         */
 
332
        public Connection getConnection() throws SQLException
 
333
        {
 
334
                if (!initialized)
 
335
                {
 
336
                        initialize();
 
337
                }
 
338
                return getPooledConnection();
 
339
        }
 
340
 
 
341
        /**
 
342
         * Closes this DataSource, and all the pooled connections, whether in use or not.
 
343
         */
 
344
        public void close()
 
345
        {
 
346
                synchronized (lock)
 
347
                {
 
348
                        while (available.size() > 0)
 
349
                        {
 
350
                                PooledConnectionImpl pci = (PooledConnectionImpl)available.pop();
 
351
                                try
 
352
                                {
 
353
                                        pci.close();
 
354
                                }
 
355
                                catch (SQLException e)
 
356
                                {}
 
357
                        }
 
358
                        available = null;
 
359
                        while (used.size() > 0)
 
360
                        {
 
361
                                PooledConnectionImpl pci = (PooledConnectionImpl)used.pop();
 
362
                                pci.removeConnectionEventListener(connectionEventListener);
 
363
                                try
 
364
                                {
 
365
                                        pci.close();
 
366
                                }
 
367
                                catch (SQLException e)
 
368
                                {}
 
369
                        }
 
370
                        used = null;
 
371
                }
 
372
        removeStoredDataSource();
 
373
    }
 
374
 
 
375
    protected void removeStoredDataSource() {
 
376
        synchronized (dataSources)
 
377
        {
 
378
            dataSources.remove(dataSourceName);
 
379
        }
 
380
    }
 
381
 
 
382
    /**
 
383
         * Gets a connection from the pool.  Will get an available one if
 
384
         * present, or create a new one if under the max limit.  Will
 
385
         * block if all used and a new one would exceed the max.
 
386
         */
 
387
        private Connection getPooledConnection() throws SQLException
 
388
        {
 
389
                PooledConnection pc = null;
 
390
                synchronized (lock)
 
391
                {
 
392
                        if (available == null)
 
393
                        {
 
394
                                throw new SQLException("DataSource has been closed.");
 
395
                        }
 
396
                        while (true)
 
397
                        {
 
398
                                if (available.size() > 0)
 
399
                                {
 
400
                                        pc = (PooledConnection)available.pop();
 
401
                                        used.push(pc);
 
402
                                        break;
 
403
                                }
 
404
                                if (maxConnections == 0 || used.size() < maxConnections)
 
405
                                {
 
406
                                        pc = source.getPooledConnection();
 
407
                                        used.push(pc);
 
408
                                        break;
 
409
                                }
 
410
                                else
 
411
                                {
 
412
                                        try
 
413
                                        {
 
414
                                                // Wake up every second at a minimum
 
415
                                                lock.wait(1000L);
 
416
                                        }
 
417
                                        catch (InterruptedException e)
 
418
                                        {}
 
419
                                }
 
420
                        }
 
421
                }
 
422
                pc.addConnectionEventListener(connectionEventListener);
 
423
                return pc.getConnection();
 
424
        }
 
425
 
 
426
        /**
 
427
         * Notified when a pooled connection is closed, or a fatal error occurs
 
428
         * on a pooled connection.      This is the only way connections are marked
 
429
         * as unused.
 
430
         */
 
431
        private ConnectionEventListener connectionEventListener = new ConnectionEventListener()
 
432
                        {
 
433
                                public void connectionClosed(ConnectionEvent event)
 
434
                                {
 
435
                                        ((PooledConnection)event.getSource()).removeConnectionEventListener(this);
 
436
                                        synchronized (lock)
 
437
                                        {
 
438
                                                if (available == null)
 
439
                                                {
 
440
                                                        return ; // DataSource has been closed
 
441
                                                }
 
442
                                                boolean removed = used.remove(event.getSource());
 
443
                                                if (removed)
 
444
                                                {
 
445
                                                        available.push(event.getSource());
 
446
                                                        // There's now a new connection available
 
447
                                                        lock.notify();
 
448
                                                }
 
449
                                                else
 
450
                                                {
 
451
                                                        // a connection error occured
 
452
                                                }
 
453
                                        }
 
454
                                }
 
455
 
 
456
                                /**
 
457
                                 * This is only called for fatal errors, where the physical connection is
 
458
                                 * useless afterward and should be removed from the pool.
 
459
                                 */
 
460
                                public void connectionErrorOccurred(ConnectionEvent event)
 
461
                                {
 
462
                                        ((PooledConnection) event.getSource()).removeConnectionEventListener(this);
 
463
                                        synchronized (lock)
 
464
                                        {
 
465
                                                if (available == null)
 
466
                                                {
 
467
                                                        return ; // DataSource has been closed
 
468
                                                }
 
469
                                                used.remove(event.getSource());
 
470
                                                // We're now at least 1 connection under the max
 
471
                                                lock.notify();
 
472
                                        }
 
473
                                }
 
474
                        };
 
475
 
 
476
        /**
 
477
         * Adds custom properties for this DataSource to the properties defined in
 
478
         * the superclass.
 
479
         */
 
480
        public Reference getReference() throws NamingException
 
481
        {
 
482
                Reference ref = super.getReference();
 
483
                ref.add(new StringRefAddr("dataSourceName", dataSourceName));
 
484
                if (initialConnections > 0)
 
485
                {
 
486
                        ref.add(new StringRefAddr("initialConnections", Integer.toString(initialConnections)));
 
487
                }
 
488
                if (maxConnections > 0)
 
489
                {
 
490
                        ref.add(new StringRefAddr("maxConnections", Integer.toString(maxConnections)));
 
491
                }
 
492
                return ref;
 
493
        }
 
494
}