FELIX-4421 - Upgrade to Jetty 8:

- bumped the embedded Jetty version to latest Jetty 8 version;
- bumped minor versions of all bundles to indicate that this
  version no longer is compatible with Java 5;
- added simple integration test to verify the basic functionality
  provided by the HTTP service.



git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1565705 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/http/itest/pom.xml b/http/itest/pom.xml
new file mode 100644
index 0000000..708876c
--- /dev/null
+++ b/http/itest/pom.xml
@@ -0,0 +1,180 @@
+<!--
+    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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>org.apache.felix.http.parent</artifactId>
+        <version>3-SNAPSHOT</version>
+        <relativePath>../parent/pom.xml</relativePath>
+    </parent>
+
+    <name>Apache Felix Http Integration Tests</name>
+    <artifactId>org.apache.felix.http.itest</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <packaging>jar</packaging>
+
+    <scm>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/felix/trunk/http/sslfilter</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/felix/trunk/http/sslfilter</developerConnection>
+        <url>http://svn.apache.org/viewvc/felix/trunk/http/sslfilter/</url>
+    </scm>
+    
+    <properties>
+    	<felix.http.api.version>2.3.0-SNAPSHOT</felix.http.api.version>
+		<pax.exam.version>2.4.0</pax.exam.version>
+		<pax.exam.plugin.version>1.2.4</pax.exam.plugin.version>
+		<pax.url.aether.version>1.4.0</pax.url.aether.version>
+		<pax.swissbox.version>1.3.1</pax.swissbox.version>
+		<pax.runner.version>1.7.6</pax.runner.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>javax.servlet-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.http.api</artifactId>
+            <version>${felix.http.api.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.http.jetty</artifactId>
+            <version>${felix.http.api.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.8.2</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-all</artifactId>
+            <version>1.8.2</version>
+            <scope>test</scope>
+        </dependency>
+		<dependency>
+			<groupId>org.ops4j.pax.exam</groupId>
+			<artifactId>pax-exam-junit4</artifactId>
+			<version>${pax.exam.version}</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.ops4j.pax.exam</groupId>
+			<artifactId>pax-exam-container-forked</artifactId>
+			<version>${pax.exam.version}</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.ops4j.pax.runner</groupId>
+			<artifactId>pax-runner-no-jcl</artifactId>
+			<version>${pax.runner.version}</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.ops4j.pax.exam</groupId>
+			<artifactId>pax-exam-link-assembly</artifactId>
+			<version>${pax.exam.version}</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.ops4j.pax.exam</groupId>
+			<artifactId>pax-exam-link-mvn</artifactId>
+			<version>${pax.exam.version}</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.ops4j.pax.url</groupId>
+			<artifactId>pax-url-aether</artifactId>
+			<version>${pax.url.aether.version}</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.ops4j.pax.url</groupId>
+			<artifactId>pax-url-wrap</artifactId>
+			<version>${pax.url.aether.version}</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>javax.inject</groupId>
+			<artifactId>javax.inject</artifactId>
+			<version>1</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.felix</groupId>
+			<artifactId>org.apache.felix.framework</artifactId>
+			<version>4.0.2</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>slf4j-simple</artifactId>
+			<version>1.6.0</version>
+			<scope>compile</scope>
+		</dependency>
+    </dependencies>
+
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-compiler-plugin</artifactId>
+				<configuration>
+					<target>1.6</target>
+					<source>1.6</source>
+				</configuration>
+			</plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+            </plugin>
+            <plugin>
+                <groupId>org.ops4j.pax.exam</groupId>
+                <artifactId>maven-paxexam-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>generate-config</id>
+                        <goals>
+                            <goal>generate-depends-file</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+		</plugins>
+	</build>
+
+</project>
diff --git a/http/itest/src/test/java/org/apache/felix/http/itest/BaseIntegrationTest.java b/http/itest/src/test/java/org/apache/felix/http/itest/BaseIntegrationTest.java
new file mode 100644
index 0000000..8fa35ac
--- /dev/null
+++ b/http/itest/src/test/java/org/apache/felix/http/itest/BaseIntegrationTest.java
@@ -0,0 +1,440 @@
+/*
+ * 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.felix.http.itest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.ops4j.pax.exam.Constants.START_LEVEL_SYSTEM_BUNDLES;
+import static org.ops4j.pax.exam.Constants.START_LEVEL_TEST_BUNDLE;
+import static org.ops4j.pax.exam.CoreOptions.bootDelegationPackage;
+import static org.ops4j.pax.exam.CoreOptions.cleanCaches;
+import static org.ops4j.pax.exam.CoreOptions.felix;
+import static org.ops4j.pax.exam.CoreOptions.frameworkStartLevel;
+import static org.ops4j.pax.exam.CoreOptions.junitBundles;
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.options;
+import static org.ops4j.pax.exam.CoreOptions.url;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Scanner;
+import java.util.concurrent.CountDownLatch;
+
+import javax.inject.Inject;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.felix.http.api.ExtHttpService;
+import org.junit.After;
+import org.junit.Before;
+import org.ops4j.pax.exam.CoreOptions;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.Configuration;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.http.HttpContext;
+import org.osgi.service.http.HttpService;
+import org.osgi.service.http.NamespaceException;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * Base class for integration tests.
+ *  
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public abstract class BaseIntegrationTest
+{
+    protected static class TestFilter implements Filter
+    {
+        private final CountDownLatch m_initLatch;
+        private final CountDownLatch m_destroyLatch;
+
+        public TestFilter(CountDownLatch initLatch, CountDownLatch destroyLatch)
+        {
+            m_initLatch = initLatch;
+            m_destroyLatch = destroyLatch;
+        }
+
+        public void destroy()
+        {
+            if (m_destroyLatch != null)
+            {
+                m_destroyLatch.countDown();
+            }
+        }
+
+        public final void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException
+        {
+            filter((HttpServletRequest) req, (HttpServletResponse) resp, chain);
+        }
+
+        public void init(FilterConfig config) throws ServletException
+        {
+            if (m_initLatch != null)
+            {
+                m_initLatch.countDown();
+            }
+        }
+
+        protected void filter(HttpServletRequest req, HttpServletResponse resp, FilterChain chain) throws IOException, ServletException
+        {
+            ((HttpServletResponse) resp).setStatus(HttpServletResponse.SC_OK);
+        }
+    }
+
+    protected static class TestServlet extends HttpServlet
+    {
+        private static final long serialVersionUID = 1L;
+
+        private final CountDownLatch m_initLatch;
+        private final CountDownLatch m_destroyLatch;
+
+        public TestServlet(CountDownLatch initLatch, CountDownLatch destroyLatch)
+        {
+            m_initLatch = initLatch;
+            m_destroyLatch = destroyLatch;
+        }
+
+        @Override
+        public void destroy()
+        {
+            super.destroy();
+            if (m_destroyLatch != null)
+            {
+                m_destroyLatch.countDown();
+            }
+        }
+
+        @Override
+        public void init() throws ServletException
+        {
+            super.init();
+            if (m_initLatch != null)
+            {
+                m_initLatch.countDown();
+            }
+        }
+
+        @Override
+        protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+        {
+            resp.setStatus(HttpServletResponse.SC_OK);
+        }
+    }
+
+    private static final int DEFAULT_TIMEOUT = 10000;
+
+    protected static final String ORG_APACHE_FELIX_HTTP_JETTY = "org.apache.felix.http.jetty";
+
+    protected static void assertContent(int expectedRC, String expected, URL url) throws IOException
+    {
+        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+
+        int rc = conn.getResponseCode();
+        assertEquals("Unexpected response code,", expectedRC, rc);
+
+        if (rc >= 200 && rc < 500)
+        {
+            InputStream is = null;
+            try
+            {
+                is = conn.getInputStream();
+                assertEquals(expected, slurpAsString(is));
+            }
+            finally
+            {
+                close(is);
+                conn.disconnect();
+            }
+        }
+        else
+        {
+            InputStream is = null;
+            try
+            {
+                is = conn.getErrorStream();
+                assertEquals(expected, slurpAsString(is));
+            }
+            finally
+            {
+                close(is);
+                conn.disconnect();
+            }
+        }
+    }
+
+    protected static void assertContent(String expected, URL url) throws IOException
+    {
+        assertContent(200, expected, url);
+    }
+
+    protected static void assertResponseCode(int expected, URL url) throws IOException
+    {
+        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+        try
+        {
+            assertEquals(expected, conn.getResponseCode());
+        }
+        finally
+        {
+            conn.disconnect();
+        }
+    }
+
+    protected static void close(Closeable resource)
+    {
+        if (resource != null)
+        {
+            try
+            {
+                resource.close();
+            }
+            catch (IOException e)
+            {
+                // Ignore...
+            }
+        }
+    }
+
+    protected static URL createURL(String path)
+    {
+        if (path == null)
+        {
+            path = "";
+        }
+        while (path.startsWith("/"))
+        {
+            path = path.substring(1);
+        }
+        int port = Integer.getInteger("org.osgi.service.http.port", 8080);
+        try
+        {
+            return new URL(String.format("http://localhost:%d/%s", port, path));
+        }
+        catch (MalformedURLException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    protected static String slurpAsString(InputStream is) throws IOException
+    {
+        // See <weblogs.java.net/blog/pat/archive/2004/10/stupid_scanner_1.html>
+        Scanner scanner = new Scanner(is, "UTF-8");
+        try
+        {
+            scanner.useDelimiter("\\A");
+
+            return scanner.hasNext() ? scanner.next() : null;
+        }
+        finally
+        {
+            try
+            {
+                scanner.close();
+            }
+            catch (Exception e)
+            {
+                // Ignore...
+            }
+        }
+    }
+
+    @Inject
+    protected volatile BundleContext m_context;
+
+    @Configuration
+    public Option[] config()
+    {
+        return options(
+            bootDelegationPackage("sun.*"),
+            cleanCaches(),
+            CoreOptions.systemProperty("logback.configurationFile").value("file:src/test/resources/logback.xml"), //
+//            CoreOptions.vmOption("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8787"),
+
+            mavenBundle("org.slf4j", "slf4j-api").version("1.6.5").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+            mavenBundle("ch.qos.logback", "logback-core").version("1.0.6").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+            mavenBundle("ch.qos.logback", "logback-classic").version("1.0.6").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+
+            url("link:classpath:META-INF/links/org.ops4j.pax.exam.link").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+            url("link:classpath:META-INF/links/org.ops4j.pax.exam.inject.link").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+            url("link:classpath:META-INF/links/org.ops4j.pax.extender.service.link").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+            url("link:classpath:META-INF/links/org.ops4j.base.link").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+            url("link:classpath:META-INF/links/org.ops4j.pax.swissbox.core.link").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+            url("link:classpath:META-INF/links/org.ops4j.pax.swissbox.extender.link").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+            url("link:classpath:META-INF/links/org.ops4j.pax.swissbox.lifecycle.link").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+            url("link:classpath:META-INF/links/org.ops4j.pax.swissbox.framework.link").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+            url("link:classpath:META-INF/links/org.apache.geronimo.specs.atinject.link").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+
+            mavenBundle("org.apache.felix", ORG_APACHE_FELIX_HTTP_JETTY).versionAsInProject().startLevel(START_LEVEL_SYSTEM_BUNDLES),
+
+            junitBundles(), frameworkStartLevel(START_LEVEL_TEST_BUNDLE), felix());
+    }
+
+    @Before
+    public void setUp() throws Exception
+    {
+        assertNotNull("No bundle context?!", m_context);
+    }
+
+    @After
+    public void tearDown() throws Exception
+    {
+        Bundle bundle = getHttpJettyBundle();
+        // Restart the HTTP-service to clean all registrations...
+        if (bundle.getState() == Bundle.ACTIVE)
+        {
+            bundle.stop();
+            bundle.start();
+        }
+    }
+
+    /**
+     * Waits for a service to become available in certain time interval.
+     * @param serviceName
+     * @return
+     * @throws Exception
+     */
+    protected <T> T awaitService(String serviceName) throws Exception
+    {
+        ServiceTracker tracker = new ServiceTracker(m_context, serviceName, null);
+        tracker.open();
+        T result;
+        try
+        {
+            result = (T) tracker.waitForService(DEFAULT_TIMEOUT);
+        }
+        finally
+        {
+            tracker.close();
+        }
+        return result;
+    }
+
+    /**
+     * @param bsn
+     * @return
+     */
+    protected Bundle findBundle(String bsn)
+    {
+        for (Bundle bundle : m_context.getBundles())
+        {
+            if (bsn.equals(bundle.getSymbolicName()))
+            {
+                return bundle;
+            }
+        }
+        return null;
+    }
+
+    protected ExtHttpService getExtHttpService()
+    {
+        return getService(ExtHttpService.class.getName());
+    }
+
+    protected Bundle getHttpJettyBundle()
+    {
+        Bundle b = findBundle(ORG_APACHE_FELIX_HTTP_JETTY);
+        assertNotNull("Filestore bundle not found?!", b);
+        return b;
+    }
+
+    protected HttpService getHttpService()
+    {
+        return getService(HttpService.class.getName());
+    }
+
+    /**
+     * Obtains a service without waiting for it to become available.
+     * @param serviceName
+     * @return
+     */
+    protected <T> T getService(String serviceName)
+    {
+        ServiceTracker tracker = new ServiceTracker(m_context, serviceName, null);
+        tracker.open();
+        T result;
+        try
+        {
+            result = (T) tracker.getService();
+        }
+        finally
+        {
+            tracker.close();
+        }
+        return result;
+    }
+
+    protected void register(String pattern, Filter filter) throws ServletException, NamespaceException
+    {
+        register(pattern, filter, null);
+    }
+
+    protected void register(String pattern, Filter servlet, HttpContext context) throws ServletException, NamespaceException
+    {
+        getExtHttpService().registerFilter(servlet, pattern, null, 0, context);
+    }
+
+    protected void register(String alias, Servlet servlet) throws ServletException, NamespaceException
+    {
+        register(alias, servlet, null);
+    }
+
+    protected void register(String alias, Servlet servlet, HttpContext context) throws ServletException, NamespaceException
+    {
+        getHttpService().registerServlet(alias, servlet, null, context);
+    }
+
+    protected void register(String alias, String name) throws ServletException, NamespaceException
+    {
+        register(alias, name, null);
+    }
+
+    protected void register(String alias, String name, HttpContext context) throws ServletException, NamespaceException
+    {
+        getExtHttpService().registerResources(alias, name, context);
+    }
+
+    protected void unregister(Filter filter) throws ServletException, NamespaceException
+    {
+        getExtHttpService().unregisterFilter(filter);
+    }
+
+    protected void unregister(Servlet servlet) throws ServletException, NamespaceException
+    {
+        getExtHttpService().unregisterServlet(servlet);
+    }
+
+    protected void unregister(String alias) throws ServletException, NamespaceException
+    {
+        getExtHttpService().unregister(alias);
+    }
+}
diff --git a/http/itest/src/test/java/org/apache/felix/http/itest/HttpJettyTest.java b/http/itest/src/test/java/org/apache/felix/http/itest/HttpJettyTest.java
new file mode 100644
index 0000000..76e354f
--- /dev/null
+++ b/http/itest/src/test/java/org/apache/felix/http/itest/HttpJettyTest.java
@@ -0,0 +1,309 @@
+/*
+ * 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.felix.http.itest;
+
+import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
+import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
+import static javax.servlet.http.HttpServletResponse.SC_OK;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.ConnectException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.junit.JUnit4TestRunner;
+import org.osgi.framework.Bundle;
+import org.osgi.service.http.HttpContext;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+@RunWith(JUnit4TestRunner.class)
+public class HttpJettyTest extends BaseIntegrationTest
+{
+
+    /**
+     * Tests the starting of Jetty.
+     */
+    @Test
+    public void test00_StartJettyOk() throws Exception
+    {
+        assertTrue(getHttpJettyBundle().getState() == Bundle.ACTIVE);
+
+        assertResponseCode(SC_NOT_FOUND, createURL("/"));
+    }
+
+    /**
+     * Tests the starting of Jetty.
+     */
+    @Test
+    public void test00_StopJettyOk() throws Exception
+    {
+        Bundle bundle = getHttpJettyBundle();
+
+        assertTrue(bundle.getState() == Bundle.ACTIVE);
+
+        CountDownLatch initLatch = new CountDownLatch(1);
+        CountDownLatch destroyLatch = new CountDownLatch(1);
+
+        TestServlet servlet = new TestServlet(initLatch, destroyLatch);
+
+        register("/test", servlet);
+
+        assertTrue(initLatch.await(5, TimeUnit.SECONDS));
+
+        assertResponseCode(SC_OK, createURL("/test"));
+
+        bundle.stop();
+
+        assertTrue(destroyLatch.await(5, TimeUnit.SECONDS));
+
+        try
+        {
+            createURL("/test").openStream();
+            fail("Could connect to stopped Jetty instance?!");
+        }
+        catch (ConnectException e)
+        {
+            // Ok; expected...
+        }
+
+        bundle.start();
+
+        Thread.sleep(500); // Allow Jetty to start (still done asynchronously)...
+
+        assertResponseCode(SC_NOT_FOUND, createURL("/test"));
+    }
+
+    /**
+     * Tests that we can register a filter with Jetty and that its lifecycle is correctly controlled.
+     */
+    @Test
+    public void testRegisterFilterLifecycleOk() throws Exception
+    {
+        CountDownLatch initLatch = new CountDownLatch(1);
+        CountDownLatch destroyLatch = new CountDownLatch(1);
+
+        TestFilter filter = new TestFilter(initLatch, destroyLatch);
+
+        register("/test", filter);
+
+        assertTrue(initLatch.await(5, TimeUnit.SECONDS));
+
+        unregister(filter);
+
+        assertTrue(destroyLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    /**
+     * Tests that we can register a servlet with Jetty and that its lifecycle is correctly controlled.
+     */
+    @Test
+    public void testRegisterServletLifecycleOk() throws Exception
+    {
+        CountDownLatch initLatch = new CountDownLatch(1);
+        CountDownLatch destroyLatch = new CountDownLatch(1);
+
+        TestServlet servlet = new TestServlet(initLatch, destroyLatch);
+
+        register("/test", servlet);
+
+        assertTrue(initLatch.await(5, TimeUnit.SECONDS));
+
+        unregister(servlet);
+
+        assertTrue(destroyLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testUseServletContextOk() throws Exception
+    {
+        CountDownLatch initLatch = new CountDownLatch(1);
+        CountDownLatch destroyLatch = new CountDownLatch(1);
+
+        HttpContext context = new HttpContext()
+        {
+            public boolean handleSecurity(HttpServletRequest request, HttpServletResponse response) throws IOException
+            {
+                return true;
+            }
+
+            public URL getResource(String name)
+            {
+                try
+                {
+                    File f = new File("src/test/resources/resource/" + name);
+                    if (f.exists())
+                    {
+                        return f.toURI().toURL();
+                    }
+                }
+                catch (MalformedURLException e)
+                {
+                    fail();
+                }
+                return null;
+            }
+
+            public String getMimeType(String name)
+            {
+                return null;
+            }
+        };
+
+        TestServlet servlet = new TestServlet(initLatch, destroyLatch)
+        {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            public void init(ServletConfig config) throws ServletException
+            {
+                ServletContext context = config.getServletContext();
+                try
+                {
+                    assertEquals("", context.getContextPath());
+                    assertNotNull(context.getResource("test.html"));
+                    assertNotNull(context.getRealPath("test.html"));
+                }
+                catch (MalformedURLException e)
+                {
+                    fail();
+                }
+
+                super.init(config);
+            }
+        };
+
+        register("/foo", servlet, context);
+
+        URL testURL = createURL("/foo");
+
+        assertTrue(initLatch.await(5, TimeUnit.SECONDS));
+
+        assertResponseCode(SC_OK, testURL);
+
+        unregister(servlet);
+
+        assertTrue(destroyLatch.await(5, TimeUnit.SECONDS));
+
+        assertResponseCode(SC_NOT_FOUND, testURL);
+    }
+
+    /**
+     * Tests that we can register servlets and filters together.
+     */
+    @Test
+    public void testHandleMultipleRegistrationsOk() throws Exception
+    {
+        CountDownLatch initLatch = new CountDownLatch(3);
+        CountDownLatch destroyLatch = new CountDownLatch(3);
+
+        TestServlet servlet1 = new TestServlet(initLatch, destroyLatch)
+        {
+            private static final long serialVersionUID = 1L;
+
+            final AtomicLong m_count = new AtomicLong();
+
+            @Override
+            protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                resp.setStatus(SC_OK);
+                resp.getWriter().printf("1.%d", m_count.incrementAndGet());
+                resp.flushBuffer();
+            }
+        };
+
+        TestServlet servlet2 = new TestServlet(initLatch, destroyLatch)
+        {
+            private static final long serialVersionUID = 1L;
+
+            final AtomicLong m_count = new AtomicLong();
+
+            @Override
+            protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                resp.setStatus(SC_OK);
+                resp.getWriter().printf("2.%d", m_count.incrementAndGet());
+                resp.flushBuffer();
+            }
+        };
+
+        TestFilter filter = new TestFilter(initLatch, destroyLatch)
+        {
+            @Override
+            protected void filter(HttpServletRequest req, HttpServletResponse resp, FilterChain chain) throws IOException, ServletException
+            {
+                String param = req.getParameter("param");
+                if ("forbidden".equals(param))
+                {
+                    resp.reset();
+                    resp.sendError(SC_FORBIDDEN);
+                    resp.flushBuffer();
+                }
+                else
+                {
+                    chain.doFilter(req, resp);
+                }
+            }
+        };
+
+        register("/test1", servlet1);
+        register("/test2", servlet2);
+        register("/test.*", filter);
+
+        assertTrue(initLatch.await(5, TimeUnit.SECONDS));
+
+        assertContent("1.1", createURL("/test1"));
+        assertContent("2.1", createURL("/test2"));
+        assertContent("2.2", createURL("/test2"));
+        assertContent("1.2", createURL("/test1"));
+        assertContent("2.3", createURL("/test2"));
+
+        assertResponseCode(SC_FORBIDDEN, createURL("/test2?param=forbidden"));
+        assertResponseCode(SC_NOT_FOUND, createURL("/?test=forbidden"));
+
+        assertContent("2.4", createURL("/test2"));
+        assertContent("1.3", createURL("/test1"));
+
+        assertResponseCode(SC_FORBIDDEN, createURL("/test?param=forbidden"));
+
+        unregister(servlet1);
+        unregister(servlet2);
+        unregister(filter);
+
+        assertTrue(destroyLatch.await(5, TimeUnit.SECONDS));
+    }
+}
diff --git a/http/itest/src/test/java/org/apache/felix/http/itest/ResourceTest.java b/http/itest/src/test/java/org/apache/felix/http/itest/ResourceTest.java
new file mode 100644
index 0000000..02cf276
--- /dev/null
+++ b/http/itest/src/test/java/org/apache/felix/http/itest/ResourceTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.felix.http.itest;
+
+import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
+import static javax.servlet.http.HttpServletResponse.SC_OK;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.junit.JUnit4TestRunner;
+import org.osgi.service.http.HttpContext;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+@RunWith(JUnit4TestRunner.class)
+public class ResourceTest extends BaseIntegrationTest
+{
+
+    @Test
+    public void testHandleResourceRegistrationOk() throws Exception
+    {
+        CountDownLatch initLatch = new CountDownLatch(1);
+        CountDownLatch destroyLatch = new CountDownLatch(1);
+
+        HttpContext context = new HttpContext()
+        {
+            public boolean handleSecurity(HttpServletRequest request, HttpServletResponse response) throws IOException
+            {
+                return true;
+            }
+
+            public URL getResource(String name)
+            {
+                try
+                {
+                    File f = new File("src/test/resources/" + name);
+                    if (f.exists())
+                    {
+                        return f.toURI().toURL();
+                    }
+                }
+                catch (MalformedURLException e)
+                {
+                    fail();
+                }
+                return null;
+            }
+
+            public String getMimeType(String name)
+            {
+                return null;
+            }
+        };
+
+        TestServlet servlet = new TestServlet(initLatch, destroyLatch);
+
+        register("/", "/resource", context);
+        register("/test", servlet, context);
+
+        URL testHtmlURL = createURL("/test.html");
+        URL testURL = createURL("/test");
+
+        assertTrue(initLatch.await(5, TimeUnit.SECONDS));
+
+        assertResponseCode(SC_OK, testHtmlURL);
+        assertResponseCode(SC_OK, testURL);
+
+        unregister(servlet);
+
+        assertTrue(destroyLatch.await(5, TimeUnit.SECONDS));
+
+        assertResponseCode(SC_OK, testHtmlURL);
+        assertResponseCode(SC_NOT_FOUND, testURL);
+
+        unregister("/");
+
+        assertResponseCode(SC_NOT_FOUND, testHtmlURL);
+    }
+}
diff --git a/http/itest/src/test/resources/logback.xml b/http/itest/src/test/resources/logback.xml
new file mode 100644
index 0000000..03dedea
--- /dev/null
+++ b/http/itest/src/test/resources/logback.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<configuration>
+	<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+		<encoder>
+			<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
+			</pattern>
+		</encoder>
+	</appender>
+
+	<root level="WARN">
+		<appender-ref ref="STDOUT" />
+	</root>
+
+	<logger name="org.ops4j" level="WARN" />
+</configuration>
diff --git a/http/itest/src/test/resources/resource/test.html b/http/itest/src/test/resources/resource/test.html
new file mode 100644
index 0000000..5573d33
--- /dev/null
+++ b/http/itest/src/test/resources/resource/test.html
@@ -0,0 +1,19 @@
+<!--
+    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.
+-->
+<html><body><h1>TEST</h1></body></html>
\ No newline at end of file