/*-------------------------------------------------------------------------
*
* Copyright (c) 2004-2014, PostgreSQL Global Development Group
*
*
*-------------------------------------------------------------------------
*/
package org.postgresql.test.jdbc2.optional;
import org.postgresql.PGConnection;
import org.postgresql.jdbc2.optional.ConnectionPool;
import org.postgresql.ds.PGConnectionPoolDataSource;
import org.postgresql.test.TestUtil;
import javax.sql.*;
import java.net.Socket;
import java.net.SocketImpl;
import java.sql.*;
import java.util.*;
import java.io.*;
/**
* 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)
*/
public class ConnectionPoolTest extends BaseDataSourceTest
{
private ArrayList connections = new ArrayList();
/**
* Constructor required by JUnit
*/
public ConnectionPoolTest(String name)
{
super(name);
}
/**
* Creates and configures a ConnectionPool
*/
protected void initializeDataSource()
{
if (bds == null)
{
bds = new ConnectionPool();
setupDataSource(bds);
}
}
protected void tearDown() throws Exception
{
for (Iterator i = connections.iterator(); i.hasNext(); ) {
PooledConnection c = (PooledConnection) i.next();
try {
c.close();
} catch (Exception ex) {
// close throws nullptr or other evil things if the connection
// is already closed
}
}
}
/**
* 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();
// we need to recast to PGConnectionPool rather than
// jdbc.optional.ConnectionPool because our ObjectFactory
// returns only the top level class, not the specific
// jdbc2/jdbc3 implementations.
PooledConnection c = ((PGConnectionPoolDataSource)bds).getPooledConnection();
connections.add(c);
return c;
}
/**
* 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)
{
}
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);
// a double close shouldn't fire additional events
con.close();
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();
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());
}
}
/**
* Make sure that close status of pooled connection reflect the one
* of the underlying physical connection.
* @throws Exception
*/
public void testBackendIsClosed() throws Exception
{
try
{
PooledConnection pc = getPooledConnection();
con = pc.getConnection();
assertTrue(!con.isClosed());
int pid = ((PGConnection) con).getBackendPID();
Connection adminCon = TestUtil.openDB();
try
{
Statement statement = adminCon.createStatement();
statement.executeQuery("SELECT pg_terminate_backend("+pid+")");
}
finally
{
TestUtil.closeDB(adminCon);
}
try
{
Statement statement = con.createStatement();
statement.executeQuery("SELECT 1");
fail("The connection should not be opened anymore. An exception was expected");
}
catch (SQLException e)
{
// this is expected as the connection has been forcibly closed from backend
}
assertTrue(con.isClosed());
}
catch (SQLException e)
{
fail(e.getMessage());
}
}
/**
* Ensures that a statement generated by a proxied connection returns the
* proxied connection from getConnection() [not the physical connection].
*/
public void testStatementConnection() {
try
{
PooledConnection pc = getPooledConnection();
con = pc.getConnection();
Statement s = con.createStatement();
Connection conRetrieved = s.getConnection();
assertTrue(con.getClass().equals(conRetrieved.getClass()));
assertTrue(con.equals(conRetrieved));
}
catch (SQLException e)
{
fail(e.getMessage());
}
}
/**
* Ensures that the Statement proxy generated by the Connection handle
* throws the correct kind of exception.
*/
public void testStatementProxy() {
Statement s = null;
try
{
PooledConnection pc = getPooledConnection();
con = pc.getConnection();
s = con.createStatement();
}
catch (SQLException e)
{
fail(e.getMessage());
}
try
{
s.executeQuery("SELECT * FROM THIS_TABLE_SHOULD_NOT_EXIST");
fail("An SQL exception was not thrown that should have been");
}
catch (SQLException e)
{
; // This is the expected and correct path
}
catch (Exception e)
{
fail("bad exception; was expecting SQLException, not" +
e.getClass().getName());
}
}
/**
* Ensures that a prepared statement generated by a proxied connection
* returns the proxied connection from getConnection() [not the physical
* connection].
*/
public void testPreparedStatementConnection() {
try
{
PooledConnection pc = getPooledConnection();
con = pc.getConnection();
PreparedStatement s = con.prepareStatement("select 'x'");
Connection conRetrieved = s.getConnection();
assertTrue(con.getClass().equals(conRetrieved.getClass()));
assertTrue(con.equals(conRetrieved));
}
catch (SQLException e)
{
fail(e.getMessage());
}
}
/**
* Ensures that a callable statement generated by a proxied connection
* returns the proxied connection from getConnection() [not the physical
* connection].
*/
public void testCallableStatementConnection() {
try
{
PooledConnection pc = getPooledConnection();
con = pc.getConnection();
CallableStatement s = con.prepareCall("select 'x'");
Connection conRetrieved = s.getConnection();
assertTrue(con.getClass().equals(conRetrieved.getClass()));
assertTrue(con.equals(conRetrieved));
}
catch (SQLException e)
{
fail(e.getMessage());
}
}
/**
* Ensure that a statement created from a pool can be used
* like any other statement in regard to pg extensions.
*/
public void testStatementsProxyPGStatement() {
try
{
PooledConnection pc = getPooledConnection();
con = pc.getConnection();
Statement s = con.createStatement();
boolean b = ((org.postgresql.PGStatement)s).isUseServerPrepare();
PreparedStatement ps = con.prepareStatement("select 'x'");
b = ((org.postgresql.PGStatement)ps).isUseServerPrepare();
CallableStatement cs = con.prepareCall("select 'x'");
b = ((org.postgresql.PGStatement)cs).isUseServerPrepare();
}
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;
}
}
public void testSerializable() throws IOException, ClassNotFoundException
{
ConnectionPool pool = new ConnectionPool();
pool.setDefaultAutoCommit(false);
pool.setServerName("db.myhost.com");
pool.setDatabaseName("mydb");
pool.setUser("user");
pool.setPassword("pass");
pool.setPortNumber(1111);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(pool);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
ConnectionPool pool2 = (ConnectionPool)ois.readObject();
assertEquals(pool.isDefaultAutoCommit(), pool2.isDefaultAutoCommit());
assertEquals(pool.getServerName(), pool2.getServerName());
assertEquals(pool.getDatabaseName(), pool2.getDatabaseName());
assertEquals(pool.getUser(), pool2.getUser());
assertEquals(pool.getPassword(), pool2.getPassword());
assertEquals(pool.getPortNumber(), pool2.getPortNumber());
}
}