/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2009 Bull S.A.S.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.itests.intravm.osgi.httpservice;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.URL;

import org.ops4j.pax.swissbox.tinybundles.core.TinyBundle;
import org.ops4j.pax.swissbox.tinybundles.core.TinyBundles;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.service.http.HttpService;
import org.ow2.jonas.itests.intravm.JOnASLauncher;
import org.ow2.jonas.itests.intravm.osgi.httpservice.resource.RegisterResourceTestActivator;
import org.ow2.jonas.itests.intravm.osgi.httpservice.servlet.RegisterServletTestActivator;
import org.ow2.jonas.itests.intravm.osgi.httpservice.servlet.SimpleTestServlet;
import org.ow2.jonas.management.ServiceManager;
import org.ow2.util.file.FileUtils;
import org.ow2.util.url.URLUtils;
import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

/**
 * Tests of the OSGi http service.
 * @author Guillaume Porcher
 * @author Florent Benoit
 */
public class OSGiHttpServiceTest extends JOnASLauncher {

    /**
     * URL for the resource bundle.
     */
    private URL resourceBundleURL = null;

    /**
     * URL for the first servlet bundle.
     */
    private URL servletBundle1URL = null;

    /**
     * URL for the second servlet bundle.
     */
    private URL servletBundle2URL = null;

    /**
     * One second delay.
     */
    private static final long ONE_SECOND = 1000L;

    /**
     * Waiting time = 5 seconds.
     */
    private static final long SLEEP = 5 * ONE_SECOND;

    /**
     * HTTP service.
     */
    private Object httpService = null;

    /**
     * Port number.
     */
    private String httpServicePort = null;

    /**
     * JOnAS service manager service.
     */
    private Object serviceManager = null;

    /**
     * Create bundles in order to be ready for the tests.
     * @throws Exception
     */
    @BeforeClass
    public void createBundles() throws Exception {
        // create bundle for resources
        TinyBundle resourceBundle = TinyBundles.newBundle();
        resourceBundle.add("files/index.html", OSGiHttpServiceTest.class.getClassLoader().getResource("index.html"));
        resourceBundle.add("files/folder/test.html", OSGiHttpServiceTest.class.getClassLoader().getResource("test.html"));
        resourceBundle.add(RegisterResourceTestActivator.class);
        resourceBundle.set(Constants.BUNDLE_SYMBOLICNAME, "RegisterResourceBundle");
        resourceBundle.set(Constants.BUNDLE_ACTIVATOR, RegisterResourceTestActivator.class.getName());
        InputStream resourceBundleInputStream = resourceBundle.build(TinyBundles.withBnd());
        File bundleFile = File.createTempFile("rsrbundle", ".jar");
        FileUtils.dump(resourceBundleInputStream, bundleFile);
        resourceBundleURL = URLUtils.fileToURL(bundleFile);

        // Create bundle for servlet
        TinyBundle servletBundle1 = TinyBundles.newBundle();
        servletBundle1.add(RegisterServletTestActivator.class);
        servletBundle1.add(SimpleTestServlet.class);
        servletBundle1.set(Constants.BUNDLE_SYMBOLICNAME, "RegisterServletBundle");
        servletBundle1.set(Constants.BUNDLE_ACTIVATOR, RegisterServletTestActivator.class.getName());
        servletBundle1.set(Constants.EXPORT_PACKAGE, "");
        servletBundle1.set(Constants.BUNDLE_VERSION, "1.0.0");
        InputStream servletBundle1InputStream = servletBundle1.build(TinyBundles.withBnd());
        File servlet1File = File.createTempFile("servletbundle1", ".jar");
        FileUtils.dump(servletBundle1InputStream, servlet1File);
        servletBundle1URL = URLUtils.fileToURL(servlet1File);

        // Create bundle for servlet with version 2
        TinyBundle servletBundle2 = TinyBundles.newBundle();
        servletBundle2.add(RegisterServletTestActivator.class);
        servletBundle2.add(SimpleTestServlet.class);
        servletBundle2.set(Constants.BUNDLE_SYMBOLICNAME, "RegisterServletBundle2");
        servletBundle2.set(Constants.BUNDLE_ACTIVATOR, RegisterServletTestActivator.class.getName());
        servletBundle2.set(Constants.EXPORT_PACKAGE, "");
        servletBundle2.set(Constants.BUNDLE_VERSION, "2.0.0");
        InputStream servletBundle2InputStream = servletBundle2.build(TinyBundles.withBnd());
        File servlet2File = File.createTempFile("servletbundle2", ".jar");
        FileUtils.dump(servletBundle2InputStream, servlet2File);
        servletBundle2URL = URLUtils.fileToURL(servlet2File);

    }

    /**
     * Test the access to the HTTP service.
     * @throws InterruptedException if waiting fails
     */
    @BeforeClass
    public void waitForServices() throws InterruptedException {
        // wait for initialization
        final long maxWaitTime = System.currentTimeMillis() + SLEEP;

        ServiceReference httpServiceReference = null;
        while (httpServiceReference == null && System.currentTimeMillis() < maxWaitTime) {
            Thread.sleep(ONE_SECOND);
            httpServiceReference = getBundleContext().getServiceReference(HttpService.class.getName());
        }
        Assert.assertNotNull(httpServiceReference, "HttpService is not registered");
        httpService = getBundleContext().getService(httpServiceReference);
        Assert.assertNotNull(httpService, "HttpService is not registered");
        httpServicePort = (String) httpServiceReference.getProperty("org.osgi.service.http.port");

        ServiceReference serviceManagerServiceReference = null;
        while (serviceManagerServiceReference == null && System.currentTimeMillis() < maxWaitTime) {
            serviceManagerServiceReference = getBundleContext().getServiceReference(ServiceManager.class.getName());
            if (serviceManagerServiceReference == null) {
                Thread.sleep(ONE_SECOND);
            }
        }
        Assert.assertNotNull(serviceManagerServiceReference, "ServiceManager is not registered");
        serviceManager = getBundleContext().getService(serviceManagerServiceReference);

    }

    /**
     * Test backwards compatibility.
     * @throws Exception on failure
     */
    @Test
    public void testContextRoot() throws Exception {
        // wait for context root
        final String expected = "JOnAS Root Context";
        final URL url = new URL("http://localhost:" + httpServicePort + "/");
        isExpectedContent(expected, url);
    }

    /**
     * Test for registering a servlet on the http service.
     * @throws Exception if registering fails
     */
    @Test
    public void testRegisterServlet() throws Exception {

        String context = "/simpleServletTest";
        Method[] methods = httpService.getClass().getDeclaredMethods();
        for (Method method : methods) {
            if (method.getName().equals("registerServlet")) {
                method.invoke(httpService, context, new SimpleTestServlet("2503"), null, null);
            }
        }

        // check index.html
        final URL url = new URL("http://localhost:" + httpServicePort + context);
        isExpectedContent("This is a test content version 2503 for request", url);

        Method unregisterServletMethod = httpService.getClass().getDeclaredMethod("unregister", String.class);
        unregisterServletMethod.invoke(httpService, context);

        try {
            url.openStream();
            Assert.fail("url still available");
        } catch (final IOException e) {
            Assert.assertTrue(e.getMessage().contains("HTTP response code: 400") || e.getMessage().contains(url.toString()));
        }
    }

    /**
     * Test for {@link HttpService#registerResources(String, String, org.osgi.service.http.HttpContext)}.
     * @throws InvalidSyntaxException
     * @throws IOException
     * @throws InterruptedException
     * @throws BundleException
     */
    @Test
    public void testRegisterResources() throws Exception {

        // start resource bundle
        final Bundle resourceBundle = getBundleContext().installBundle(resourceBundleURL.toString());
        resourceBundle.start();

        // check index.html
        final URL url = new URL("http://localhost:" + httpServicePort + "/test/index.html");
        final URL refUrl = OSGiHttpServiceTest.class.getClassLoader().getResource("index.html");
        this.checkContent(url, refUrl);

        // check folder/test.html
        final URL url2 = new URL("http://localhost:" + httpServicePort + "/test/folder/test.html");
        final URL refUrl2 = OSGiHttpServiceTest.class.getClassLoader().getResource("test.html");
        this.checkContent(url2, refUrl2);

        resourceBundle.stop();
        resourceBundle.uninstall();

        // check url not available
        final URL url3 = new URL("http://localhost:" + httpServicePort + "/test/index.html");
        try {
            url3.openStream();
            Assert.fail("url still available");
        } catch (final IOException e) {
            Assert.assertTrue(e.getMessage().contains("HTTP response code: 400") || e.getMessage().contains(url3.toString()));
        }
    }

    /**
     * Test for
     * {@link HttpService#registerServlet(String, javax.servlet.Servlet, java.util.Dictionary, org.osgi.service.http.HttpContext)}
     * .
     * @throws InvalidSyntaxException
     * @throws IOException
     * @throws InterruptedException
     * @throws BundleException
     */
    @Test
    public void testRegisterBundleServlet() throws InvalidSyntaxException, IOException, InterruptedException, BundleException {

        // start servlet bundle
        final Bundle bundle = getBundleContext().installBundle(servletBundle1URL.toString());
        bundle.start();

        // check index.html
        final URL url = new URL("http://localhost:" + httpServicePort + "/servletTest/index.html");

        final String expected = "This is a test content version 1.0.0 for request: /index.html";
        Assert.assertEquals(expected, this.getContentAsString(url));

        bundle.stop();
        bundle.uninstall();

        // check url not available
        final URL url2 = new URL("http://localhost:" + httpServicePort + "/servletTest/index.html");
        try {
            url2.openStream();
            Assert.fail("url available");
        } catch (final IOException e) {
            Assert.assertTrue(e.getMessage().contains("HTTP response code: 400") || e.getMessage().contains(url2.toString()));
        }
    }

    /**
     * Enable versioning.
     * @throws Exception on failure
     */
    @Test
    public void enableVersioning() throws Exception {
        Method getServiceStateMethod = serviceManager.getClass().getDeclaredMethod("getServiceState", String.class);
        Method startServiceMethod = serviceManager.getClass().getDeclaredMethod("startService", String.class);

        String state = (String) getServiceStateMethod.invoke(serviceManager, "versioning");
        Assert.assertNull(state, "the versioning service is not active by default");

        startServiceMethod.invoke(serviceManager, "versioning");

        final long maxWaitTime = System.currentTimeMillis() + SLEEP;
        while (!"RUNNING".equals(state) && System.currentTimeMillis() < maxWaitTime) {
            Thread.sleep(ONE_SECOND);
            state = (String) getServiceStateMethod.invoke(serviceManager, "versioning");
        }
        Assert.assertEquals("RUNNING", state, "the versioning service is not running");
    }

    /**
     * Test for servlet versioning.
     * @throws InvalidSyntaxException
     * @throws IOException
     * @throws InterruptedException
     * @throws BundleException
     */
    @Test(dependsOnMethods = {"testRegisterServlet", "enableVersioning"})
    public void testVersioning() throws InvalidSyntaxException, IOException, InterruptedException, BundleException {

        final URL url = new URL("http://localhost:" + httpServicePort + "/servletTest/index.html");

        // start bundle v1
        final Bundle bundle1 = getBundleContext().installBundle(servletBundle1URL.toString());
        bundle1.start();

        // check versioned url is available
        final URL url1 = new URL("http://localhost:" + httpServicePort + "/servletTest-1.0.0/index.html");
        final String expected1 = "This is a test content version 1.0.0 for request: /index.html";
        Assert.assertEquals(expected1, this.getContentAsString(url1));

        // check non-versioned url is available and pointing to v1
        Assert.assertEquals(expected1, this.getContentAsString(url));

        // start bundle 2
        final Bundle bundle2 = getBundleContext().installBundle(servletBundle2URL.toString());
        bundle2.start();

        // check versioned url is available
        final URL url2 = new URL("http://localhost:" + httpServicePort + "/servletTest-2.0.0/index.html");
        final String expected2 = "This is a test content version 2.0.0 for request: /index.html";
        Assert.assertEquals(expected2, this.getContentAsString(url2));

        // check non-versioned url is available and still pointing to v1
        Assert.assertEquals(expected1, this.getContentAsString(url));

        // stop bundle 1
        bundle1.stop();

        // check non-versioned url is unavailable
        try {
            url.openStream();
            Assert.fail("url still available");
        } catch (final IOException e) {
            Assert.assertTrue(e.getMessage().contains("HTTP response code: 400") || e.getMessage().contains(url.toString()));
        }

        // check versioned url is available
        Assert.assertEquals(expected2, this.getContentAsString(url2));

        // cleanup
        bundle1.uninstall();
        bundle2.stop();
        bundle2.uninstall();
    }

    /**
     * Checks the string content in local and remote urls are the same. Throw
     * {@link AssertionError} if they are not the same.
     * @param remoteURL the remote url
     * @param localURL the reference url
     * @throws IOException if an I/O exception occurs.
     * @throws InterruptedException if sleeping is not done
     */
    private void checkContent(final URL remoteURL, final URL localURL) throws IOException, InterruptedException {
        // wait for initialization
        final long stopTime = System.currentTimeMillis() + SLEEP;

        final String localContent = this.getContentAsString(localURL);

        String remoteContent = null;
        while (System.currentTimeMillis() < stopTime) {
            Thread.sleep(ONE_SECOND);
            remoteContent = this.getContentAsString(remoteURL);
            if (remoteContent.equals(localContent)) {
                break;
            }
        }

        Assert.assertEquals(remoteContent, localContent);
    }

    /**
     * Check that the content on the given URL is the expected content.
     * @param expectedContent the expected content
     * @param url the url on which to get content
     * @throws InterruptedException if cannot wait
     * @throws IOException if stream is not opened
     */
    protected void isExpectedContent(final String expectedContent, final URL url) throws InterruptedException, IOException {
        // wait for initialization
        final long maxWaitTime = System.currentTimeMillis() + SLEEP;

        String content = null;
        while (System.currentTimeMillis() < maxWaitTime) {
            Thread.sleep(ONE_SECOND);
            content = this.getContentAsString(url);
            if (content != null && content.contains(expectedContent)) {
                return;
            }
        }
        Assert.fail("The content '" + content + "' does not contain '" + expectedContent + "'.");
    }

    /**
     * Get the URL content as a String.
     * @param url the url
     * @return the URL content as a String or the exception message
     * @throws IOException if there is a problem with streams
     */
    private String getContentAsString(final URL url) throws IOException {
        final InputStream inStream = url.openStream();
        final ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try {
            int c;
            while ((c = inStream.read()) != -1) {
                bos.write(c);
            }
        } finally {
            if (inStream != null) {
                inStream.close();
            }
            if (bos != null) {
                bos.flush();
            }
        }
        final String urlString = bos.toString();
        return urlString;
    }
}
