mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-12-09 08:10:09 +08:00
Added DataSource code and tests submitted by Aaron Mulder
This commit is contained in:
parent
6410c22265
commit
c82fed3d87
@ -0,0 +1,224 @@
|
||||
package org.postgresql.jdbc2.optional;
|
||||
|
||||
import javax.naming.*;
|
||||
import java.io.PrintWriter;
|
||||
import java.sql.*;
|
||||
|
||||
/**
|
||||
* Base class for data sources and related classes.
|
||||
*
|
||||
* @author Aaron Mulder (ammulder@chariotsolutions.com)
|
||||
* @version $Revision: 1.1 $
|
||||
*/
|
||||
public abstract class BaseDataSource implements Referenceable {
|
||||
// Load the normal driver, since we'll use it to actually connect to the
|
||||
// database. That way we don't have to maintain the connecting code in
|
||||
// multiple places.
|
||||
static {
|
||||
try {
|
||||
Class.forName("org.postgresql.Driver");
|
||||
} catch (ClassNotFoundException e) {
|
||||
System.err.println("PostgreSQL DataSource unable to load PostgreSQL JDBC Driver");
|
||||
}
|
||||
}
|
||||
|
||||
// Needed to implement the DataSource/ConnectionPoolDataSource interfaces
|
||||
private transient PrintWriter logger;
|
||||
// Don't track loginTimeout, since we'd just ignore it anyway
|
||||
|
||||
// Standard properties, defined in the JDBC 2.0 Optional Package spec
|
||||
private String serverName = "localhost";
|
||||
private String databaseName;
|
||||
private String user;
|
||||
private String password;
|
||||
private int portNumber;
|
||||
|
||||
/**
|
||||
* Gets a connection to the PostgreSQL database. The database is identified by the
|
||||
* DataSource properties serverName, databaseName, and portNumber. The user to
|
||||
* connect as is identified by the DataSource properties user and password.
|
||||
*
|
||||
* @return A valid database connection.
|
||||
* @throws SQLException
|
||||
* Occurs when the database connection cannot be established.
|
||||
*/
|
||||
public Connection getConnection() throws SQLException {
|
||||
return getConnection(user, password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a connection to the PostgreSQL database. The database is identified by the
|
||||
* DataAource properties serverName, databaseName, and portNumber. The user to
|
||||
* connect as is identified by the arguments user and password, which override
|
||||
* the DataSource properties by the same name.
|
||||
*
|
||||
* @return A valid database connection.
|
||||
* @throws SQLException
|
||||
* Occurs when the database connection cannot be established.
|
||||
*/
|
||||
public Connection getConnection(String user, String password) throws SQLException {
|
||||
try {
|
||||
Connection con = DriverManager.getConnection(getUrl(), user, password);
|
||||
if (logger != null) {
|
||||
logger.println("Created a non-pooled connection for " + user + " at " + getUrl());
|
||||
}
|
||||
return con;
|
||||
} catch (SQLException e) {
|
||||
if (logger != null) {
|
||||
logger.println("Failed to create a non-pooled connection for " + user + " at " + getUrl() + ": " + e);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This DataSource does not support a configurable login timeout.
|
||||
* @return 0
|
||||
*/
|
||||
public int getLoginTimeout() throws SQLException {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* This DataSource does not support a configurable login timeout. Any value
|
||||
* provided here will be ignored.
|
||||
*/
|
||||
public void setLoginTimeout(int i) throws SQLException {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the log writer used to log connections opened.
|
||||
*/
|
||||
public PrintWriter getLogWriter() throws SQLException {
|
||||
return logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* The DataSource will note every connection opened to the provided log writer.
|
||||
*/
|
||||
public void setLogWriter(PrintWriter printWriter) throws SQLException {
|
||||
logger = printWriter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the host the PostgreSQL database is running on.
|
||||
*/
|
||||
public String getServerName() {
|
||||
return serverName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of the host the PostgreSQL database is running on. If this
|
||||
* is changed, it will only affect future calls to getConnection. The default
|
||||
* value is <tt>localhost</tt>.
|
||||
*/
|
||||
public void setServerName(String serverName) {
|
||||
if(serverName == null || serverName.equals("")) {
|
||||
this.serverName = "localhost";
|
||||
} else {
|
||||
this.serverName = serverName;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the PostgreSQL database, running on the server identified
|
||||
* by the serverName property.
|
||||
*/
|
||||
public String getDatabaseName() {
|
||||
return databaseName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of the PostgreSQL database, running on the server identified
|
||||
* by the serverName property. If this is changed, it will only affect
|
||||
* future calls to getConnection.
|
||||
*/
|
||||
public void setDatabaseName(String databaseName) {
|
||||
this.databaseName = databaseName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a description of this DataSource-ish thing. Must be customized by
|
||||
* subclasses.
|
||||
*/
|
||||
public abstract String getDescription();
|
||||
|
||||
/**
|
||||
* Gets the user to connect as by default. If this is not specified, you must
|
||||
* use the getConnection method which takes a user and password as parameters.
|
||||
*/
|
||||
public String getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the user to connect as by default. If this is not specified, you must
|
||||
* use the getConnection method which takes a user and password as parameters.
|
||||
* If this is changed, it will only affect future calls to getConnection.
|
||||
*/
|
||||
public void setUser(String user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the password to connect with by default. If this is not specified but a
|
||||
* password is needed to log in, you must use the getConnection method which takes
|
||||
* a user and password as parameters.
|
||||
*/
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the password to connect with by default. If this is not specified but a
|
||||
* password is needed to log in, you must use the getConnection method which takes
|
||||
* a user and password as parameters. If this is changed, it will only affect
|
||||
* future calls to getConnection.
|
||||
*/
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the port which the PostgreSQL server is listening on for TCP/IP
|
||||
* connections.
|
||||
*
|
||||
* @return The port, or 0 if the default port will be used.
|
||||
*/
|
||||
public int getPortNumber() {
|
||||
return portNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the port which the PostgreSQL server is listening on for TCP/IP
|
||||
* connections. Be sure the -i flag is passed to postmaster when PostgreSQL
|
||||
* is started. If this is not set, or set to 0, the default port will be used.
|
||||
*/
|
||||
public void setPortNumber(int portNumber) {
|
||||
this.portNumber = portNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a DriverManager URL from the other properties supplied.
|
||||
*/
|
||||
private String getUrl() {
|
||||
return "jdbc:postgresql://"+serverName+(portNumber == 0 ? "" : ":"+portNumber)+"/"+databaseName;
|
||||
}
|
||||
|
||||
public Reference getReference() throws NamingException {
|
||||
Reference ref = new Reference(getClass().getName(), PGObjectFactory.class.getName(), null);
|
||||
ref.add(new StringRefAddr("serverName", serverName));
|
||||
if (portNumber != 0) {
|
||||
ref.add(new StringRefAddr("portNumber", Integer.toString(portNumber)));
|
||||
}
|
||||
ref.add(new StringRefAddr("databaseName", databaseName));
|
||||
if (user != null) {
|
||||
ref.add(new StringRefAddr("user", user));
|
||||
}
|
||||
if (password != null) {
|
||||
ref.add(new StringRefAddr("password", password));
|
||||
}
|
||||
return ref;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package org.postgresql.jdbc2.optional;
|
||||
|
||||
import javax.sql.ConnectionPoolDataSource;
|
||||
import javax.sql.PooledConnection;
|
||||
import java.sql.SQLException;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* PostgreSQL implementation of ConnectionPoolDataSource. The app server or
|
||||
* middleware vendor should provide a DataSource implementation that takes advantage
|
||||
* of this ConnectionPoolDataSource. If not, you can use the PostgreSQL implementation
|
||||
* known as PoolingDataSource, but that should only be used if your server or middleware
|
||||
* vendor does not provide their own. Why? The server may want to reuse the same
|
||||
* Connection across all EJBs requesting a Connection within the same Transaction, or
|
||||
* provide other similar advanced features.
|
||||
*
|
||||
* <p>In any case, in order to use this ConnectionPoolDataSource, you must set the property
|
||||
* databaseName. The settings for serverName, portNumber, user, and password are
|
||||
* optional. Note: these properties are declared in the superclass.</p>
|
||||
*
|
||||
* <p>This implementation supports JDK 1.3 and higher.</p>
|
||||
*
|
||||
* @author Aaron Mulder (ammulder@chariotsolutions.com)
|
||||
* @version $Revision: 1.1 $
|
||||
*/
|
||||
public class ConnectionPool extends BaseDataSource implements Serializable, ConnectionPoolDataSource {
|
||||
private boolean defaultAutoCommit = false;
|
||||
|
||||
/**
|
||||
* Gets a description of this DataSource.
|
||||
*/
|
||||
public String getDescription() {
|
||||
return "ConnectionPoolDataSource from "+org.postgresql.Driver.getVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a connection which may be pooled by the app server or middleware
|
||||
* implementation of DataSource.
|
||||
*
|
||||
* @throws java.sql.SQLException
|
||||
* Occurs when the physical database connection cannot be established.
|
||||
*/
|
||||
public PooledConnection getPooledConnection() throws SQLException {
|
||||
return new PooledConnectionImpl(getConnection(), defaultAutoCommit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a connection which may be pooled by the app server or middleware
|
||||
* implementation of DataSource.
|
||||
*
|
||||
* @throws java.sql.SQLException
|
||||
* Occurs when the physical database connection cannot be established.
|
||||
*/
|
||||
public PooledConnection getPooledConnection(String user, String password) throws SQLException {
|
||||
return new PooledConnectionImpl(getConnection(user, password), defaultAutoCommit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether connections supplied by this pool will have autoCommit
|
||||
* turned on by default. The default value is <tt>false</tt>, so that
|
||||
* autoCommit will be turned off by default.
|
||||
*/
|
||||
public boolean isDefaultAutoCommit() {
|
||||
return defaultAutoCommit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether connections supplied by this pool will have autoCommit
|
||||
* turned on by default. The default value is <tt>false</tt>, so that
|
||||
* autoCommit will be turned off by default.
|
||||
*/
|
||||
public void setDefaultAutoCommit(boolean defaultAutoCommit) {
|
||||
this.defaultAutoCommit = defaultAutoCommit;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
package org.postgresql.jdbc2.optional;
|
||||
|
||||
import javax.naming.spi.ObjectFactory;
|
||||
import javax.naming.*;
|
||||
import java.util.Hashtable;
|
||||
|
||||
/**
|
||||
* Returns a DataSource-ish thing based on a JNDI reference. In the case of a
|
||||
* SimpleDataSource or ConnectionPool, a new instance is created each time, as
|
||||
* there is no connection state to maintain. In the case of a PoolingDataSource,
|
||||
* the same DataSource will be returned for every invocation within the same
|
||||
* VM/ClassLoader, so that the state of the connections in the pool will be
|
||||
* consistent.
|
||||
*
|
||||
* @author Aaron Mulder (ammulder@chariotsolutions.com)
|
||||
* @version $Revision: 1.1 $
|
||||
*/
|
||||
public class PGObjectFactory implements ObjectFactory {
|
||||
/**
|
||||
* Dereferences a PostgreSQL DataSource. Other types of references are
|
||||
* ignored.
|
||||
*/
|
||||
public Object getObjectInstance(Object obj, Name name, Context nameCtx,
|
||||
Hashtable environment) throws Exception {
|
||||
Reference ref = (Reference)obj;
|
||||
if(ref.getClassName().equals(SimpleDataSource.class.getName())) {
|
||||
return loadSimpleDataSource(ref);
|
||||
} else if (ref.getClassName().equals(ConnectionPool.class.getName())) {
|
||||
return loadConnectionPool(ref);
|
||||
} else if (ref.getClassName().equals(PoolingDataSource.class.getName())) {
|
||||
return loadPoolingDataSource(ref);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Object loadPoolingDataSource(Reference ref) {
|
||||
// If DataSource exists, return it
|
||||
String name = getProperty(ref, "dataSourceName");
|
||||
PoolingDataSource pds = PoolingDataSource.getDataSource(name);
|
||||
if(pds != null) {
|
||||
return pds;
|
||||
}
|
||||
// Otherwise, create a new one
|
||||
pds = new PoolingDataSource();
|
||||
pds.setDataSourceName(name);
|
||||
loadBaseDataSource(pds, ref);
|
||||
String min = getProperty(ref, "initialConnections");
|
||||
if (min != null) {
|
||||
pds.setInitialConnections(Integer.parseInt(min));
|
||||
}
|
||||
String max = getProperty(ref, "maxConnections");
|
||||
if (max != null) {
|
||||
pds.setMaxConnections(Integer.parseInt(max));
|
||||
}
|
||||
return pds;
|
||||
}
|
||||
|
||||
private Object loadSimpleDataSource(Reference ref) {
|
||||
SimpleDataSource ds = new SimpleDataSource();
|
||||
return loadBaseDataSource(ds, ref);
|
||||
}
|
||||
|
||||
private Object loadConnectionPool(Reference ref) {
|
||||
ConnectionPool cp = new ConnectionPool();
|
||||
return loadBaseDataSource(cp, ref);
|
||||
}
|
||||
|
||||
private Object loadBaseDataSource(BaseDataSource ds, Reference ref) {
|
||||
ds.setDatabaseName(getProperty(ref, "databaseName"));
|
||||
ds.setPassword(getProperty(ref, "password"));
|
||||
String port = getProperty(ref, "portNumber");
|
||||
if(port != null) {
|
||||
ds.setPortNumber(Integer.parseInt(port));
|
||||
}
|
||||
ds.setServerName(getProperty(ref, "serverName"));
|
||||
ds.setUser(getProperty(ref, "user"));
|
||||
return ds;
|
||||
}
|
||||
|
||||
private String getProperty(Reference ref, String s) {
|
||||
RefAddr addr = ref.get(s);
|
||||
if(addr == null) {
|
||||
return null;
|
||||
}
|
||||
return (String)addr.getContent();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,199 @@
|
||||
package org.postgresql.jdbc2.optional;
|
||||
|
||||
import javax.sql.*;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Connection;
|
||||
import java.util.*;
|
||||
import java.lang.reflect.*;
|
||||
|
||||
/**
|
||||
* PostgreSQL implementation of the PooledConnection interface. This shouldn't
|
||||
* be used directly, as the pooling client should just interact with the
|
||||
* ConnectionPool instead.
|
||||
* @see ConnectionPool
|
||||
*
|
||||
* @author Aaron Mulder (ammulder@chariotsolutions.com)
|
||||
* @version $Revision: 1.1 $
|
||||
*/
|
||||
public class PooledConnectionImpl implements PooledConnection {
|
||||
private List listeners = new LinkedList();
|
||||
private Connection con;
|
||||
private ConnectionHandler last;
|
||||
private boolean autoCommit;
|
||||
|
||||
/**
|
||||
* Creates a new PooledConnection representing the specified physical
|
||||
* connection.
|
||||
*/
|
||||
PooledConnectionImpl(Connection con, boolean autoCommit) {
|
||||
this.con = con;
|
||||
this.autoCommit = autoCommit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener for close or fatal error events on the connection
|
||||
* handed out to a client.
|
||||
*/
|
||||
public void addConnectionEventListener(ConnectionEventListener connectionEventListener) {
|
||||
listeners.add(connectionEventListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a listener for close or fatal error events on the connection
|
||||
* handed out to a client.
|
||||
*/
|
||||
public void removeConnectionEventListener(ConnectionEventListener connectionEventListener) {
|
||||
listeners.remove(connectionEventListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the physical database connection represented by this
|
||||
* PooledConnection. If any client has a connection based on
|
||||
* this PooledConnection, it is forcibly closed as well.
|
||||
*/
|
||||
public void close() throws SQLException {
|
||||
if(last != null) {
|
||||
last.close();
|
||||
if(!con.getAutoCommit()) {
|
||||
try {con.rollback();} catch (SQLException e) {}
|
||||
}
|
||||
}
|
||||
try {
|
||||
con.close();
|
||||
} finally {
|
||||
con = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a handle for a client to use. This is a wrapper around the
|
||||
* physical connection, so the client can call close and it will just
|
||||
* return the connection to the pool without really closing the
|
||||
* pgysical connection.
|
||||
*
|
||||
* <p>According to the JDBC 2.0 Optional Package spec (6.2.3), only one
|
||||
* client may have an active handle to the connection at a time, so if
|
||||
* there is a previous handle active when this is called, the previous
|
||||
* one is forcibly closed and its work rolled back.</p>
|
||||
*/
|
||||
public Connection getConnection() throws SQLException {
|
||||
if(con == null) {
|
||||
throw new SQLException("This PooledConnection has already been closed!");
|
||||
}
|
||||
// Only one connection can be open at a time from this PooledConnection. See JDBC 2.0 Optional Package spec section 6.2.3
|
||||
if(last != null) {
|
||||
last.close();
|
||||
if(!con.getAutoCommit()) {
|
||||
try {con.rollback();} catch(SQLException e) {}
|
||||
}
|
||||
con.clearWarnings();
|
||||
}
|
||||
con.setAutoCommit(autoCommit);
|
||||
ConnectionHandler handler = new ConnectionHandler(con);
|
||||
last = handler;
|
||||
return (Connection)Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Connection.class}, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to fire a connection event to all listeners.
|
||||
*/
|
||||
void fireConnectionClosed() {
|
||||
ConnectionEvent evt = null;
|
||||
// Copy the listener list so the listener can remove itself during this method call
|
||||
ConnectionEventListener[] local = (ConnectionEventListener[]) listeners.toArray(new ConnectionEventListener[listeners.size()]);
|
||||
for (int i = 0; i < local.length; i++) {
|
||||
ConnectionEventListener listener = local[i];
|
||||
if (evt == null) {
|
||||
evt = new ConnectionEvent(this);
|
||||
}
|
||||
listener.connectionClosed(evt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to fire a connection event to all listeners.
|
||||
*/
|
||||
void fireConnectionFatalError(SQLException e) {
|
||||
ConnectionEvent evt = null;
|
||||
// Copy the listener list so the listener can remove itself during this method call
|
||||
ConnectionEventListener[] local = (ConnectionEventListener[])listeners.toArray(new ConnectionEventListener[listeners.size()]);
|
||||
for (int i=0; i<local.length; i++) {
|
||||
ConnectionEventListener listener = local[i];
|
||||
if (evt == null) {
|
||||
evt = new ConnectionEvent(this, e);
|
||||
}
|
||||
listener.connectionErrorOccurred(evt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Instead of declaring a class implementing Connection, which would have
|
||||
* to be updated for every JDK rev, use a dynamic proxy to handle all
|
||||
* calls through the Connection interface. This is the part that
|
||||
* requires JDK 1.3 or higher, though JDK 1.2 could be supported with a
|
||||
* 3rd-party proxy package.
|
||||
*/
|
||||
private class ConnectionHandler implements InvocationHandler {
|
||||
private Connection con;
|
||||
private boolean automatic = false;
|
||||
|
||||
public ConnectionHandler(Connection con) {
|
||||
this.con = con;
|
||||
}
|
||||
|
||||
public Object invoke(Object proxy, Method method, Object[] args)
|
||||
throws Throwable {
|
||||
// From Object
|
||||
if(method.getDeclaringClass().getName().equals("java.lang.Object")) {
|
||||
if(method.getName().equals("toString")) {
|
||||
return "Pooled connection wrapping physical connection "+con;
|
||||
}
|
||||
if(method.getName().equals("hashCode")) {
|
||||
return new Integer(con.hashCode());
|
||||
}
|
||||
if(method.getName().equals("equals")) {
|
||||
if(args[0] == null) {
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
try {
|
||||
return Proxy.isProxyClass(args[0].getClass()) && ((ConnectionHandler) Proxy.getInvocationHandler(args[0])).con == con ? Boolean.TRUE : Boolean.FALSE;
|
||||
} catch(ClassCastException e) {
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
}
|
||||
return method.invoke(con, args);
|
||||
}
|
||||
// All the rest is from the Connection interface
|
||||
if(method.getName().equals("isClosed")) {
|
||||
return con == null ? Boolean.TRUE : Boolean.FALSE;
|
||||
}
|
||||
if(con == null) {
|
||||
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");
|
||||
}
|
||||
if(method.getName().equals("close")) {
|
||||
SQLException ex = null;
|
||||
if(!con.getAutoCommit()) {
|
||||
try {con.rollback();} catch(SQLException e) {ex = e;}
|
||||
}
|
||||
con.clearWarnings();
|
||||
con = null;
|
||||
last = null;
|
||||
fireConnectionClosed();
|
||||
if(ex != null) {
|
||||
throw ex;
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
return method.invoke(con, args);
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if(con != null) {
|
||||
automatic = true;
|
||||
}
|
||||
con = null;
|
||||
// No close event fired here: see JDBC 2.0 Optional Package spec section 6.3
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,413 @@
|
||||
package org.postgresql.jdbc2.optional;
|
||||
|
||||
import javax.sql.*;
|
||||
import javax.naming.*;
|
||||
import java.util.*;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* DataSource which uses connection pooling. <font color="red">Don't use this if
|
||||
* your server/middleware vendor provides a connection pooling implementation
|
||||
* which interfaces with the PostgreSQL ConnectionPoolDataSource implementation!</font>
|
||||
* This class is provided as a convenience, but the JDBC Driver is really not
|
||||
* supposed to handle the connection pooling algorithm. Instead, the server or
|
||||
* middleware product is supposed to handle the mechanics of connection pooling,
|
||||
* and use the PostgreSQL implementation of ConnectionPoolDataSource to provide
|
||||
* the connections to pool.
|
||||
*
|
||||
* <p>If you're sure you want to use this, then you must set the properties
|
||||
* dataSourceName, databaseName, user, and password (if required for the user).
|
||||
* The settings for serverName, portNumber, initialConnections, and
|
||||
* maxConnections are optional. Note that <i>only connections
|
||||
* for the default user will be pooled!</i> Connections for other users will
|
||||
* be normal non-pooled connections, and will not count against the maximum pool
|
||||
* size limit.</p>
|
||||
*
|
||||
* <p>If you put this DataSource in JNDI, and access it from different JVMs (or
|
||||
* otherwise load this class from different ClassLoaders), you'll end up with one
|
||||
* pool per ClassLoader or VM. This is another area where a server-specific
|
||||
* implementation may provide advanced features, such as using a single pool
|
||||
* across all VMs in a cluster.</p>
|
||||
*
|
||||
* <p>This implementation supports JDK 1.3 and higher.</p>
|
||||
*
|
||||
* @author Aaron Mulder (ammulder@chariotsolutions.com)
|
||||
* @version $Revision: 1.1 $
|
||||
*/
|
||||
public class PoolingDataSource extends BaseDataSource implements DataSource {
|
||||
private static Map dataSources = new HashMap();
|
||||
|
||||
static PoolingDataSource getDataSource(String name) {
|
||||
return (PoolingDataSource)dataSources.get(name);
|
||||
}
|
||||
|
||||
// Additional Data Source properties
|
||||
private String dataSourceName;
|
||||
private int initialConnections = 0;
|
||||
private int maxConnections = 0;
|
||||
// State variables
|
||||
private boolean initialized = false;
|
||||
private Stack available = new Stack();
|
||||
private Stack used = new Stack();
|
||||
private Object lock = new Object();
|
||||
private ConnectionPool source;
|
||||
|
||||
/**
|
||||
* Gets a description of this DataSource.
|
||||
*/
|
||||
public String getDescription() {
|
||||
return "Pooling DataSource '"+dataSourceName+" from "+org.postgresql.Driver.getVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the DataSource properties are not changed after the DataSource has
|
||||
* been used.
|
||||
*
|
||||
* @throws java.lang.IllegalStateException
|
||||
* The Server Name cannot be changed after the DataSource has been
|
||||
* used.
|
||||
*/
|
||||
public void setServerName(String serverName) {
|
||||
if (initialized) {
|
||||
throw new IllegalStateException("Cannot set Data Source properties after DataSource has been used");
|
||||
}
|
||||
super.setServerName(serverName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the DataSource properties are not changed after the DataSource has
|
||||
* been used.
|
||||
*
|
||||
* @throws java.lang.IllegalStateException
|
||||
* The Database Name cannot be changed after the DataSource has been
|
||||
* used.
|
||||
*/
|
||||
public void setDatabaseName(String databaseName) {
|
||||
if (initialized) {
|
||||
throw new IllegalStateException("Cannot set Data Source properties after DataSource has been used");
|
||||
}
|
||||
super.setDatabaseName(databaseName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the DataSource properties are not changed after the DataSource has
|
||||
* been used.
|
||||
*
|
||||
* @throws java.lang.IllegalStateException
|
||||
* The User cannot be changed after the DataSource has been
|
||||
* used.
|
||||
*/
|
||||
public void setUser(String user) {
|
||||
if (initialized) {
|
||||
throw new IllegalStateException("Cannot set Data Source properties after DataSource has been used");
|
||||
}
|
||||
super.setUser(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the DataSource properties are not changed after the DataSource has
|
||||
* been used.
|
||||
*
|
||||
* @throws java.lang.IllegalStateException
|
||||
* The Password cannot be changed after the DataSource has been
|
||||
* used.
|
||||
*/
|
||||
public void setPassword(String password) {
|
||||
if (initialized) {
|
||||
throw new IllegalStateException("Cannot set Data Source properties after DataSource has been used");
|
||||
}
|
||||
super.setPassword(password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the DataSource properties are not changed after the DataSource has
|
||||
* been used.
|
||||
*
|
||||
* @throws java.lang.IllegalStateException
|
||||
* The Port Number cannot be changed after the DataSource has been
|
||||
* used.
|
||||
*/
|
||||
public void setPortNumber(int portNumber) {
|
||||
if (initialized) {
|
||||
throw new IllegalStateException("Cannot set Data Source properties after DataSource has been used");
|
||||
}
|
||||
super.setPortNumber(portNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of connections that will be created when this DataSource
|
||||
* is initialized. If you do not call initialize explicitly, it will be
|
||||
* initialized the first time a connection is drawn from it.
|
||||
*/
|
||||
public int getInitialConnections() {
|
||||
return initialConnections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of connections that will be created when this DataSource
|
||||
* is initialized. If you do not call initialize explicitly, it will be
|
||||
* initialized the first time a connection is drawn from it.
|
||||
*
|
||||
* @throws java.lang.IllegalStateException
|
||||
* The Initial Connections cannot be changed after the DataSource has been
|
||||
* used.
|
||||
*/
|
||||
public void setInitialConnections(int initialConnections) {
|
||||
if (initialized) {
|
||||
throw new IllegalStateException("Cannot set Data Source properties after DataSource has been used");
|
||||
}
|
||||
this.initialConnections = initialConnections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maximum number of connections that the pool will allow. If a request
|
||||
* comes in and this many connections are in use, the request will block until a
|
||||
* connection is available. Note that connections for a user other than the
|
||||
* default user will not be pooled and don't count against this limit.
|
||||
*
|
||||
* @return The maximum number of pooled connection allowed, or 0 for no maximum.
|
||||
*/
|
||||
public int getMaxConnections() {
|
||||
return maxConnections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum number of connections that the pool will allow. If a request
|
||||
* comes in and this many connections are in use, the request will block until a
|
||||
* connection is available. Note that connections for a user other than the
|
||||
* default user will not be pooled and don't count against this limit.
|
||||
*
|
||||
* @param maxConnections The maximum number of pooled connection to allow, or
|
||||
* 0 for no maximum.
|
||||
*
|
||||
* @throws java.lang.IllegalStateException
|
||||
* The Maximum Connections cannot be changed after the DataSource has been
|
||||
* used.
|
||||
*/
|
||||
public void setMaxConnections(int maxConnections) {
|
||||
if (initialized) {
|
||||
throw new IllegalStateException("Cannot set Data Source properties after DataSource has been used");
|
||||
}
|
||||
this.maxConnections = maxConnections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of this DataSource. This uniquely identifies the DataSource.
|
||||
* You cannot use more than one DataSource in the same VM with the same name.
|
||||
*/
|
||||
public String getDataSourceName() {
|
||||
return dataSourceName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of this DataSource. This is required, and uniquely identifies
|
||||
* the DataSource. You cannot create or use more than one DataSource in the
|
||||
* same VM with the same name.
|
||||
*
|
||||
* @throws java.lang.IllegalStateException
|
||||
* The Data Source Name cannot be changed after the DataSource has been
|
||||
* used.
|
||||
* @throws java.lang.IllegalArgumentException
|
||||
* Another PoolingDataSource with the same dataSourceName already
|
||||
* exists.
|
||||
*/
|
||||
public void setDataSourceName(String dataSourceName) {
|
||||
if(initialized) {
|
||||
throw new IllegalStateException("Cannot set Data Source properties after DataSource has been used");
|
||||
}
|
||||
if(this.dataSourceName != null && dataSourceName != null && dataSourceName.equals(this.dataSourceName)) {
|
||||
return;
|
||||
}
|
||||
synchronized(dataSources) {
|
||||
if(getDataSource(dataSourceName) != null) {
|
||||
throw new IllegalArgumentException("DataSource with name '"+dataSourceName+"' already exists!");
|
||||
}
|
||||
if (this.dataSourceName != null) {
|
||||
dataSources.remove(this.dataSourceName);
|
||||
}
|
||||
this.dataSourceName = dataSourceName;
|
||||
dataSources.put(dataSourceName, this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this DataSource. If the initialConnections is greater than zero,
|
||||
* that number of connections will be created. After this method is called,
|
||||
* the DataSource properties cannot be changed. If you do not call this
|
||||
* explicitly, it will be called the first time you get a connection from the
|
||||
* Datasource.
|
||||
* @throws java.sql.SQLException
|
||||
* Occurs when the initialConnections is greater than zero, but the
|
||||
* DataSource is not able to create enough physical connections.
|
||||
*/
|
||||
public void initialize() throws SQLException {
|
||||
synchronized (lock) {
|
||||
source = new ConnectionPool();
|
||||
source.setDatabaseName(getDatabaseName());
|
||||
source.setPassword(getPassword());
|
||||
source.setPortNumber(getPortNumber());
|
||||
source.setServerName(getServerName());
|
||||
source.setUser(getUser());
|
||||
while (available.size() < initialConnections) {
|
||||
available.push(source.getPooledConnection());
|
||||
}
|
||||
initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a <b>non-pooled</b> connection, unless the user and password are the
|
||||
* same as the default values for this connection pool.
|
||||
*
|
||||
* @return A pooled connection.
|
||||
* @throws SQLException
|
||||
* Occurs when no pooled connection is available, and a new physical
|
||||
* connection cannot be created.
|
||||
*/
|
||||
public Connection getConnection(String user, String password) throws SQLException {
|
||||
// If this is for the default user/password, use a pooled connection
|
||||
if(user == null ||
|
||||
(user.equals(getUser()) && ((password == null && getPassword() == null) || (password != null && password.equals(getPassword()))))) {
|
||||
return getConnection();
|
||||
}
|
||||
// Otherwise, use a non-pooled connection
|
||||
if (!initialized) {
|
||||
initialize();
|
||||
}
|
||||
return super.getConnection(user, password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a connection from the connection pool.
|
||||
*
|
||||
* @return A pooled connection.
|
||||
* @throws SQLException
|
||||
* Occurs when no pooled connection is available, and a new physical
|
||||
* connection cannot be created.
|
||||
*/
|
||||
public Connection getConnection() throws SQLException {
|
||||
if(!initialized) {
|
||||
initialize();
|
||||
}
|
||||
return getPooledConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes this DataSource, and all the pooled connections, whether in use or not.
|
||||
*/
|
||||
public void close() {
|
||||
synchronized(lock) {
|
||||
while(available.size() > 0) {
|
||||
PooledConnectionImpl pci = (PooledConnectionImpl)available.pop();
|
||||
try {
|
||||
pci.close();
|
||||
} catch (SQLException e) {
|
||||
}
|
||||
}
|
||||
available = null;
|
||||
while (used.size() > 0) {
|
||||
PooledConnectionImpl pci = (PooledConnectionImpl)used.pop();
|
||||
pci.removeConnectionEventListener(connectionEventListener);
|
||||
try {
|
||||
pci.close();
|
||||
} catch (SQLException e) {
|
||||
}
|
||||
}
|
||||
used = null;
|
||||
}
|
||||
synchronized (dataSources) {
|
||||
dataSources.remove(dataSourceName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a connection from the pool. Will get an available one if
|
||||
* present, or create a new one if under the max limit. Will
|
||||
* block if all used and a new one would exceed the max.
|
||||
*/
|
||||
private Connection getPooledConnection() throws SQLException {
|
||||
PooledConnection pc = null;
|
||||
synchronized(lock) {
|
||||
if (available == null) {
|
||||
throw new SQLException("DataSource has been closed.");
|
||||
}
|
||||
while(true) {
|
||||
if(available.size() > 0) {
|
||||
pc = (PooledConnection)available.pop();
|
||||
used.push(pc);
|
||||
break;
|
||||
}
|
||||
if(maxConnections == 0 || used.size() < maxConnections) {
|
||||
pc = source.getPooledConnection();
|
||||
used.push(pc);
|
||||
break;
|
||||
} else {
|
||||
try {
|
||||
// Wake up every second at a minimum
|
||||
lock.wait(1000L);
|
||||
} catch(InterruptedException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pc.addConnectionEventListener(connectionEventListener);
|
||||
return pc.getConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notified when a pooled connection is closed, or a fatal error occurs
|
||||
* on a pooled connection. This is the only way connections are marked
|
||||
* as unused.
|
||||
*/
|
||||
private ConnectionEventListener connectionEventListener = new ConnectionEventListener() {
|
||||
public void connectionClosed(ConnectionEvent event) {
|
||||
((PooledConnection)event.getSource()).removeConnectionEventListener(this);
|
||||
synchronized(lock) {
|
||||
if(available == null) {
|
||||
return; // DataSource has been closed
|
||||
}
|
||||
boolean removed = used.remove(event.getSource());
|
||||
if(removed) {
|
||||
available.push(event.getSource());
|
||||
// There's now a new connection available
|
||||
lock.notify();
|
||||
} else {
|
||||
// a connection error occured
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is only called for fatal errors, where the physical connection is
|
||||
* useless afterward and should be removed from the pool.
|
||||
*/
|
||||
public void connectionErrorOccurred(ConnectionEvent event) {
|
||||
((PooledConnection) event.getSource()).removeConnectionEventListener(this);
|
||||
synchronized(lock) {
|
||||
if (available == null) {
|
||||
return; // DataSource has been closed
|
||||
}
|
||||
used.remove(event.getSource());
|
||||
// We're now at least 1 connection under the max
|
||||
lock.notify();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds custom properties for this DataSource to the properties defined in
|
||||
* the superclass.
|
||||
*/
|
||||
public Reference getReference() throws NamingException {
|
||||
Reference ref = super.getReference();
|
||||
ref.add(new StringRefAddr("dataSourceName", dataSourceName));
|
||||
if (initialConnections > 0) {
|
||||
ref.add(new StringRefAddr("initialConnections", Integer.toString(initialConnections)));
|
||||
}
|
||||
if (maxConnections > 0) {
|
||||
ref.add(new StringRefAddr("maxConnections", Integer.toString(maxConnections)));
|
||||
}
|
||||
return ref;
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package org.postgresql.jdbc2.optional;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Simple DataSource which does not perform connection pooling. In order to use
|
||||
* the DataSource, you must set the property databaseName. The settings for
|
||||
* serverName, portNumber, user, and password are optional. Note: these properties
|
||||
* are declared in the superclass.
|
||||
*
|
||||
* @author Aaron Mulder (ammulder@chariotsolutions.com)
|
||||
* @version $Revision: 1.1 $
|
||||
*/
|
||||
public class SimpleDataSource extends BaseDataSource implements Serializable, DataSource {
|
||||
/**
|
||||
* Gets a description of this DataSource.
|
||||
*/
|
||||
public String getDescription() {
|
||||
return "Non-Pooling DataSource from "+org.postgresql.Driver.getVersion();
|
||||
}
|
||||
}
|
@ -0,0 +1,160 @@
|
||||
package org.postgresql.test.jdbc2.optional;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
import org.postgresql.test.JDBC2Tests;
|
||||
import org.postgresql.jdbc2.optional.SimpleDataSource;
|
||||
import org.postgresql.jdbc2.optional.BaseDataSource;
|
||||
|
||||
import java.sql.*;
|
||||
|
||||
/**
|
||||
* Common tests for all the BaseDataSource implementations. This is
|
||||
* a small variety to make sure that a connection can be opened and
|
||||
* some basic queries run. The different BaseDataSource subclasses
|
||||
* have different subclasses of this which add additional custom
|
||||
* tests.
|
||||
*
|
||||
* @author Aaron Mulder (ammulder@chariotsolutions.com)
|
||||
* @version $Revision: 1.1 $
|
||||
*/
|
||||
public abstract class BaseDataSourceTest extends TestCase {
|
||||
protected Connection con;
|
||||
protected BaseDataSource bds;
|
||||
|
||||
/**
|
||||
* Constructor required by JUnit
|
||||
*/
|
||||
public BaseDataSourceTest(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a test table using a standard connection (not from a
|
||||
* DataSource).
|
||||
*/
|
||||
protected void setUp() throws Exception {
|
||||
con = JDBC2Tests.openDB();
|
||||
JDBC2Tests.createTable(con, "poolingtest", "id int4 not null primary key, name varchar(50)");
|
||||
Statement stmt = con.createStatement();
|
||||
stmt.executeUpdate("INSERT INTO poolingtest VALUES (1, 'Test Row 1')");
|
||||
stmt.executeUpdate("INSERT INTO poolingtest VALUES (2, 'Test Row 2')");
|
||||
JDBC2Tests.closeDB(con);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the test table using a standard connection (not from
|
||||
* a DataSource)
|
||||
*/
|
||||
protected void tearDown() throws Exception {
|
||||
con = JDBC2Tests.openDB();
|
||||
JDBC2Tests.dropTable(con, "poolingtest");
|
||||
JDBC2Tests.closeDB(con);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a connection from the current BaseDataSource
|
||||
*/
|
||||
protected Connection getDataSourceConnection() throws SQLException {
|
||||
initializeDataSource();
|
||||
return bds.getConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of the current BaseDataSource for
|
||||
* testing. Must be customized by each subclass.
|
||||
*/
|
||||
protected abstract void initializeDataSource();
|
||||
|
||||
/**
|
||||
* Test to make sure you can instantiate and configure the
|
||||
* appropriate DataSource
|
||||
*/
|
||||
public void testCreateDataSource() {
|
||||
initializeDataSource();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to make sure you can get a connection from the DataSource,
|
||||
* which in turn means the DataSource was able to open it.
|
||||
*/
|
||||
public void testGetConnection() {
|
||||
try {
|
||||
con = getDataSourceConnection();
|
||||
con.close();
|
||||
} catch (SQLException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple test to make sure you can execute SQL using the
|
||||
* Connection from the DataSource
|
||||
*/
|
||||
public void testUseConnection() {
|
||||
try {
|
||||
con = getDataSourceConnection();
|
||||
Statement st = con.createStatement();
|
||||
ResultSet rs = st.executeQuery("SELECT COUNT(*) FROM poolingtest");
|
||||
if(rs.next()) {
|
||||
int count = rs.getInt(1);
|
||||
if(rs.next()) {
|
||||
fail("Should only have one row in SELECT COUNT result set");
|
||||
}
|
||||
if(count != 2) {
|
||||
fail("Count returned "+count+" expecting 2");
|
||||
}
|
||||
} else {
|
||||
fail("Should have one row in SELECT COUNT result set");
|
||||
}
|
||||
rs.close();
|
||||
st.close();
|
||||
con.close();
|
||||
} catch (SQLException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A test to make sure you can execute DDL SQL using the
|
||||
* Connection from the DataSource.
|
||||
*/
|
||||
public void testDdlOverConnection() {
|
||||
try {
|
||||
con = getDataSourceConnection();
|
||||
JDBC2Tests.dropTable(con, "poolingtest");
|
||||
JDBC2Tests.createTable(con, "poolingtest", "id int4 not null primary key, name varchar(50)");
|
||||
con.close();
|
||||
} catch (SQLException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A test to make sure the connections are not being pooled by the
|
||||
* current DataSource. Obviously need to be overridden in the case
|
||||
* of a pooling Datasource.
|
||||
*/
|
||||
public void testNotPooledConnection() {
|
||||
try {
|
||||
con = getDataSourceConnection();
|
||||
String name = con.toString();
|
||||
con.close();
|
||||
con = getDataSourceConnection();
|
||||
String name2 = con.toString();
|
||||
con.close();
|
||||
assertTrue(!name.equals(name2));
|
||||
} catch (SQLException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Eventually, we must test stuffing the DataSource in JNDI and
|
||||
* then getting it back out and make sure it's still usable. This
|
||||
* should ideally test both Serializable and Referenceable
|
||||
* mechanisms. Will probably be multiple tests when implemented.
|
||||
*/
|
||||
public void testJndi() {
|
||||
// TODO: Put the DS in JNDI, retrieve it, and try some of this stuff again
|
||||
}
|
||||
}
|
@ -0,0 +1,327 @@
|
||||
package org.postgresql.test.jdbc2.optional;
|
||||
|
||||
import org.postgresql.jdbc2.optional.ConnectionPool;
|
||||
import org.postgresql.test.JDBC2Tests;
|
||||
import javax.sql.*;
|
||||
import java.sql.*;
|
||||
|
||||
/**
|
||||
* Tests for the ConnectionPoolDataSource and PooledConnection
|
||||
* implementations. They are tested together because the only client
|
||||
* interface to the PooledConnection is through the CPDS.
|
||||
*
|
||||
* @author Aaron Mulder (ammulder@chariotsolutions.com)
|
||||
* @version $Revision: 1.1 $
|
||||
*/
|
||||
public class ConnectionPoolTest extends BaseDataSourceTest {
|
||||
/**
|
||||
* Constructor required by JUnit
|
||||
*/
|
||||
public ConnectionPoolTest(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and configures a ConnectionPool
|
||||
*/
|
||||
protected void initializeDataSource() {
|
||||
if(bds == null) {
|
||||
bds = new ConnectionPool();
|
||||
String db = JDBC2Tests.getURL();
|
||||
if(db.indexOf('/') > -1) {
|
||||
db = db.substring(db.lastIndexOf('/')+1);
|
||||
} else if(db.indexOf(':') > -1) {
|
||||
db = db.substring(db.lastIndexOf(':')+1);
|
||||
}
|
||||
bds.setDatabaseName(db);
|
||||
bds.setUser(JDBC2Tests.getUser());
|
||||
bds.setPassword(JDBC2Tests.getPassword());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Though the normal client interface is to grab a Connection, in
|
||||
* order to test the middleware/server interface, we need to deal
|
||||
* with PooledConnections. Some tests use each.
|
||||
*/
|
||||
protected PooledConnection getPooledConnection() throws SQLException {
|
||||
initializeDataSource();
|
||||
return ((ConnectionPool)bds).getPooledConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Instead of just fetching a Connection from the ConnectionPool,
|
||||
* get a PooledConnection, add a listener to close it when the
|
||||
* Connection is closed, and then get the Connection. Without
|
||||
* the listener the PooledConnection (and thus the physical connection)
|
||||
* would never by closed. Probably not a disaster during testing, but
|
||||
* you never know.
|
||||
*/
|
||||
protected Connection getDataSourceConnection() throws SQLException {
|
||||
initializeDataSource();
|
||||
final PooledConnection pc = getPooledConnection();
|
||||
// Since the pooled connection won't be reused in these basic tests, close it when the connection is closed
|
||||
pc.addConnectionEventListener(new ConnectionEventListener() {
|
||||
public void connectionClosed(ConnectionEvent event) {
|
||||
try {
|
||||
pc.close();
|
||||
} catch (SQLException e) {
|
||||
fail("Unable to close PooledConnection: "+e);
|
||||
}
|
||||
}
|
||||
|
||||
public void connectionErrorOccurred(ConnectionEvent event) {
|
||||
}
|
||||
});
|
||||
return pc.getConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure that if you get a connection from a PooledConnection,
|
||||
* close it, and then get another one, you're really using the same
|
||||
* physical connection. Depends on the implementation of toString
|
||||
* for the connection handle.
|
||||
*/
|
||||
public void testPoolReuse() {
|
||||
try {
|
||||
PooledConnection pc = getPooledConnection();
|
||||
con = pc.getConnection();
|
||||
String name = con.toString();
|
||||
con.close();
|
||||
con = pc.getConnection();
|
||||
String name2 = con.toString();
|
||||
con.close();
|
||||
pc.close();
|
||||
assertTrue("Physical connection doesn't appear to be reused across PooledConnection wrappers", name.equals(name2));
|
||||
} catch (SQLException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure that when you request a connection from the
|
||||
* PooledConnection, and previous connection it might have given
|
||||
* out is closed. See JDBC 2.0 Optional Package spec section
|
||||
* 6.2.3
|
||||
*/
|
||||
public void testPoolCloseOldWrapper() {
|
||||
try {
|
||||
PooledConnection pc = getPooledConnection();
|
||||
con = pc.getConnection();
|
||||
Connection con2 = pc.getConnection();
|
||||
try {
|
||||
con.createStatement();
|
||||
fail("Original connection wrapper should be closed when new connection wrapper is generated");
|
||||
} catch(SQLException e) {}
|
||||
try {
|
||||
con.close();
|
||||
fail("Original connection wrapper should be closed when new connection wrapper is generated");
|
||||
} catch(SQLException e) {}
|
||||
con2.close();
|
||||
pc.close();
|
||||
} catch (SQLException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure that if you get two connection wrappers from the same
|
||||
* PooledConnection, they are different, even though the represent
|
||||
* the same physical connection. See JDBC 2.0 Optional Pacakge spec
|
||||
* section 6.2.2
|
||||
*/
|
||||
public void testPoolNewWrapper() {
|
||||
try {
|
||||
PooledConnection pc = getPooledConnection();
|
||||
con = pc.getConnection();
|
||||
Connection con2 = pc.getConnection();
|
||||
con2.close();
|
||||
pc.close();
|
||||
assertTrue("Two calls to PooledConnection.getConnection should not return the same connection wrapper", con != con2);
|
||||
} catch (SQLException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure that exactly one close event is fired for each time a
|
||||
* connection handle is closed. Also checks that events are not
|
||||
* fired after a given handle has been closed once.
|
||||
*/
|
||||
public void testCloseEvent() {
|
||||
try {
|
||||
PooledConnection pc = getPooledConnection();
|
||||
CountClose cc = new CountClose();
|
||||
pc.addConnectionEventListener(cc);
|
||||
con = pc.getConnection();
|
||||
assertTrue(cc.getCount() == 0);
|
||||
assertTrue(cc.getErrorCount() == 0);
|
||||
con.close();
|
||||
assertTrue(cc.getCount() == 1);
|
||||
assertTrue(cc.getErrorCount() == 0);
|
||||
con = pc.getConnection();
|
||||
assertTrue(cc.getCount() == 1);
|
||||
assertTrue(cc.getErrorCount() == 0);
|
||||
con.close();
|
||||
assertTrue(cc.getCount() == 2);
|
||||
assertTrue(cc.getErrorCount() == 0);
|
||||
try {
|
||||
con.close();
|
||||
fail("Should not be able to close a connection wrapper twice");
|
||||
} catch (SQLException e) {}
|
||||
assertTrue(cc.getCount() == 2);
|
||||
assertTrue(cc.getErrorCount() == 0);
|
||||
pc.close();
|
||||
} catch (SQLException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure that close events are not fired after a listener has
|
||||
* been removed.
|
||||
*/
|
||||
public void testNoCloseEvent() {
|
||||
try {
|
||||
PooledConnection pc = getPooledConnection();
|
||||
CountClose cc = new CountClose();
|
||||
pc.addConnectionEventListener(cc);
|
||||
con = pc.getConnection();
|
||||
assertTrue(cc.getCount() == 0);
|
||||
assertTrue(cc.getErrorCount() == 0);
|
||||
con.close();
|
||||
assertTrue(cc.getCount() == 1);
|
||||
assertTrue(cc.getErrorCount() == 0);
|
||||
pc.removeConnectionEventListener(cc);
|
||||
con = pc.getConnection();
|
||||
assertTrue(cc.getCount() == 1);
|
||||
assertTrue(cc.getErrorCount() == 0);
|
||||
con.close();
|
||||
assertTrue(cc.getCount() == 1);
|
||||
assertTrue(cc.getErrorCount() == 0);
|
||||
} catch (SQLException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure that a listener can be removed while dispatching
|
||||
* events. Sometimes this causes a ConcurrentModificationException
|
||||
* or something.
|
||||
*/
|
||||
public void testInlineCloseEvent() {
|
||||
try {
|
||||
PooledConnection pc = getPooledConnection();
|
||||
RemoveClose rc1 = new RemoveClose();
|
||||
RemoveClose rc2 = new RemoveClose();
|
||||
RemoveClose rc3 = new RemoveClose();
|
||||
pc.addConnectionEventListener(rc1);
|
||||
pc.addConnectionEventListener(rc2);
|
||||
pc.addConnectionEventListener(rc3);
|
||||
con = pc.getConnection();
|
||||
con.close();
|
||||
con = pc.getConnection();
|
||||
con.close();
|
||||
} catch (Exception e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a close event is not generated when a connection
|
||||
* handle is closed automatically due to a new connection handle
|
||||
* being opened for the same PooledConnection. See JDBC 2.0
|
||||
* Optional Package spec section 6.3
|
||||
*/
|
||||
public void testAutomaticCloseEvent() {
|
||||
try {
|
||||
PooledConnection pc = getPooledConnection();
|
||||
CountClose cc = new CountClose();
|
||||
pc.addConnectionEventListener(cc);
|
||||
con = pc.getConnection();
|
||||
assertTrue(cc.getCount() == 0);
|
||||
assertTrue(cc.getErrorCount() == 0);
|
||||
con.close();
|
||||
assertTrue(cc.getCount() == 1);
|
||||
assertTrue(cc.getErrorCount() == 0);
|
||||
con = pc.getConnection();
|
||||
assertTrue(cc.getCount() == 1);
|
||||
assertTrue(cc.getErrorCount() == 0);
|
||||
// Open a 2nd connection, causing the first to be closed. No even should be generated.
|
||||
Connection con2 = pc.getConnection();
|
||||
assertTrue("Connection handle was not closed when new handle was opened", con.isClosed());
|
||||
assertTrue(cc.getCount() == 1);
|
||||
assertTrue(cc.getErrorCount() == 0);
|
||||
con2.close();
|
||||
assertTrue(cc.getCount() == 2);
|
||||
assertTrue(cc.getErrorCount() == 0);
|
||||
pc.close();
|
||||
} catch (SQLException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure the isClosed method on a connection wrapper does what
|
||||
* you'd expect. Checks the usual case, as well as automatic
|
||||
* closure when a new handle is opened on the same physical connection.
|
||||
*/
|
||||
public void testIsClosed() {
|
||||
try {
|
||||
PooledConnection pc = getPooledConnection();
|
||||
Connection con = pc.getConnection();
|
||||
assertTrue(!con.isClosed());
|
||||
con.close();
|
||||
assertTrue(con.isClosed());
|
||||
con = pc.getConnection();
|
||||
Connection con2 = pc.getConnection();
|
||||
assertTrue(con.isClosed());
|
||||
assertTrue(!con2.isClosed());
|
||||
con2.close();
|
||||
assertTrue(con.isClosed());
|
||||
pc.close();
|
||||
} catch (SQLException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class to remove a listener during event dispatching.
|
||||
*/
|
||||
private class RemoveClose implements ConnectionEventListener {
|
||||
public void connectionClosed(ConnectionEvent event) {
|
||||
((PooledConnection)event.getSource()).removeConnectionEventListener(this);
|
||||
}
|
||||
|
||||
public void connectionErrorOccurred(ConnectionEvent event) {
|
||||
((PooledConnection)event.getSource()).removeConnectionEventListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class that implements the event listener interface, and
|
||||
* counts the number of events it sees.
|
||||
*/
|
||||
private class CountClose implements ConnectionEventListener {
|
||||
private int count = 0, errorCount = 0;
|
||||
public void connectionClosed(ConnectionEvent event) {
|
||||
count++;
|
||||
}
|
||||
|
||||
public void connectionErrorOccurred(ConnectionEvent event) {
|
||||
errorCount++;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public int getErrorCount() {
|
||||
return errorCount;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
count = errorCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package org.postgresql.test.jdbc2.optional;
|
||||
|
||||
import junit.framework.TestSuite;
|
||||
|
||||
/**
|
||||
* Test suite for the JDBC 2.0 Optional Package implementation. This
|
||||
* includes the DataSource, ConnectionPoolDataSource, and
|
||||
* PooledConnection implementations.
|
||||
*
|
||||
* @author Aaron Mulder (ammulder@chariotsolutions.com)
|
||||
* @version $Revision: 1.1 $
|
||||
*/
|
||||
public class OptionalTestSuite extends TestSuite {
|
||||
/**
|
||||
* Gets the test suite for the entire JDBC 2.0 Optional Package
|
||||
* implementation.
|
||||
*/
|
||||
public static TestSuite suite() {
|
||||
TestSuite suite = new TestSuite();
|
||||
suite.addTestSuite(SimpleDataSourceTest.class);
|
||||
suite.addTestSuite(ConnectionPoolTest.class);
|
||||
return suite;
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package org.postgresql.test.jdbc2.optional;
|
||||
|
||||
import org.postgresql.test.JDBC2Tests;
|
||||
import org.postgresql.jdbc2.optional.SimpleDataSource;
|
||||
|
||||
/**
|
||||
* Performs the basic tests defined in the superclass. Just adds the
|
||||
* configuration logic.
|
||||
*
|
||||
* @author Aaron Mulder (ammulder@chariotsolutions.com)
|
||||
* @version $Revision: 1.1 $
|
||||
*/
|
||||
public class SimpleDataSourceTest extends BaseDataSourceTest {
|
||||
/**
|
||||
* Constructor required by JUnit
|
||||
*/
|
||||
public SimpleDataSourceTest(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and configures a new SimpleDataSource.
|
||||
*/
|
||||
protected void initializeDataSource() {
|
||||
if(bds == null) {
|
||||
bds = new SimpleDataSource();
|
||||
String db = JDBC2Tests.getURL();
|
||||
if(db.indexOf('/') > -1) {
|
||||
db = db.substring(db.lastIndexOf('/')+1);
|
||||
} else if(db.indexOf(':') > -1) {
|
||||
db = db.substring(db.lastIndexOf(':')+1);
|
||||
}
|
||||
bds.setDatabaseName(db);
|
||||
bds.setUser(JDBC2Tests.getUser());
|
||||
bds.setPassword(JDBC2Tests.getPassword());
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user