/*
 * This software is released under a licence similar to the Apache Software Licence.
 * See org.logicalcobwebs.proxool.package.html for details.
 * The latest version is available at http://proxool.sourceforge.net
 */
package org.logicalcobwebs.proxool;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.logicalcobwebs.proxool.admin.SnapshotIF;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.sql.SQLException;
import java.util.Properties;

/**
 * Test the house keeper in ConnectionPool
 *
 * @author bill
 * @author $Author: billhorsman $ (current maintainer)
 * @version $Revision: 1.15 $, $Date: 2007/01/25 23:38:24 $
 * @since Proxool 0.8
 */
public class HouseKeeperTest extends AbstractProxoolTest {

    private static final Log LOG = LogFactory.getLog(HouseKeeperTest.class);

    public HouseKeeperTest(String alias) {
        super(alias);
    }

    /**
     * Test that connections that remain active for longer than the configured
     * time are closed (and destroyed) automatically.
     */
    public void testMaximumActiveTime() throws Exception {

        ConnectionResetter.setTriggerResetException(true);
        String testName = "maximumActiveTime";
        String alias = testName;

        String url = TestHelper.buildProxoolUrl(alias,
                TestConstants.HYPERSONIC_DRIVER,
                TestConstants.HYPERSONIC_TEST_URL);
        Properties info = new Properties();
        info.setProperty(ProxoolConstants.USER_PROPERTY, TestConstants.HYPERSONIC_USER);
        info.setProperty(ProxoolConstants.PASSWORD_PROPERTY, TestConstants.HYPERSONIC_PASSWORD);
        info.setProperty(ProxoolConstants.MAXIMUM_ACTIVE_TIME_PROPERTY, "1000");
        info.setProperty(ProxoolConstants.MINIMUM_CONNECTION_COUNT_PROPERTY, "1");
        info.setProperty(ProxoolConstants.TRACE_PROPERTY, "true");
        info.setProperty(ProxoolConstants.HOUSE_KEEPING_SLEEP_TIME_PROPERTY, "1000");
        ProxoolFacade.registerConnectionPool(url, info);

        Listener listener = new Listener();
        ProxoolFacade.addConnectionListener(alias, listener);
        assertEquals("Shouldn't be any active connections yet", 0, ProxoolFacade.getSnapshot(alias, false).getServedCount());

        final Connection connection = DriverManager.getConnection(url);
        connection.setAutoCommit(false);
        connection.createStatement().executeQuery(TestConstants.HYPERSONIC_TEST_SQL);
        long start = System.currentTimeMillis();

        assertEquals("We just opened 1 connection", 1, ProxoolFacade.getSnapshot(alias, false).getServedCount());

        new ResultMonitor() {
            public boolean check() throws Exception {
                return connection.isClosed();
            }
        }.getResult();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            LOG.debug("Awoken.");
        }

        long elapsed = System.currentTimeMillis() - start;
        assertTrue("Connection has not been closed after " + elapsed + " milliseconds as expected", connection.isClosed());
        assertTrue("onAboutToDie not called as expected", listener.called);
        assertEquals("Expected the connection to be inactive", 0, ProxoolFacade.getSnapshot(alias, false).getActiveConnectionCount());

        try {
            connection.createStatement().executeQuery(TestConstants.HYPERSONIC_TEST_SQL);
            fail("Calling createStatement() on a closed connection should fail");
        } catch (Exception e) {
            // s'okay. We expected this
            LOG.debug("Ignoring expected exception: " + e.getMessage());
        }

        // Now close the connection ourselves. It's already been closed by the House Keeper but nothing bad should
        // happen if we do it again now.
        connection.close();

        // Let's see if the prototyper builds another one
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            LOG.debug("Awoken.");
        }
        SnapshotIF snapshot = ProxoolFacade.getSnapshot(alias, false);
        assertEquals("activeConnectionCount", 0, snapshot.getActiveConnectionCount());
        assertEquals("availableConnectionCount", 1, snapshot.getAvailableConnectionCount());
        assertEquals("connectionCount", 1, snapshot.getConnectionCount());

    }

    /**
     * Test that connections that remain active for longer than the configured
     * time are closed (and destroyed) automatically. Also, it gets errors during
     * reset. We don't want the connectionCount to be decremented twice.
     */
    public void testMaximumActiveTimeWithResetFailure() throws Exception {

        try {
            ConnectionResetter.setTriggerResetException(true);
            String testName = "maximumActiveTimeWithResetFailure";
            String alias = testName;

            String url = TestHelper.buildProxoolUrl(alias,
                    TestConstants.HYPERSONIC_DRIVER,
                    TestConstants.HYPERSONIC_TEST_URL);
            Properties info = new Properties();
            info.setProperty(ProxoolConstants.USER_PROPERTY, TestConstants.HYPERSONIC_USER);
            info.setProperty(ProxoolConstants.PASSWORD_PROPERTY, TestConstants.HYPERSONIC_PASSWORD);
            info.setProperty(ProxoolConstants.MAXIMUM_ACTIVE_TIME_PROPERTY, "1000");
            info.setProperty(ProxoolConstants.TRACE_PROPERTY, "true");
            info.setProperty(ProxoolConstants.HOUSE_KEEPING_SLEEP_TIME_PROPERTY, "1000");
            ProxoolFacade.registerConnectionPool(url, info);

            assertEquals("Shouldn't be any active connections yet", 0, ProxoolFacade.getSnapshot(alias, false).getServedCount());

            final Connection connection = DriverManager.getConnection(url);
            connection.setAutoCommit(false);
            connection.createStatement().executeQuery(TestConstants.HYPERSONIC_TEST_SQL);
            long start = System.currentTimeMillis();

            assertEquals("We just opened 1 connection", 1, ProxoolFacade.getSnapshot(alias, false).getServedCount());

            new ResultMonitor() {
                public boolean check() throws Exception {
                    return connection.isClosed();
                }
            }.getResult();
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                LOG.debug("Awoken.");
            }

            long elapsed = System.currentTimeMillis() - start;
            assertTrue("Connection has not been closed after " + elapsed + " milliseconds as expected", connection.isClosed());
            assertEquals("Expected the connection to be inactive", 0, ProxoolFacade.getSnapshot(alias, false).getActiveConnectionCount());

            try {
                connection.createStatement().executeQuery(TestConstants.HYPERSONIC_TEST_SQL);
                fail("Calling createStatement() on a closed connection should fail");
            } catch (Exception e) {
                // s'okay. We expected this
                LOG.debug("Ignoring expected exception: " + e.getMessage());
            }

            // Now close the connection ourselves. It's already been closed by the House Keeper but nothing bad should
            // happen if we do it again now.
            connection.close();

            // Let's see if the prototyper builds another one
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                LOG.debug("Awoken.");
            }
            SnapshotIF snapshot = ProxoolFacade.getSnapshot(alias, false);
            assertEquals("activeConnectionCount", 0, snapshot.getActiveConnectionCount());
            assertEquals("availableConnectionCount", 0, snapshot.getAvailableConnectionCount());
            assertEquals("connectionCount", 0, snapshot.getConnectionCount());
        } finally {
            // Back to normal
            ConnectionResetter.setTriggerResetException(false);
        }

    }

    /**
     * Test that house keeper destroys connections that fail configured
     * the test sql
     */
    public void testHouseKeeperTestSql() throws Exception {

        String testName = "houseKeeperTestSql";
        String alias = testName;

        String url = TestHelper.buildProxoolUrl(alias,
                TestConstants.HYPERSONIC_DRIVER,
                TestConstants.HYPERSONIC_TEST_URL);
        Properties info = new Properties();
        info.setProperty(ProxoolConstants.USER_PROPERTY, TestConstants.HYPERSONIC_USER);
        info.setProperty(ProxoolConstants.PASSWORD_PROPERTY, TestConstants.HYPERSONIC_PASSWORD);
        info.setProperty(ProxoolConstants.HOUSE_KEEPING_TEST_SQL_PROPERTY, "SELECT NOW");
        info.setProperty(ProxoolConstants.HOUSE_KEEPING_SLEEP_TIME_PROPERTY, "1000");
        ProxoolFacade.registerConnectionPool(url, info);

        DriverManager.getConnection(url).close();

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            LOG.debug("Awoken.");
        }

        DriverManager.getConnection(url).close();
    }

    /**
     * Test that house keeper destroys connections that fail configured
     * the test sql
     */
    public void testInvalidBeforeUse() throws Exception {

        String testName = "invalidBeforeUse";
        String alias = testName;

        String url = TestHelper.buildProxoolUrl(alias,
                TestConstants.HYPERSONIC_DRIVER,
                TestConstants.HYPERSONIC_TEST_URL);
        Properties info = new Properties();
        info.setProperty(ProxoolConstants.USER_PROPERTY, TestConstants.HYPERSONIC_USER);
        info.setProperty(ProxoolConstants.PASSWORD_PROPERTY, TestConstants.HYPERSONIC_PASSWORD);
        info.setProperty(ProxoolConstants.HOUSE_KEEPING_TEST_SQL_PROPERTY, "Invalid test");
        info.setProperty(ProxoolConstants.TEST_BEFORE_USE_PROPERTY, Boolean.TRUE.toString());
        info.setProperty(ProxoolConstants.VERBOSE_PROPERTY, Boolean.TRUE.toString());
        info.setProperty(ProxoolConstants.TRACE_PROPERTY, Boolean.TRUE.toString());
        ProxoolFacade.registerConnectionPool(url, info);

        // This should trigger a test followed the actual executed command. Because we've
        // deliberately made the test invalid, we should get an exception when getting a
        // connection
        Connection connection = null;
        Statement s = null;
        try {
            connection = DriverManager.getConnection(url);
            s = connection.createStatement();
            s.execute(TestConstants.HYPERSONIC_TEST_SQL);
            fail("Expected to get an exception because the test failed");
        } catch (SQLException e) {
            // Log message only so we don't get a worrying stack trace
            LOG.debug("Expected exception: " + e.getMessage());
        }

    }

    /**
     * Test that house keeper destroys connections that fail configured
     * the test sql
     */
    public void testInvalidAfterUse() throws Exception {

        String testName = "invalidAfterUse";
        String alias = testName;

        String url = TestHelper.buildProxoolUrl(alias,
                TestConstants.HYPERSONIC_DRIVER,
                TestConstants.HYPERSONIC_TEST_URL);
        Properties info = new Properties();
        info.setProperty(ProxoolConstants.USER_PROPERTY, TestConstants.HYPERSONIC_USER);
        info.setProperty(ProxoolConstants.PASSWORD_PROPERTY, TestConstants.HYPERSONIC_PASSWORD);
        info.setProperty(ProxoolConstants.HOUSE_KEEPING_TEST_SQL_PROPERTY, "Invalid test");
        info.setProperty(ProxoolConstants.TEST_AFTER_USE_PROPERTY, Boolean.TRUE.toString());
        info.setProperty(ProxoolConstants.VERBOSE_PROPERTY, Boolean.TRUE.toString());
        info.setProperty(ProxoolConstants.TRACE_PROPERTY, Boolean.TRUE.toString());
        ProxoolFacade.registerConnectionPool(url, info);

        // This should trigger a test as soon as we close the connection. Because we've
        // deliberately made the test invalid then it should get thrown away
        Connection connection = null;
        Statement s = null;
        try {
            connection = DriverManager.getConnection(url);
            s = connection.createStatement();
            s.execute(TestConstants.HYPERSONIC_TEST_SQL);
        } finally {
            if (connection != null) {
                connection.close();
            }
        }

        // There should be no available connections. We don't have a minimum setup and the one we
        // just created on demand got thrown away because it failed its test
        assertEquals("Available connections", 0, ProxoolFacade.getSnapshot(alias).getAvailableConnectionCount());

    }

    public void testBeforeAndAfterUse() throws Exception {

        String testName = "beforeAndAfterUse";
        String alias = testName;

        String url = TestHelper.buildProxoolUrl(alias,
                TestConstants.HYPERSONIC_DRIVER,
                TestConstants.HYPERSONIC_TEST_URL);
        Properties info = new Properties();
        info.setProperty(ProxoolConstants.USER_PROPERTY, TestConstants.HYPERSONIC_USER);
        info.setProperty(ProxoolConstants.PASSWORD_PROPERTY, TestConstants.HYPERSONIC_PASSWORD);
        info.setProperty(ProxoolConstants.HOUSE_KEEPING_TEST_SQL_PROPERTY, TestConstants.HYPERSONIC_TEST_SQL);
        info.setProperty(ProxoolConstants.TEST_BEFORE_USE_PROPERTY, Boolean.TRUE.toString());
        info.setProperty(ProxoolConstants.TEST_AFTER_USE_PROPERTY, Boolean.TRUE.toString());
        info.setProperty(ProxoolConstants.VERBOSE_PROPERTY, Boolean.TRUE.toString());
        info.setProperty(ProxoolConstants.TRACE_PROPERTY, Boolean.TRUE.toString());
        ProxoolFacade.registerConnectionPool(url, info);

        Connection connection = null;
        Statement s = null;
        try {
            connection = DriverManager.getConnection(url);
            s = connection.createStatement();
            s.execute(TestConstants.HYPERSONIC_TEST_SQL);
        } finally {
            if (connection != null) {
                connection.close();
            }
        }

        // There should be one available connection.
        assertEquals("Available connections", 1, ProxoolFacade.getSnapshot(alias).getAvailableConnectionCount());

    }

    class Listener implements ConnectionListenerIF {

        boolean called;

        public void onBirth(Connection connection) throws SQLException {}

        public void onDeath(Connection connection, int reasonCode) throws SQLException {
            called = (reasonCode == ConnectionListenerIF.MAXIMUM_ACTIVE_TIME_EXPIRED);
        }

        public void onExecute(String command, long elapsedTime) {}

        public void onFail(String command, Exception exception) {}

    }
}

/*
Revision history:
$Log: HouseKeeperTest.java,v $
Revision 1.15  2007/01/25 23:38:24  billhorsman
Scrapped onAboutToDie and altered onDeath signature instead. Now includes reasonCode (see ConnectionListenerIF)

Revision 1.14  2007/01/25 00:10:24  billhorsman
New onAboutToDie event for ConnectionListenerIF that gets called if the maximum-active-time is exceeded.

Revision 1.13  2006/03/24 00:17:32  billhorsman
Correct alias name

Revision 1.12  2006/01/18 14:40:06  billhorsman
Unbundled Jakarta's Commons Logging.

Revision 1.11  2005/10/07 08:11:34  billhorsman
New test for reset failure

Revision 1.10  2005/10/02 12:30:59  billhorsman
Improved test by checking connectionCount

Revision 1.9  2004/06/02 21:05:19  billhorsman
Don't log worrying stack traces for expected exceptions.

Revision 1.8  2003/09/30 18:40:16  billhorsman
New tests for test-before-use and test-after-use

Revision 1.7  2003/09/11 23:58:05  billhorsman
New test for house-keeper-test-sql

Revision 1.6  2003/03/04 10:24:40  billhorsman
removed try blocks around each test

Revision 1.5  2003/03/03 17:08:57  billhorsman
all tests now extend AbstractProxoolTest

Revision 1.4  2003/03/03 11:12:04  billhorsman
fixed licence

Revision 1.3  2003/03/02 00:53:49  billhorsman
more robust wait

Revision 1.2  2003/03/01 15:27:24  billhorsman
checkstyle

Revision 1.1  2003/02/27 18:01:48  billhorsman
completely rethought the test structure. it's now
more obvious. no new tests yet though.

*/