/**
 *  Derby - Class org.apache.derbyTesting.functionTests.tests.derbynet.DerbyNetAutoStartTest
 *  
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.apache.derbyTesting.functionTests.tests.derbynet;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Locale;

import junit.framework.Test;
import junit.framework.TestSuite;

import org.apache.derby.drda.NetworkServerControl;
import org.apache.derbyTesting.functionTests.util.PrivilegedFileOpsForTests;
import org.apache.derbyTesting.junit.BaseJDBCTestCase;
import org.apache.derbyTesting.junit.Derby;
import org.apache.derbyTesting.junit.NetworkServerTestSetup;
import org.apache.derbyTesting.junit.TestConfiguration;

/**
 * Test the network server derby.drda.startNetworkServer property.
 * 
 * Test that:
 * <ul>

 * <li> 1 ) The network server is not started when the property value is false.
 * <li> 2 ) The network server is started when the property value is true, and
 * <li>  a) uses the default port when the port property is not specified.
 * <li>  b) uses a non-default port when a port number is specified
 * <li>  c) uses an invalid port number (-1)
 * <li> 3 ) A message is printed to derby.log when the server is 
 *          already started.
 * </ul>
 */

public class DerbyNetAutoStartTest extends BaseJDBCTestCase {

    private Locale oldLocale = Locale.getDefault();
    
    public DerbyNetAutoStartTest(String name) {
        super(name);

    }

    //args to helper method. With or without port
    private static boolean WITHOUTPORT  = false;
    private static boolean WITHPORT  = true;

    /**
     * Do some steps to prepare for the test.
     */
    public void setUp() {
        // make sure no network server is running
        TestConfiguration.getCurrent().shutdownEngine();
    }

    /**
     * Test case 1
     * Test that if derby.drda.startNetworkServer property is false
     * that server does not come up.
     * 
     * @throws Exception
     */
    public void testStartNetworkServerFalse() throws Exception {
        setSystemProperty("derby.drda.startNetworkServer", "false");         
        // Boot with an embedded connection
        // Should not start network server
        getConnection();
        NetworkServerControl ns = 
                NetworkServerTestSetup.getNetworkServerControl();

        // Verify the server is not up
        assertFalse(NetworkServerTestSetup.pingForServerUp(ns,null, false));
    }

    /**
     * Test case 2a.
     * Test setting derby.drda.startNetworkServer property without
     * specifying anything in the port number property.
     * Should start, using the default port.
     * 
     * To avoid possible conflict with other tests running concurrently,
     * this test may only run if baseport is not set and we are 
     * using the default 1527 port
     * 
     * @throws Exception
     */
    public void ttestStartNetworkServerTrueNoPort() throws Exception {
        startNetworkServerTrueHelper(WITHOUTPORT);
    }

    /**
     * Test case 2b.
     * Test setting derby.drda.startNetworkServer property
     * and specifying a port number
     * 
     * @throws Exception
     */
    public void testStartNetworkServerTrueWithPort() throws Exception {
        startNetworkServerTrueHelper(WITHPORT);
    }

    /**
     * Test case 2c.
     * Test setting derby.drda.startNetworkServer property
     * and specifying an invalid port number
     * Should fail to start network server
     * 
     * @throws Exception
     */
    public void testStartNetworkServerTrueWithInvalidPort() throws Exception {
        setSystemProperty("derby.drda.startNetworkServer", "true");
        // Note that internally, portNumber -1 means 'no port number provided'
        setSystemProperty("derby.drda.portNumber", "-1");
        // Boot with an embedded connection
        // Should not start network server
        // But it still appears to find an embedded connection.

        // Check by attempting something on a connection
        // This will currently print an InvocationException to the console
        // Is it a bug that it will not print to derby.log?
        // But, for now, capture the output and throw it away
        final PrintStream realSystemOut = System.out;
        final PrintStream realSystemErr = System.err;
        ByteArrayOutputStream serverOutputBOS = new ByteArrayOutputStream();
        final PrintStream serverOutputOut = new PrintStream( serverOutputBOS);
        AccessController.doPrivileged(new PrivilegedAction() {
            public Object run() {
                System.setOut(new PrintStream(serverOutputOut));
                System.setErr(new PrintStream(serverOutputOut));
                return null;
            }
        });

        try {
            try
            {
                // Network start fails, but we get an Embedded connection
                DatabaseMetaData dbmd = getConnection().getMetaData();
                ResultSet rs = dbmd.getSchemas();
                assertNotNull(rs);
                rs.close();
            }
            catch( SQLException e)
            {
                fail();
            }
        } finally {
            // Restore the original out streams
            AccessController.doPrivileged(new PrivilegedAction() {
                public Object run() {
                    System.setOut(realSystemOut);
                    System.setErr(realSystemErr);
                    return null;
                }
            });
        }

        // Verify the server - use default port - is not up
        NetworkServerControl ns = 
                NetworkServerTestSetup.getNetworkServerControl();
        assertFalse(NetworkServerTestSetup.pingForServerUp(ns,null, false));
    }

    /**
     * Helper method that actually starts the server.
     *
     * @param withport
     * @throws Exception
     */
    private void startNetworkServerTrueHelper(boolean withport) 
            throws Exception  {
        int theport = withport ? 
                TestConfiguration.getCurrent().getNextAvailablePort() :
                    TestConfiguration.getCurrent().getBasePort();
                
        setSystemProperty("derby.drda.startNetworkServer", "true");
        if (withport)
        {
            setSystemProperty("derby.drda.portNumber",
                    Integer.toString(theport));
        }
        // Boot with an embedded connection
        // Should start network server
        getConnection();

        // Check the server is up and then bring it back down
        NetworkServerControl ns = NetworkServerTestSetup
                .getNetworkServerControl(theport);
        NetworkServerTestSetup.waitForServerStart(ns);
        ns.shutdown();
        assertFalse
            (NetworkServerTestSetup.pingForServerUp(ns, null, false));
    }

    /**
     * Test case 3
     * Test that if a network server is already running on 
     * a certain port, starting the server after setting 
     * derby.drda.startNetworkServer reflects an error message
     * indicating the server is already in use.
     * 
     * To avoid possible conflict with other tests running concurrently,
     * this test will also set derby.drda.portNumber.
     * 
     * @throws Exception
     */
    public void testStartNetworkServerLogMessageOnDualStart()
            throws Exception {
        final Locale newLocale = Locale.ENGLISH;
        // first force English locale
        AccessController.doPrivileged
        (new java.security.PrivilegedAction() {
            public Object run() {
                Locale.setDefault(newLocale);
                return null;
            }
        }
        );
        int doubleport = TestConfiguration.getCurrent().getPort();
        // start a network server
        NetworkServerControl ns = 
                NetworkServerTestSetup.getNetworkServerControl(doubleport);
        ns.start(null);
        NetworkServerTestSetup.waitForServerStart(ns);
        // shutdown to ensure getConnection reads the properties
        TestConfiguration.getCurrent().shutdownEngine();

        setSystemProperty("derby.drda.startNetworkServer", "true");
        setSystemProperty("derby.drda.portNumber",
                Integer.toString(doubleport));
        // Boot with an embedded connection
        // Should attempt to start network server
        getConnection();

        // Check the server is still up
        assertTrue(NetworkServerTestSetup.pingForServerUp(ns, null, true));

        String logFileName = 
                getSystemProperty("derby.system.home") + 
                File.separator + "derby.log";
        // Give it a little time to write the message        
        // There should be a warning in the derby.log file.
        // With some JVMS there will be a java.net.BindException
        // But always there will be the more generic message.
        // Note that by checking on the generic message, we cannot
        // distinguish the expected from any other exception.
        String expectedString = 
                "An exception was thrown during network server startup";
        final long startTime = System.currentTimeMillis();
        final long waitTime = NetworkServerTestSetup.getWaitTime();
        while (true)
        {
            Thread.sleep(1000);
            if (checkLog( logFileName, new String[] {expectedString})){
                break;
            }
            long elapsed = System.currentTimeMillis() - startTime;
            if (elapsed > waitTime) {
                fail("did not find the expected string: " + expectedString
                        + " within the maximum wait time " + waitTime);
            }
        }

        assertTrue(checkLog( logFileName, new String[] {expectedString}));
        
        ns.shutdown();
    }

    private static boolean checkLog( String logFileName, String[] expected)
            throws IOException
    {
        boolean allFound = true;
        boolean[] found = new boolean[ expected.length];
        FileInputStream is =
                PrivilegedFileOpsForTests
                .getFileInputStream(new File(logFileName));
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        String logLine; 
        while((logLine = br.readLine()) != null)
        {
            // to print out derby.log, uncomment this line:
            // System.out.println(logLine);
            for( int i = 0; i < expected.length; i++)
            {
                if( (! found[i]) && logLine.indexOf( expected[i]) >= 0)
                    found[i] = true;
            }
        }
        for( int i = 0; i < expected.length; i++)
        {
            if( ! found[i])
            {
                allFound = false;
            }
        }
        return allFound;
    } // end of checkLog

    public static Test suite() {
        TestSuite suite = new TestSuite("DerbyNetAutoStartTest");
        suite.addTest(baseSuite("DerbyNetAutoStartTest:embedded"));
        return suite;
    }

    private static Test baseSuite(String name) {

        TestSuite suite = new TestSuite(name);
        // Need derbynet.jar in the classpath, and cannot run with ME/JSR169/cdc profile
        if (!Derby.hasServer())
            return suite;
        // Adds all tests that can run with baseport set or not.
        suite.addTestSuite(DerbyNetAutoStartTest.class);
        if (getSystemProperty("derby.tests.basePort") != null )
        {
            return suite;
        }
        // We assume, that if baseport is set, then the intention is that
        // tests are run concurrently, so we cannot use the default port
        // 1527. Lists tests that rely on/test the usage of that port here:
        suite.addTest
            (new DerbyNetAutoStartTest("ttestStartNetworkServerTrueNoPort"));
        return suite;
    }

    protected void tearDown() throws Exception {
        // unset the system properties
        removeSystemProperty("derby.drda.startNetworkServer");
        removeSystemProperty("derby.drda.portNumber");
        // set the old locale back to the original
        AccessController.doPrivileged
        (new java.security.PrivilegedAction() {
            public Object run() {
                Locale.setDefault(oldLocale);
                return null;
            }
        }
        );
        oldLocale=null;
        super.tearDown();
    }
}