--- /dev/null
+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;
+ }
+
+}
--- /dev/null
+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;
+ }
+
+}
--- /dev/null
+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();
+ }
+
+}
--- /dev/null
+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
+ }
+ }
+}
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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();
+ }
+}
--- /dev/null
+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
+ }
+}
--- /dev/null
+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;
+ }
+ }
+}
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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());
+ }
+ }
+}