/*-------------------------------------------------------------------------
*
* Copyright (c) 2004-2005, PostgreSQL Global Development Group
*
* IDENTIFICATION
*   $PostgreSQL: pgjdbc/org/postgresql/test/jdbc2/optional/BaseDataSourceTest.java,v 1.11 2005/01/11 08:25:48 jurka Exp $
*
*-------------------------------------------------------------------------
*/
package org.postgresql.test.jdbc2.optional;

import junit.framework.TestCase;
import org.postgresql.test.TestUtil;
import org.postgresql.test.util.MiniJndiContextFactory;
import org.postgresql.ds.common.BaseDataSource;
import org.postgresql.PGConnection;

import java.sql.*;
import java.util.*;
import javax.naming.*;

/**
 * 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)
 */
public abstract class BaseDataSourceTest extends TestCase
{
    public static String DATA_SOURCE_JNDI = "BaseDataSource";
    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 = TestUtil.openDB();
        TestUtil.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')");
        TestUtil.closeDB(con);
    }

    /**
     * Removes the test table using a standard connection (not from
     * a DataSource)
     */
    protected void tearDown() throws Exception
    {
        con = TestUtil.openDB();
        TestUtil.dropTable(con, "poolingtest");
        TestUtil.closeDB(con);
    }

    /**
     * Gets a connection from the current BaseDataSource
     */
    protected Connection getDataSourceConnection() throws SQLException
    {
        if (bds == null)
        {
            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();
            TestUtil.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());
        }
    }

    /**
     * Test to make sure that PGConnection methods can be called on the
     * pooled Connection.
     */
    public void testPGConnection()
    {
        try
        {
            con = getDataSourceConnection();
            ((PGConnection)con).getNotifications();
            con.close();
        }
        catch (Exception e)
        {
            fail("Unable to call PGConnection method on pooled connection due to " + e.getClass().getName() + " (" + e.getMessage() + ")");
        }
    }

    /**
     * Uses the mini-JNDI implementation for testing purposes
     */
    protected InitialContext getInitialContext()
    {
        Hashtable env = new Hashtable();
        env.put(Context.INITIAL_CONTEXT_FACTORY, MiniJndiContextFactory.class.getName());
        try
        {
            return new InitialContext(env);
        }
        catch (NamingException e)
        {
            fail("Unable to create InitialContext: " + e.getMessage());
            return null;
        }
    }

    /**
     * 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()
    {
        initializeDataSource();
        BaseDataSource oldbds = bds;
        InitialContext ic = getInitialContext();
        try
        {
            ic.rebind(DATA_SOURCE_JNDI, bds);
            bds = (BaseDataSource)ic.lookup(DATA_SOURCE_JNDI);
            assertTrue("Got null looking up DataSource from JNDI!", bds != null);
            compareJndiDataSource(oldbds, bds);
        }
        catch (NamingException e)
        {
            fail(e.getMessage());
        }
        oldbds = bds;
        testUseConnection();
        assertTrue("Test should not have changed DataSource (" + bds + " != " + oldbds + ")!", bds == oldbds);
    }

    /**
     * Check whether a DS was dereferenced from JNDI or recreated.
     */
    protected void compareJndiDataSource(BaseDataSource oldbds, BaseDataSource bds) {
        assertTrue("DataSource was dereferenced, should have been serialized or recreated", bds != oldbds);
    }
}
