1
package org.postgresql.jdbc2.optional;
6
import java.sql.Connection;
7
import java.sql.SQLException;
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.
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
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>
33
* <p>This implementation supports JDK 1.3 and higher.</p>
35
* @author Aaron Mulder (ammulder@chariotsolutions.com)
36
* @version $Revision: 1.3 $
38
public class PoolingDataSource extends BaseDataSource implements DataSource
40
private static Map dataSources = new HashMap();
42
static PoolingDataSource getDataSource(String name)
44
return (PoolingDataSource)dataSources.get(name);
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;
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;
59
* Gets a description of this DataSource.
61
public String getDescription()
63
return "Pooling DataSource '" + dataSourceName + " from " + org.postgresql.Driver.getVersion();
67
* Ensures the DataSource properties are not changed after the DataSource has
70
* @throws java.lang.IllegalStateException
71
* The Server Name cannot be changed after the DataSource has been
74
public void setServerName(String serverName)
78
throw new IllegalStateException("Cannot set Data Source properties after DataSource has been used");
80
super.setServerName(serverName);
84
* Ensures the DataSource properties are not changed after the DataSource has
87
* @throws java.lang.IllegalStateException
88
* The Database Name cannot be changed after the DataSource has been
91
public void setDatabaseName(String databaseName)
95
throw new IllegalStateException("Cannot set Data Source properties after DataSource has been used");
97
super.setDatabaseName(databaseName);
101
* Ensures the DataSource properties are not changed after the DataSource has
104
* @throws java.lang.IllegalStateException
105
* The User cannot be changed after the DataSource has been
108
public void setUser(String user)
112
throw new IllegalStateException("Cannot set Data Source properties after DataSource has been used");
118
* Ensures the DataSource properties are not changed after the DataSource has
121
* @throws java.lang.IllegalStateException
122
* The Password cannot be changed after the DataSource has been
125
public void setPassword(String password)
129
throw new IllegalStateException("Cannot set Data Source properties after DataSource has been used");
131
super.setPassword(password);
135
* Ensures the DataSource properties are not changed after the DataSource has
138
* @throws java.lang.IllegalStateException
139
* The Port Number cannot be changed after the DataSource has been
142
public void setPortNumber(int portNumber)
146
throw new IllegalStateException("Cannot set Data Source properties after DataSource has been used");
148
super.setPortNumber(portNumber);
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.
156
public int getInitialConnections()
158
return initialConnections;
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.
166
* @throws java.lang.IllegalStateException
167
* The Initial Connections cannot be changed after the DataSource has been
170
public void setInitialConnections(int initialConnections)
174
throw new IllegalStateException("Cannot set Data Source properties after DataSource has been used");
176
this.initialConnections = initialConnections;
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.
185
* @return The maximum number of pooled connection allowed, or 0 for no maximum.
187
public int getMaxConnections()
189
return maxConnections;
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.
198
* @param maxConnections The maximum number of pooled connection to allow, or
201
* @throws java.lang.IllegalStateException
202
* The Maximum Connections cannot be changed after the DataSource has been
205
public void setMaxConnections(int maxConnections)
209
throw new IllegalStateException("Cannot set Data Source properties after DataSource has been used");
211
this.maxConnections = maxConnections;
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.
218
public String getDataSourceName()
220
return dataSourceName;
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.
228
* @throws java.lang.IllegalStateException
229
* The Data Source Name cannot be changed after the DataSource has been
231
* @throws java.lang.IllegalArgumentException
232
* Another PoolingDataSource with the same dataSourceName already
235
public void setDataSourceName(String dataSourceName)
239
throw new IllegalStateException("Cannot set Data Source properties after DataSource has been used");
241
if (this.dataSourceName != null && dataSourceName != null && dataSourceName.equals(this.dataSourceName))
245
synchronized (dataSources)
247
if (getDataSource(dataSourceName) != null)
249
throw new IllegalArgumentException("DataSource with name '" + dataSourceName + "' already exists!");
251
if (this.dataSourceName != null)
253
dataSources.remove(this.dataSourceName);
255
this.dataSourceName = dataSourceName;
256
dataSources.put(dataSourceName, this);
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
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.
270
public void initialize() throws SQLException
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)
282
available.push(source.getPooledConnection());
288
protected boolean isInitialized() {
293
* Creates the appropriate ConnectionPool to use for this DataSource.
295
protected ConnectionPool createConnectionPool() {
296
return new ConnectionPool();
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.
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.
308
public Connection getConnection(String user, String password) throws SQLException
310
// If this is for the default user/password, use a pooled connection
312
(user.equals(getUser()) && ((password == null && getPassword() == null) || (password != null && password.equals(getPassword())))))
314
return getConnection();
316
// Otherwise, use a non-pooled connection
321
return super.getConnection(user, password);
325
* Gets a connection from the connection pool.
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.
332
public Connection getConnection() throws SQLException
338
return getPooledConnection();
342
* Closes this DataSource, and all the pooled connections, whether in use or not.
348
while (available.size() > 0)
350
PooledConnectionImpl pci = (PooledConnectionImpl)available.pop();
355
catch (SQLException e)
359
while (used.size() > 0)
361
PooledConnectionImpl pci = (PooledConnectionImpl)used.pop();
362
pci.removeConnectionEventListener(connectionEventListener);
367
catch (SQLException e)
372
removeStoredDataSource();
375
protected void removeStoredDataSource() {
376
synchronized (dataSources)
378
dataSources.remove(dataSourceName);
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.
387
private Connection getPooledConnection() throws SQLException
389
PooledConnection pc = null;
392
if (available == null)
394
throw new SQLException("DataSource has been closed.");
398
if (available.size() > 0)
400
pc = (PooledConnection)available.pop();
404
if (maxConnections == 0 || used.size() < maxConnections)
406
pc = source.getPooledConnection();
414
// Wake up every second at a minimum
417
catch (InterruptedException e)
422
pc.addConnectionEventListener(connectionEventListener);
423
return pc.getConnection();
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
431
private ConnectionEventListener connectionEventListener = new ConnectionEventListener()
433
public void connectionClosed(ConnectionEvent event)
435
((PooledConnection)event.getSource()).removeConnectionEventListener(this);
438
if (available == null)
440
return ; // DataSource has been closed
442
boolean removed = used.remove(event.getSource());
445
available.push(event.getSource());
446
// There's now a new connection available
451
// a connection error occured
457
* This is only called for fatal errors, where the physical connection is
458
* useless afterward and should be removed from the pool.
460
public void connectionErrorOccurred(ConnectionEvent event)
462
((PooledConnection) event.getSource()).removeConnectionEventListener(this);
465
if (available == null)
467
return ; // DataSource has been closed
469
used.remove(event.getSource());
470
// We're now at least 1 connection under the max
477
* Adds custom properties for this DataSource to the properties defined in
480
public Reference getReference() throws NamingException
482
Reference ref = super.getReference();
483
ref.add(new StringRefAddr("dataSourceName", dataSourceName));
484
if (initialConnections > 0)
486
ref.add(new StringRefAddr("initialConnections", Integer.toString(initialConnections)));
488
if (maxConnections > 0)
490
ref.add(new StringRefAddr("maxConnections", Integer.toString(maxConnections)));