Initial attempt at moving over Oscar's Jetty-based HTTP Service implementation
(FELIX-9).


git-svn-id: https://svn.apache.org/repos/asf/incubator/felix/trunk@395268 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/org.apache.felix.http.jetty/src/main/java/org/apache/felix/http/jetty/Activator.java b/org.apache.felix.http.jetty/src/main/java/org/apache/felix/http/jetty/Activator.java
new file mode 100644
index 0000000..76467cd
--- /dev/null
+++ b/org.apache.felix.http.jetty/src/main/java/org/apache/felix/http/jetty/Activator.java
@@ -0,0 +1,250 @@
+/*
+ *   Copyright 2006 The Apache Software Foundation
+ *
+ *   Licensed 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.jetty;
+
+import java.lang.reflect.Constructor;
+
+import org.mortbay.http.HashUserRealm;
+import org.mortbay.http.HttpServer;
+import org.mortbay.http.SocketListener;
+import org.mortbay.util.Code;
+import org.mortbay.util.InetAddrPort;
+import org.osgi.framework.*;
+import org.osgi.service.http.HttpService;
+import org.mortbay.http.SunJsseListener;
+import org.mortbay.http.JsseListener;
+
+/**
+ *  Basic implementation of OSGi HTTP service 1.1.
+ *
+ *  TODO:
+ *
+ *      - fuller suite of testing and compatibility tests
+ *
+ *      - only exposed params are those defined in the OSGi spec. Jetty is
+ *        very tunable via params, some of which it may be useful to expose
+ *
+ *      - no cacheing is performed on delivered resources. Although not part
+ *        of the OSGi spec, it also isn't precluded and would enhance
+ *        performance in a high usage environment. Jetty's ResourceHandler
+ *        class could be a model for this.
+ *
+ *      - scanning the Jetty ResourceHandler class it's clear that there are
+ *        many other sophisticated areas to do with resource handling such
+ *        as checking date and range fields in the http headers. It's not clear
+ *        whether any of these play a part in the OSGi service - the spec
+ *        just describes "returning the contents of the URL to the client" which
+ *        doesn't state what other HTTP handling might be compliant or desirable
+ */
+public class Activator implements BundleActivator
+{
+    protected static boolean debug = false;
+
+    private BundleContext m_bundleContext = null;
+    private ServiceRegistration m_svcReg = null;
+    private HttpServiceFactory  m_httpServ = null;
+    private HttpServer m_server = null;
+
+    private int m_httpPort;
+    private int m_httpsPort;
+
+
+    public void start(BundleContext bundleContext)
+        throws BundleException
+    {
+        m_bundleContext = bundleContext;
+
+        // org.mortbay.util.Loader needs this (used for JDK 1.4 log classes)
+        Thread.currentThread().setContextClassLoader(
+                this.getClass().getClassLoader());
+        
+        String optDebug =
+            m_bundleContext.getProperty("org.apache.felix.http.jetty.debug");
+        if (optDebug != null && optDebug.toLowerCase().equals("true"))
+        {
+            Code.setDebug(true);
+            debug = true;
+        }
+
+        // get default HTTP and HTTPS ports as per the OSGi spec
+        try
+        {
+            m_httpPort = Integer.parseInt(m_bundleContext.getProperty(
+                    "org.osgi.service.http.port"));
+        }
+        catch (Exception e)
+        {
+            // maybe log a message saying using default?
+            m_httpPort = 80;
+        }
+
+        try
+        {
+            // TODO: work out how/when we should use the HTTPS port
+            m_httpsPort = Integer.parseInt(m_bundleContext.getProperty(
+                    "org.osgi.service.http.port.secure"));
+        }
+        catch (Exception e)
+        {
+            // maybe log a message saying using default?
+            m_httpsPort = 443;
+        }
+
+        try
+        {
+            initializeJetty();
+
+        } catch (Exception ex) {
+            //TODO: maybe throw a bundle exception in here?
+            System.out.println("Http2: " + ex);
+            return;
+        }
+
+        m_httpServ = new HttpServiceFactory();
+        m_svcReg = m_bundleContext.registerService(
+            HttpService.class.getName(), m_httpServ, null);
+    }
+
+    
+    public void stop(BundleContext bundleContext)
+        throws BundleException
+    {
+        //TODO: wonder if we need to closedown service factory ???
+
+        if (m_svcReg != null)
+        {
+            m_svcReg.unregister();
+        }
+
+        try
+        {
+            m_server.stop();
+        }
+        catch (Exception e)
+        {
+            //TODO: log some form of error
+        }
+    }
+
+    protected void initializeJetty()
+        throws Exception
+    {
+        //TODO: Maybe create a separate "JettyServer" object here?
+        // Realm
+        HashUserRealm realm =
+            new HashUserRealm("OSGi HTTP Service Realm");
+
+        // Create server
+        m_server = new HttpServer();
+        m_server.addRealm(realm);
+
+        // Add a regular HTTP listener
+        SocketListener listener = null;
+        listener = (SocketListener)
+            m_server.addListener(new InetAddrPort(m_httpPort));        
+        listener.setMaxIdleTimeMs(60000);
+        
+        // See if we need to add an HTTPS listener
+        String enableHTTPS = m_bundleContext.getProperty("org.ungoverned.osgi.bundle.https.enable");
+        if (enableHTTPS != null && enableHTTPS.toLowerCase().equals("true"))
+        {
+            initializeHTTPS();
+        }
+        
+        m_server.start();
+    }
+
+    //TODO: Just a basic implementation to give us a working HTTPS port. A better
+    //      long-term solution may be to separate out the SSL provider handling, 
+    //      keystore, passwords etc. into it's own pluggable service
+    protected void initializeHTTPS()
+        throws Exception
+    {
+        String sslProvider = m_bundleContext.getProperty("org.ungoverned.osgi.bundle.https.provider");
+        if (sslProvider == null)
+        {
+            sslProvider = "org.mortbay.http.SunJsseListener";
+        }
+
+        // Set default jetty properties for supplied values. For any not set, 
+        // Jetty will fallback to checking system properties.
+        String keystore = m_bundleContext.getProperty("org.ungoverned.osgi.bundle.https.keystore");
+        if (keystore != null)
+        {
+            System.setProperty(JsseListener.KEYSTORE_PROPERTY, keystore);
+        }
+
+        String passwd = m_bundleContext.getProperty("org.ungoverned.osgi.bundle.https.password");
+        if (passwd != null)
+        {
+            System.setProperty(JsseListener.PASSWORD_PROPERTY, passwd);
+        }
+        
+        String keyPasswd = m_bundleContext.getProperty("org.ungoverned.osgi.bundle.https.key.password");
+        if (keyPasswd != null)
+        {
+            System.setProperty(JsseListener.KEYPASSWORD_PROPERTY, keyPasswd);
+        }
+
+        //SunJsseListener s_listener = new SunJsseListener(new InetAddrPort(m_httpsPort));
+        Object args[] = { new InetAddrPort(m_httpsPort) };
+        Class argTypes[] = { args[0].getClass() };
+        Class clazz = Class.forName(sslProvider);
+        Constructor cstruct = clazz.getDeclaredConstructor(argTypes);
+        JsseListener s_listener = (JsseListener) cstruct.newInstance(args);
+
+        m_server.addListener(s_listener);        
+        s_listener.setMaxIdleTimeMs(60000);
+    }
+
+    
+    protected static void debug(String txt)
+    {
+        if (debug)
+        {
+            System.err.println(">>Oscar HTTP: " + txt);
+        }
+    }
+
+    // Inner class to provide basic service factory functionality
+
+    public class HttpServiceFactory implements ServiceFactory
+    {
+        public HttpServiceFactory()
+        {
+            // Initialize the statics for the service implementation.
+            HttpServiceImpl.initializeStatics();
+        }
+
+        public Object getService(Bundle bundle,
+                ServiceRegistration registration)
+        {
+            Object srv = new HttpServiceImpl(bundle, m_server); 
+            debug("** http service get:" + bundle + ", service: " + srv);
+            return srv;
+        }
+
+        public void ungetService(Bundle bundle,
+                ServiceRegistration registration, Object service)
+        {
+            debug("** http service unget:" + bundle + ", service: " 
+            + service);
+            ((HttpServiceImpl) service).unregisterAll();
+        }
+    }
+    
+}
\ No newline at end of file
diff --git a/org.apache.felix.http.jetty/src/main/java/org/apache/felix/http/jetty/DefaultContextImpl.java b/org.apache.felix.http.jetty/src/main/java/org/apache/felix/http/jetty/DefaultContextImpl.java
new file mode 100644
index 0000000..c8fbc22
--- /dev/null
+++ b/org.apache.felix.http.jetty/src/main/java/org/apache/felix/http/jetty/DefaultContextImpl.java
@@ -0,0 +1,75 @@
+/*
+ *   Copyright 2006 The Apache Software Foundation
+ *
+ *   Licensed 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.jetty;
+
+import java.net.URL;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.osgi.framework.Bundle;
+import org.osgi.service.http.HttpContext;
+
+/**
+ * Implementation of default HttpContext as per OSGi specification.
+ *
+ * Notes
+ *
+ *      - no current inclusion/support for permissions
+ *      - security allows all request. Spec leaves security handling to be
+ *        implementation specific, but does outline some suggested handling.
+ *        Deeper than my understanding of HTTP at this stage, so left for now.
+ */
+public class DefaultContextImpl implements HttpContext
+{
+    private Bundle m_bundle;
+
+    public DefaultContextImpl(Bundle bundle)
+    {
+        m_bundle = bundle;
+    }
+
+    public String getMimeType(String name)
+    {
+        return null;
+    }
+
+    public URL getResource(String name)
+    {
+        //TODO: need to grant "org.osgi.framework.AdminPermission" when
+        //      permissions are included.
+        Activator.debug("getResource for:" + name);
+
+        //TODO: temp measure for name. Bundle classloading doesn't seem to find
+        // resources which have a leading "/". This code should be removed
+        // if the bundle classloader is changed to allow a leading "/"
+        if (name.startsWith("/"))
+        {
+            name = name.substring(1);
+        }
+
+        return m_bundle.getResource(name);
+    }
+
+    public boolean handleSecurity(HttpServletRequest request,
+        HttpServletResponse response)
+    {
+        //TODO: need to look into what's appropriate for default security
+        //      handling. Default to all requests to be serviced for now.
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/org.apache.felix.http.jetty/src/main/java/org/apache/felix/http/jetty/HttpServiceImpl.java b/org.apache.felix.http.jetty/src/main/java/org/apache/felix/http/jetty/HttpServiceImpl.java
new file mode 100644
index 0000000..82e3eb4
--- /dev/null
+++ b/org.apache.felix.http.jetty/src/main/java/org/apache/felix/http/jetty/HttpServiceImpl.java
@@ -0,0 +1,379 @@
+/*
+ *   Copyright 2006 The Apache Software Foundation
+ *
+ *   Licensed 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.jetty;
+
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.util.*;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+
+import org.mortbay.http.HttpServer;
+import org.mortbay.jetty.servlet.*;
+import org.osgi.framework.Bundle;
+import org.osgi.service.http.HttpService;
+import org.osgi.service.http.NamespaceException;
+
+public class HttpServiceImpl implements HttpService
+{
+    /** global namesspace of all aliases that have been registered */
+    private static Map      m_aliasNamespace = null;
+    /** global pool of all OSGi HttpContext that have been created */
+    private static Map      m_contextMap = null;
+    /** global set of all servlet instances that have been registered */
+    private static Set      m_servletSet = null;
+
+    /** local list of aliases registered by the bundle holding this service */
+    private Set m_localAliasSet = null;
+
+    /** Bundle which "got" this service instance from the service factory */
+    private Bundle m_bundle = null;
+    /** Instance of Jetty server which provides underlying http server */
+    private HttpServer m_server = null;
+
+    public HttpServiceImpl(Bundle bundle, HttpServer server)
+    {
+        m_bundle = bundle;
+        m_server = server;
+        m_localAliasSet = new HashSet();
+
+        if (m_aliasNamespace == null)
+        {
+            m_aliasNamespace = new HashMap();
+        }
+
+        if (m_contextMap == null)
+        {
+            m_contextMap = new HashMap();
+        }
+
+        if (m_servletSet == null)
+        {
+            m_servletSet = new HashSet();
+        }
+    }
+
+    /**
+     * Initializes static variables.
+    **/
+    public static void initializeStatics()
+    {
+        if (m_aliasNamespace != null)
+        {
+            m_aliasNamespace.clear();
+        }
+        if (m_contextMap != null)
+        {
+            m_contextMap.clear();
+        }
+        if (m_servletSet != null)
+        {
+            m_servletSet.clear();
+        }
+    }
+
+    public org.osgi.service.http.HttpContext createDefaultHttpContext()
+    {
+        return new DefaultContextImpl(m_bundle);
+    }
+
+    public void registerServlet(String alias, Servlet servlet,
+        Dictionary params, org.osgi.service.http.HttpContext osgiHttpContext)
+        throws ServletException, NamespaceException
+    {
+        Activator.debug("http register servlet :" + m_bundle + ", alias: " + alias);
+
+        if (!aliasValid(alias))
+        {
+            throw new IllegalArgumentException("malformed alias");
+        }
+
+        if (m_servletSet.contains(servlet))
+        {
+            throw new ServletException("servlet already registered");
+        }
+
+        // add alias with null details, and record servlet instance details
+        addAlias(alias, null);
+
+        //make sure alias is unique, and create
+        ServletContextGroup grp = null;
+
+        if (osgiHttpContext == null)
+        {
+            osgiHttpContext = createDefaultHttpContext();
+        }
+
+        // servlets using same context must get same handler to ensure
+        // they share a common ServletContext
+        Activator.debug("looking for context: " + osgiHttpContext);
+        grp = (ServletContextGroup) m_contextMap.get(osgiHttpContext);
+        if (grp == null)
+        {
+            grp = new ServletContextGroup(
+                    servlet.getClass().getClassLoader(), osgiHttpContext);
+        }
+
+        grp.addServlet(servlet, alias, params);
+
+        // update alias namespace with reference to group object for later
+        // unregistering
+        updateAlias(alias, grp);
+
+        // maybe should remove alias/servlet entries if exceptions?
+    }
+
+    public void registerResources(String alias, String name,
+        org.osgi.service.http.HttpContext osgiHttpContext)
+        throws NamespaceException
+    {
+        Activator.debug("** http register resource :" + m_bundle + ", alias: " + alias);
+
+        if (!aliasValid(alias))
+        {
+            throw new IllegalArgumentException("malformed alias");
+        }
+
+        // add alias with null details
+        addAlias(alias, null);
+
+        //make sure alias is unique, and create
+        org.mortbay.http.HttpContext hdlrContext = null;
+
+        if (osgiHttpContext == null)
+        {
+            osgiHttpContext = createDefaultHttpContext();
+        }
+
+        hdlrContext = m_server.addContext(alias);
+
+        // update alias namespace with reference to context object for later
+        // unregistering
+        updateAlias(alias, hdlrContext);
+
+        // create resource handler, observing any access controls
+        AccessControlContext acc = null;
+        if (System.getSecurityManager() != null)
+        {
+            acc = AccessController.getContext();
+        }
+        OsgiResourceHandler hdlr = new OsgiResourceHandler(osgiHttpContext,
+                name, acc);
+
+        hdlrContext.addHandler(hdlr);
+        try
+        {
+            hdlrContext.start();
+        }
+        catch (Exception e)
+        {
+            System.err.println("Oscar exception adding resource: " + e);
+            e.printStackTrace(System.err);
+            // maybe we should remove alias here?
+        }
+    }
+
+    public void unregister(String alias)
+    {
+        doUnregister(alias, true);
+    }
+
+    protected void unregisterAll()
+    {
+        // note that this is a forced unregister, so we shouldn't call destroy
+        // on any servlets
+        // unregister each alias for the bundle - copy list since it will
+        // change
+        String[] all = (String[]) m_localAliasSet.toArray(new String[0]);
+        for (int ix = 0; ix < all.length; ix++)
+        {
+            doUnregister(all[ix], false);
+        }
+    }
+
+    protected void doUnregister(String alias, boolean forced)
+    {
+        Object obj = removeAlias(alias);
+
+        if (obj instanceof org.mortbay.http.HttpContext)
+        {
+            Activator.debug("** http unregister resource :" + m_bundle + ", alias: " + alias);
+
+            org.mortbay.http.HttpContext ctxt = (org.mortbay.http.HttpContext) obj;
+            try
+            {
+                ctxt.stop();
+                m_server.removeContext(ctxt);
+            }
+            catch(Exception e)
+            {
+                System.err.println("Oscar exception removing resource: " + e);
+                e.printStackTrace();
+            }
+        }
+        else if (obj instanceof ServletContextGroup)
+        {
+            Activator.debug("** http unregister servlet :" + m_bundle + ", alias: " + alias + ",forced:" + forced);
+
+            ServletContextGroup grp = (ServletContextGroup) obj;
+            grp.removeServlet(alias, forced);
+        }
+        else
+        {
+            // oops - this shouldn't happen !
+        }
+    }
+
+    protected void addAlias(String alias, Object obj)
+            throws NamespaceException
+    {
+        synchronized (m_aliasNamespace)
+        {
+            if (m_aliasNamespace.containsKey(alias))
+            {
+                throw new NamespaceException("alias already registered");
+            }
+
+            m_aliasNamespace.put(alias, obj);
+            m_localAliasSet.add(alias);
+        }
+    }
+
+    protected Object removeAlias(String alias)
+    {
+        synchronized (m_aliasNamespace)
+        {
+            // remove alias, don't worry if doesn't exist
+            Object obj = m_aliasNamespace.remove(alias);
+            m_localAliasSet.remove(alias);
+            return obj;
+        }
+    }
+
+    protected void updateAlias(String alias, Object obj)
+    {
+        synchronized (m_aliasNamespace)
+        {
+            // only update if already present
+            if (m_aliasNamespace.containsKey(alias))
+            {
+                m_aliasNamespace.put(alias, obj);
+            }
+        }
+    }
+
+    protected boolean aliasValid(String alias)
+    {
+       if (!alias.equals("/") &&
+            (!alias.startsWith("/") || alias.endsWith("/")))
+        {
+            return false;
+        }
+
+        return true;
+    }
+
+    private class ServletContextGroup
+    {
+        private OsgiServletHttpContext m_hdlrContext = null;
+        private OsgiServletHandler m_hdlr = null;
+        private org.osgi.service.http.HttpContext m_osgiHttpContext = null;
+        private int m_servletCount = 0;
+
+        private ServletContextGroup(ClassLoader loader,
+                org.osgi.service.http.HttpContext osgiHttpContext)
+        {
+            init(loader, osgiHttpContext);
+        }
+
+        private void init(ClassLoader loader,
+                org.osgi.service.http.HttpContext osgiHttpContext)
+        {
+            m_osgiHttpContext = osgiHttpContext;
+            m_hdlrContext = new OsgiServletHttpContext(m_osgiHttpContext);
+            m_hdlrContext.setContextPath("/");
+            //TODO: was in original code, but seems we shouldn't serve
+            //      resources in servlet context
+            //m_hdlrContext.setServingResources(true);
+            m_hdlrContext.setClassLoader(loader);
+            Activator.debug(" adding handler context : " + m_hdlrContext);
+            m_server.addContext(m_hdlrContext);
+
+            m_hdlr = new OsgiServletHandler(m_osgiHttpContext);
+            m_hdlrContext.addHandler(m_hdlr);
+
+            try
+            {
+                m_hdlrContext.start();
+            }
+            catch (Exception e)
+            {
+                // make sure we unwind the adding process
+                System.err.println("Oscar exception adding servlet: " + e);
+                e.printStackTrace(System.err);
+            }
+
+            m_contextMap.put(m_osgiHttpContext, this);
+        }
+
+        private void destroy()
+        {
+            Activator.debug(" removing handler context : " + m_hdlrContext);
+            m_server.removeContext(m_hdlrContext);
+            m_contextMap.remove(m_osgiHttpContext);
+        }
+
+        private void addServlet(Servlet servlet, String alias,
+                Dictionary params)
+        {
+            String wAlias = aliasWildcard(alias);
+            ServletHolder holder = new OsgiServletHolder(m_hdlr, servlet, wAlias, params);
+            m_hdlr.addOsgiServletHolder(wAlias, holder);
+            Activator.debug(" adding servlet instance: " + servlet);
+            m_servletSet.add(servlet);
+            m_servletCount++;
+        }
+
+        private void removeServlet(String alias, boolean destroy)
+        {
+            String wAlias = aliasWildcard(alias);
+            OsgiServletHolder holder = m_hdlr.removeOsgiServletHolder(wAlias);
+            Servlet servlet = holder.getOsgiServlet();
+            Activator.debug(" removing servlet instance: " + servlet);
+            m_servletSet.remove(servlet);
+
+            if (destroy)
+            {
+                servlet.destroy();
+            }
+
+            if (--m_servletCount == 0)
+            {
+                destroy();
+            }
+        }
+        
+        private String aliasWildcard(String alias)
+        {
+            // add wilcard filter at the end of the alias to allow servlet to 
+            // get requests which include sub-paths
+            return alias + "/*";            
+        }
+        
+    }
+}
\ No newline at end of file
diff --git a/org.apache.felix.http.jetty/src/main/java/org/apache/felix/http/jetty/OsgiResourceHandler.java b/org.apache.felix.http.jetty/src/main/java/org/apache/felix/http/jetty/OsgiResourceHandler.java
new file mode 100644
index 0000000..c65c39c
--- /dev/null
+++ b/org.apache.felix.http.jetty/src/main/java/org/apache/felix/http/jetty/OsgiResourceHandler.java
@@ -0,0 +1,197 @@
+/*
+ *   Copyright 2006 The Apache Software Foundation
+ *
+ *   Licensed 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.jetty;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+import java.security.*;
+
+import org.mortbay.http.HttpException;
+import org.mortbay.http.HttpRequest;
+import org.mortbay.http.HttpResponse;
+import org.mortbay.http.handler.AbstractHttpHandler;
+import org.mortbay.jetty.servlet.*;
+
+/**
+ *
+ */
+public class OsgiResourceHandler extends AbstractHttpHandler
+{
+    protected org.osgi.service.http.HttpContext     m_osgiHttpContext;
+    protected String                                m_name;
+    protected OsgiServletHandler                    m_dummyHandler;
+    protected AccessControlContext                  m_acc;
+
+    
+    public OsgiResourceHandler(
+            org.osgi.service.http.HttpContext osgiHttpContext, String name,
+            AccessControlContext acc)
+    {
+        m_osgiHttpContext = osgiHttpContext;
+        m_name = name;
+        // needed for OSGi security handling
+        m_dummyHandler = new OsgiServletHandler(osgiHttpContext);
+        m_acc = acc;
+    }
+
+    
+    public void initialize(org.mortbay.http.HttpContext context)
+    {
+        super.initialize(context);
+        m_dummyHandler.initialize(context);
+    }
+
+    
+    public void handle(String pathInContext,
+                       String pathParams,
+                       HttpRequest request,
+                       HttpResponse response)
+        throws HttpException, IOException
+    {
+        Activator.debug("handle for name:" + m_name
+                + "(path=" + pathInContext + ")");
+
+        ServletHttpRequest servletRequest = new DummyServletHttpRequest(
+                m_dummyHandler, pathInContext, request);
+        ServletHttpResponse servletResponse = new DummyServletHttpResponse(
+                servletRequest, response);
+
+        if (!m_osgiHttpContext.handleSecurity(servletRequest, servletResponse))
+        {
+            // spec doesn't state specific processing here apart from 
+            // "send the response back to the client". We take this to mean
+            // any response generated in the context, and so all we do here
+            // is set handled to "true" to ensure any output is sent back
+            request.setHandled(true);
+            return;
+        }
+
+        // Create resource based name and see if we can resolve it
+        String resName = m_name + pathInContext;
+        Activator.debug("** looking for: " + resName); 
+        URL url = m_osgiHttpContext.getResource(resName);
+
+        if (url == null)
+        {
+            return;
+        }
+        
+        Activator.debug("serving up:" + resName);
+
+        // It doesn't state so in the OSGi spec, but can't see how anything
+        // other than GET and variants would be supported
+        String method=request.getMethod();
+        if (method.equals(HttpRequest.__GET) ||
+            method.equals(HttpRequest.__POST) ||
+            method.equals(HttpRequest.__HEAD))
+        {
+            handleGet(request, response, url, resName);
+        }
+        else
+        {
+            try
+            {
+                response.sendError(HttpResponse.__501_Not_Implemented);
+            }
+            catch(Exception e) {/*TODO: include error logging*/}
+        }
+    }
+
+    
+    public void handleGet(HttpRequest request, final HttpResponse response, 
+            final URL url, String resName)
+        throws IOException
+    {
+        String encoding = m_osgiHttpContext.getMimeType(resName);
+
+        if (encoding == null)
+        {
+            encoding = getHttpContext().getMimeByExtension(resName);
+        }
+
+        if (encoding == null)
+        {
+            encoding = getHttpContext().getMimeByExtension(".default");
+        }
+
+        //TODO: not sure why this is needed, but sometimes get "IllegalState"
+        // errors if not included
+        response.setAcceptTrailer(true);
+        response.setContentType(encoding);
+
+        //TODO: check other http fields e.g. ranges, timestamps etc.
+
+        // make sure we access the resource inside the bundle's access control
+        // context if supplied
+        if (System.getSecurityManager() != null)
+        {
+            try
+            {
+                AccessController.doPrivileged(new PrivilegedExceptionAction()
+                {
+                    public Object run()
+                            throws Exception
+                    {
+                        copyResourceBytes(url, response);
+                        return null;
+                    }
+                }, m_acc);
+            } 
+            catch (PrivilegedActionException ex) 
+            {
+                IOException ioe = (IOException) ex.getException();
+                throw ioe;
+            }
+        }
+        else
+        {
+            copyResourceBytes(url, response);
+        }
+
+        request.setHandled(true);
+        //TODO: set other http fields e.g. __LastModified, __ContentLength
+    }
+    
+    
+    private void copyResourceBytes(URL url, HttpResponse response)
+            throws
+                    IOException
+    {
+        OutputStream os = response.getOutputStream();
+        InputStream is = url.openStream();
+        int len = 0;
+        byte[] buf = new byte[1024];
+        int n = 0;
+
+        while ((n = is.read(buf, 0, buf.length)) >= 0)
+        {
+            os.write(buf, 0, n);
+            len += n;
+        }
+        
+        try 
+        {
+            response.setContentLength(len);
+        } 
+        catch (IllegalStateException ex) 
+        {
+            System.err.println("OsgiResourceHandler: " + ex);
+        }
+    }
+}
diff --git a/org.apache.felix.http.jetty/src/main/java/org/mortbay/jetty/servlet/DummyServletHttpRequest.java b/org.apache.felix.http.jetty/src/main/java/org/mortbay/jetty/servlet/DummyServletHttpRequest.java
new file mode 100644
index 0000000..962a267
--- /dev/null
+++ b/org.apache.felix.http.jetty/src/main/java/org/mortbay/jetty/servlet/DummyServletHttpRequest.java
@@ -0,0 +1,32 @@
+/*
+ *   Copyright 2006 The Apache Software Foundation
+ *
+ *   Licensed 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.mortbay.jetty.servlet;
+
+import org.mortbay.http.HttpRequest;
+
+public class DummyServletHttpRequest
+        extends
+                ServletHttpRequest
+{
+    public DummyServletHttpRequest(ServletHandler servletHandler,
+                       String pathInContext,
+                       HttpRequest request)
+    {
+        super(servletHandler, pathInContext, request);
+    }
+
+}
\ No newline at end of file
diff --git a/org.apache.felix.http.jetty/src/main/java/org/mortbay/jetty/servlet/DummyServletHttpResponse.java b/org.apache.felix.http.jetty/src/main/java/org/mortbay/jetty/servlet/DummyServletHttpResponse.java
new file mode 100644
index 0000000..e2c9387
--- /dev/null
+++ b/org.apache.felix.http.jetty/src/main/java/org/mortbay/jetty/servlet/DummyServletHttpResponse.java
@@ -0,0 +1,32 @@
+/*
+ *   Copyright 2006 The Apache Software Foundation
+ *
+ *   Licensed 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.mortbay.jetty.servlet;
+
+import org.mortbay.http.HttpResponse;
+
+public class DummyServletHttpResponse
+        extends
+                ServletHttpResponse
+{
+    public DummyServletHttpResponse(ServletHttpRequest request,
+            HttpResponse response)
+    {
+        super(request, response);
+    }
+
+}
+
diff --git a/org.apache.felix.http.jetty/src/main/java/org/mortbay/jetty/servlet/OsgiServletHandler.java b/org.apache.felix.http.jetty/src/main/java/org/mortbay/jetty/servlet/OsgiServletHandler.java
new file mode 100644
index 0000000..a3528b4
--- /dev/null
+++ b/org.apache.felix.http.jetty/src/main/java/org/mortbay/jetty/servlet/OsgiServletHandler.java
@@ -0,0 +1,98 @@
+/*
+ *   Copyright 2006 The Apache Software Foundation
+ *
+ *   Licensed 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.mortbay.jetty.servlet;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import javax.servlet.ServletException;
+import javax.servlet.UnavailableException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.mortbay.http.PathMap;
+import org.mortbay.util.Code;
+
+
+public class OsgiServletHandler
+        extends ServletHandler
+{
+    protected org.osgi.service.http.HttpContext     m_osgiHttpContext;
+
+
+    public OsgiServletHandler(
+            org.osgi.service.http.HttpContext osgiHttpContext)
+    {
+        m_osgiHttpContext = osgiHttpContext;
+    }
+
+
+    // allow external adding of osgi servlet holder
+    public void addOsgiServletHolder(String pathSpec, ServletHolder holder)
+    {
+        super.addServletHolder(pathSpec, holder);
+    }
+
+
+    public OsgiServletHolder removeOsgiServletHolder(String pathSpec)
+    {
+        OsgiServletHolder holder = (OsgiServletHolder)
+                super.getServletHolder(pathSpec);
+        PathMap map = super.getServletMap();
+        map.remove(pathSpec);
+
+        // Remove holder from handler name map to allow re-registration.
+        super._nameMap.remove(holder.getName());
+
+        return holder;
+    }
+
+
+    // override standard handler behaviour to return resource from OSGi
+    // HttpContext
+    public URL getResource(String uriInContext)
+                         throws MalformedURLException
+    {
+        Code.debug("OSGI ServletHandler getResource:" + uriInContext);
+        return m_osgiHttpContext.getResource(uriInContext);
+    }
+
+    // override standard behaviour to check context first
+    protected void dispatch(String pathInContext,
+                  HttpServletRequest request,
+                  HttpServletResponse response,
+                  ServletHolder servletHolder)
+        throws ServletException,
+               UnavailableException,
+               IOException
+    {
+        Code.debug("dispatch path = " + pathInContext);
+        if (m_osgiHttpContext.handleSecurity(request, response))
+        {
+            // service request
+            servletHolder.handle(request,response);
+        }
+        else
+        {
+            //TODO: any other error/auth handling we should do in here?
+            response.flushBuffer();
+        }
+    }
+}
+
+
diff --git a/org.apache.felix.http.jetty/src/main/java/org/mortbay/jetty/servlet/OsgiServletHolder.java b/org.apache.felix.http.jetty/src/main/java/org/mortbay/jetty/servlet/OsgiServletHolder.java
new file mode 100644
index 0000000..e4dba28
--- /dev/null
+++ b/org.apache.felix.http.jetty/src/main/java/org/mortbay/jetty/servlet/OsgiServletHolder.java
@@ -0,0 +1,91 @@
+/*
+ *   Copyright 2006 The Apache Software Foundation
+ *
+ *   Licensed 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.mortbay.jetty.servlet;
+
+
+import java.util.Dictionary;
+import java.util.Enumeration;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
+import javax.servlet.UnavailableException;
+
+public class OsgiServletHolder
+        extends
+                ServletHolder
+{
+    private Servlet m_servlet;
+    private ServletConfig m_config;
+
+
+    public OsgiServletHolder(ServletHandler handler, Servlet servlet,
+            String name, Dictionary params)
+    {
+        super(handler,name,servlet.getClass().getName());
+        m_servlet = servlet;
+
+        // Seemed safer to copy params into parent holder, rather than override
+        // the getInitxxx methods.
+        if (params != null)
+        {
+            Enumeration e = params.keys();
+            while (e.hasMoreElements())
+            {
+                Object key = e.nextElement();
+                super.put(key, params.get(key));
+            }
+        }
+    }
+
+    public synchronized Servlet getServlet()
+        throws UnavailableException
+    {
+        return m_servlet;        
+    }
+
+    public Servlet getOsgiServlet()
+    {
+        return m_servlet;
+    }
+
+
+    // override "Holder" method to prevent instantiation
+    public synchronized Object newInstance()
+        throws InstantiationException,
+               IllegalAccessException
+    {
+        return getOsgiServlet();
+    }
+
+    // override "Holder" method to prevent attempt to load
+    // the servlet class.
+    public void start()
+        throws Exception
+    {
+        _class=m_servlet.getClass();
+        
+        m_config=new Config();
+        m_servlet.init(m_config);        
+    }
+
+    // override "Holder" method to prevent destroy, which is only called
+    // when a bundle manually unregisters
+    public void stop()
+    {
+    }
+}
+
diff --git a/org.apache.felix.http.jetty/src/main/java/org/mortbay/jetty/servlet/OsgiServletHttpContext.java b/org.apache.felix.http.jetty/src/main/java/org/mortbay/jetty/servlet/OsgiServletHttpContext.java
new file mode 100644
index 0000000..3a95d13
--- /dev/null
+++ b/org.apache.felix.http.jetty/src/main/java/org/mortbay/jetty/servlet/OsgiServletHttpContext.java
@@ -0,0 +1,49 @@
+/*
+ *   Copyright 2006 The Apache Software Foundation
+ *
+ *   Licensed 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.mortbay.jetty.servlet;
+
+
+import org.mortbay.util.Code;
+
+public class OsgiServletHttpContext
+        extends
+                ServletHttpContext
+{
+    protected org.osgi.service.http.HttpContext     m_osgiHttpContext;
+    
+    public OsgiServletHttpContext(
+            org.osgi.service.http.HttpContext osgiHttpContext)
+    {
+        m_osgiHttpContext = osgiHttpContext;
+    }
+    
+    // intercept to ensure OSGi context is used first for servlet calls to 
+    // getMimeType()
+    public String getMimeByExtension(String filename)
+    { 
+        Code.debug("OSGi servlet context: get mime type");
+        String encoding = m_osgiHttpContext.getMimeType(filename);
+
+        if (encoding == null)
+        {
+            encoding = super.getMimeByExtension(filename);
+        }
+        
+        return encoding;
+    }
+    
+}
diff --git a/org.apache.felix.http.jetty/src/main/java/org/osgi/service/http/HttpContext.java b/org.apache.felix.http.jetty/src/main/java/org/osgi/service/http/HttpContext.java
new file mode 100644
index 0000000..d5a0eb5
--- /dev/null
+++ b/org.apache.felix.http.jetty/src/main/java/org/osgi/service/http/HttpContext.java
@@ -0,0 +1,167 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.http/src/org/osgi/service/http/HttpContext.java,v 1.9 2006/03/14 01:20:39 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2000, 2005). All Rights Reserved.
+ *
+ * Licensed 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.osgi.service.http;
+
+import java.io.IOException;
+import java.net.URL;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * This interface defines methods that the Http Service may call to get
+ * information about a registration.
+ * 
+ * <p>
+ * Servlets and resources may be registered with an <code>HttpContext</code>
+ * object; if no <code>HttpContext</code> object is specified, a default
+ * <code>HttpContext</code> object is used. Servlets that are registered using the
+ * same <code>HttpContext</code> object will share the same
+ * <code>ServletContext</code> object.
+ * 
+ * <p>
+ * This interface is implemented by users of the <code>HttpService</code>.
+ * 
+ * @version $Revision: 1.9 $
+ */
+public interface HttpContext {
+	/**
+	 * <code>HttpServletRequest</code> attribute specifying the name of the
+	 * authenticated user. The value of the attribute can be retrieved by
+	 * <code>HttpServletRequest.getRemoteUser</code>. This attribute name is
+	 * <code>org.osgi.service.http.authentication.remote.user</code>.
+	 * 
+	 * @since 1.1
+	 */
+	public static final String	REMOTE_USER			= "org.osgi.service.http.authentication.remote.user";
+	/**
+	 * <code>HttpServletRequest</code> attribute specifying the scheme used in
+	 * authentication. The value of the attribute can be retrieved by
+	 * <code>HttpServletRequest.getAuthType</code>. This attribute name is
+	 * <code>org.osgi.service.http.authentication.type</code>.
+	 * 
+	 * @since 1.1
+	 */
+	public static final String	AUTHENTICATION_TYPE	= "org.osgi.service.http.authentication.type";
+	/**
+	 * <code>HttpServletRequest</code> attribute specifying the
+	 * <code>Authorization</code> object obtained from the
+	 * <code>org.osgi.service.useradmin.UserAdmin</code> service. The value of the
+	 * attribute can be retrieved by
+	 * <code>HttpServletRequest.getAttribute(HttpContext.AUTHORIZATION)</code>.
+	 * This attribute name is <code>org.osgi.service.useradmin.authorization</code>.
+	 * 
+	 * @since 1.1
+	 */
+	public static final String	AUTHORIZATION		= "org.osgi.service.useradmin.authorization";
+
+	/**
+	 * Handles security for the specified request.
+	 * 
+	 * <p>
+	 * The Http Service calls this method prior to servicing the specified
+	 * request. This method controls whether the request is processed in the
+	 * normal manner or an error is returned.
+	 * 
+	 * <p>
+	 * If the request requires authentication and the Authorization header in
+	 * the request is missing or not acceptable, then this method should set the
+	 * WWW-Authenticate header in the response object, set the status in the
+	 * response object to Unauthorized(401) and return <code>false</code>. See
+	 * also RFC 2617: <i>HTTP Authentication: Basic and Digest Access
+	 * Authentication </i> (available at http://www.ietf.org/rfc/rfc2617.txt).
+	 * 
+	 * <p>
+	 * If the request requires a secure connection and the <code>getScheme</code>
+	 * method in the request does not return 'https' or some other acceptable
+	 * secure protocol, then this method should set the status in the response
+	 * object to Forbidden(403) and return <code>false</code>.
+	 * 
+	 * <p>
+	 * When this method returns <code>false</code>, the Http Service will send
+	 * the response back to the client, thereby completing the request. When
+	 * this method returns <code>true</code>, the Http Service will proceed with
+	 * servicing the request.
+	 * 
+	 * <p>
+	 * If the specified request has been authenticated, this method must set the
+	 * {@link #AUTHENTICATION_TYPE}request attribute to the type of
+	 * authentication used, and the {@link #REMOTE_USER}request attribute to
+	 * the remote user (request attributes are set using the
+	 * <code>setAttribute</code> method on the request). If this method does not
+	 * perform any authentication, it must not set these attributes.
+	 * 
+	 * <p>
+	 * If the authenticated user is also authorized to access certain resources,
+	 * this method must set the {@link #AUTHORIZATION}request attribute to the
+	 * <code>Authorization</code> object obtained from the
+	 * <code>org.osgi.service.useradmin.UserAdmin</code> service.
+	 * 
+	 * <p>
+	 * The servlet responsible for servicing the specified request determines
+	 * the authentication type and remote user by calling the
+	 * <code>getAuthType</code> and <code>getRemoteUser</code> methods,
+	 * respectively, on the request.
+	 * 
+	 * @param request the HTTP request
+	 * @param response the HTTP response
+	 * @return <code>true</code> if the request should be serviced, <code>false</code>
+	 *         if the request should not be serviced and Http Service will send
+	 *         the response back to the client.
+	 * @throws java.io.IOException may be thrown by this method. If this
+	 *            occurs, the Http Service will terminate the request and close
+	 *            the socket.
+	 */
+	public boolean handleSecurity(HttpServletRequest request,
+			HttpServletResponse response) throws IOException;
+
+	/**
+	 * Maps a resource name to a URL.
+	 * 
+	 * <p>
+	 * Called by the Http Service to map a resource name to a URL. For servlet
+	 * registrations, Http Service will call this method to support the
+	 * <code>ServletContext</code> methods <code>getResource</code> and
+	 * <code>getResourceAsStream</code>. For resource registrations, Http Service
+	 * will call this method to locate the named resource. The context can
+	 * control from where resources come. For example, the resource can be
+	 * mapped to a file in the bundle's persistent storage area via
+	 * <code>bundleContext.getDataFile(name).toURL()</code> or to a resource in
+	 * the context's bundle via <code>getClass().getResource(name)</code>
+	 * 
+	 * @param name the name of the requested resource
+	 * @return URL that Http Service can use to read the resource or
+	 *         <code>null</code> if the resource does not exist.
+	 */
+	public URL getResource(String name);
+
+	/**
+	 * Maps a name to a MIME type.
+	 * 
+	 * Called by the Http Service to determine the MIME type for the name. For
+	 * servlet registrations, the Http Service will call this method to support
+	 * the <code>ServletContext</code> method <code>getMimeType</code>. For
+	 * resource registrations, the Http Service will call this method to
+	 * determine the MIME type for the Content-Type header in the response.
+	 * 
+	 * @param name determine the MIME type for this name.
+	 * @return MIME type (e.g. text/html) of the name or <code>null</code> to
+	 *         indicate that the Http Service should determine the MIME type
+	 *         itself.
+	 */
+	public String getMimeType(String name);
+}
diff --git a/org.apache.felix.http.jetty/src/main/java/org/osgi/service/http/HttpService.java b/org.apache.felix.http.jetty/src/main/java/org/osgi/service/http/HttpService.java
new file mode 100644
index 0000000..95acce9
--- /dev/null
+++ b/org.apache.felix.http.jetty/src/main/java/org/osgi/service/http/HttpService.java
@@ -0,0 +1,177 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.http/src/org/osgi/service/http/HttpService.java,v 1.10 2006/03/14 01:20:39 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2000, 2005). All Rights Reserved.
+ *
+ * Licensed 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.osgi.service.http;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+import java.util.Dictionary;
+
+/**
+ * The Http Service allows other bundles in the OSGi environment to dynamically
+ * register resources and servlets into the URI namespace of Http Service. A
+ * bundle may later unregister its resources or servlets.
+ * 
+ * @version $Revision: 1.10 $
+ * @see HttpContext
+ */
+public interface HttpService {
+	/**
+	 * Registers a servlet into the URI namespace.
+	 * 
+	 * <p>
+	 * The alias is the name in the URI namespace of the Http Service at which
+	 * the registration will be mapped.
+	 * 
+	 * <p>
+	 * An alias must begin with slash ('/') and must not end with slash ('/'),
+	 * with the exception that an alias of the form &quot;/&quot; is used to
+	 * denote the root alias. See the specification text for details on how HTTP
+	 * requests are mapped to servlet and resource registrations.
+	 * 
+	 * <p>
+	 * The Http Service will call the servlet's <code>init</code> method before
+	 * returning.
+	 * 
+	 * <pre>
+	 * httpService.registerServlet(&quot;/myservlet&quot;, servlet, initparams, context);
+	 * </pre>
+	 * 
+	 * <p>
+	 * Servlets registered with the same <code>HttpContext</code> object will
+	 * share the same <code>ServletContext</code>. The Http Service will call the
+	 * <code>context</code> argument to support the <code>ServletContext</code>
+	 * methods <code>getResource</code>,<code>getResourceAsStream</code> and
+	 * <code>getMimeType</code>, and to handle security for requests. If the
+	 * <code>context</code> argument is <code>null</code>, a default
+	 * <code>HttpContext</code> object is used (see
+	 * {@link #createDefaultHttpContext}).
+	 * 
+	 * @param alias name in the URI namespace at which the servlet is registered
+	 * @param servlet the servlet object to register
+	 * @param initparams initialization arguments for the servlet or
+	 *        <code>null</code> if there are none. This argument is used by the
+	 *        servlet's <code>ServletConfig</code> object.
+	 * @param context the <code>HttpContext</code> object for the registered
+	 *        servlet, or <code>null</code> if a default <code>HttpContext</code> is
+	 *        to be created and used.
+	 * @throws NamespaceException if the registration fails because the alias
+	 *            is already in use.
+	 * @throws javax.servlet.ServletException if the servlet's <code>init</code>
+	 *            method throws an exception, or the given servlet object has
+	 *            already been registered at a different alias.
+	 * @throws java.lang.IllegalArgumentException if any of the arguments are
+	 *            invalid
+	 */
+	public void registerServlet(String alias, Servlet servlet,
+			Dictionary initparams, HttpContext context)
+			throws ServletException, NamespaceException;
+
+	/**
+	 * Registers resources into the URI namespace.
+	 * 
+	 * <p>
+	 * The alias is the name in the URI namespace of the Http Service at which
+	 * the registration will be mapped. An alias must begin with slash ('/') and
+	 * must not end with slash ('/'), with the exception that an alias of the
+	 * form &quot;/&quot; is used to denote the root alias. The name parameter
+	 * must also not end with slash ('/'). See the specification text for
+	 * details on how HTTP requests are mapped to servlet and resource
+	 * registrations.
+	 * <p>
+	 * For example, suppose the resource name /tmp is registered to the alias
+	 * /files. A request for /files/foo.txt will map to the resource name
+	 * /tmp/foo.txt.
+	 * 
+	 * <pre>
+	 * httpservice.registerResources(&quot;/files&quot;, &quot;/tmp&quot;, context);
+	 * </pre>
+	 * 
+	 * The Http Service will call the <code>HttpContext</code> argument to map
+	 * resource names to URLs and MIME types and to handle security for
+	 * requests. If the <code>HttpContext</code> argument is <code>null</code>, a
+	 * default <code>HttpContext</code> is used (see
+	 * {@link #createDefaultHttpContext}).
+	 * 
+	 * @param alias name in the URI namespace at which the resources are
+	 *        registered
+	 * @param name the base name of the resources that will be registered
+	 * @param context the <code>HttpContext</code> object for the registered
+	 *        resources, or <code>null</code> if a default <code>HttpContext</code>
+	 *        is to be created and used.
+	 * @throws NamespaceException if the registration fails because the alias
+	 *            is already in use.
+	 * @throws java.lang.IllegalArgumentException if any of the parameters
+	 *            are invalid
+	 */
+	public void registerResources(String alias, String name,
+			HttpContext context) throws NamespaceException;
+
+	/**
+	 * Unregisters a previous registration done by <code>registerServlet</code> or
+	 * <code>registerResources</code> methods.
+	 * 
+	 * <p>
+	 * After this call, the registered alias in the URI name-space will no
+	 * longer be available. If the registration was for a servlet, the Http
+	 * Service must call the <code>destroy</code> method of the servlet before
+	 * returning.
+	 * <p>
+	 * If the bundle which performed the registration is stopped or otherwise
+	 * "unget"s the Http Service without calling {@link #unregister}then Http
+	 * Service must automatically unregister the registration. However, if the
+	 * registration was for a servlet, the <code>destroy</code> method of the
+	 * servlet will not be called in this case since the bundle may be stopped.
+	 * {@link #unregister}must be explicitly called to cause the
+	 * <code>destroy</code> method of the servlet to be called. This can be done
+	 * in the <code>BundleActivator.stop</code> method of the
+	 * bundle registering the servlet.
+	 * 
+	 * @param alias name in the URI name-space of the registration to unregister
+	 * @throws java.lang.IllegalArgumentException if there is no registration
+	 *            for the alias or the calling bundle was not the bundle which
+	 *            registered the alias.
+	 */
+	public void unregister(String alias);
+
+	/**
+	 * Creates a default <code>HttpContext</code> for registering servlets or
+	 * resources with the HttpService, a new <code>HttpContext</code> object is
+	 * created each time this method is called.
+	 * 
+	 * <p>
+	 * The behavior of the methods on the default <code>HttpContext</code> is
+	 * defined as follows:
+	 * <ul>
+	 * <li><code>getMimeType</code>- Does not define any customized MIME types
+	 * for the Content-Type header in the response, and always returns
+	 * <code>null</code>.
+	 * <li><code>handleSecurity</code>- Performs implementation-defined
+	 * authentication on the request.
+	 * <li><code>getResource</code>- Assumes the named resource is in the
+	 * context bundle; this method calls the context bundle's
+	 * <code>Bundle.getResource</code> method, and returns the appropriate URL to
+	 * access the resource. On a Java runtime environment that supports
+	 * permissions, the Http Service needs to be granted 
+	 * <code>org.osgi.framework.AdminPermission[*,RESOURCE]</code>.
+	 * </ul>
+	 * 
+	 * @return a default <code>HttpContext</code> object.
+	 * @since 1.1
+	 */
+	public HttpContext createDefaultHttpContext();
+}
diff --git a/org.apache.felix.http.jetty/src/main/java/org/osgi/service/http/NamespaceException.java b/org.apache.felix.http.jetty/src/main/java/org/osgi/service/http/NamespaceException.java
new file mode 100644
index 0000000..c8e7889
--- /dev/null
+++ b/org.apache.felix.http.jetty/src/main/java/org/osgi/service/http/NamespaceException.java
@@ -0,0 +1,95 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.http/src/org/osgi/service/http/NamespaceException.java,v 1.9 2006/03/14 01:20:39 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2000, 2005). All Rights Reserved.
+ *
+ * Licensed 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.osgi.service.http;
+
+/**
+ * A NamespaceException is thrown to indicate an error with the caller's request
+ * to register a servlet or resources into the URI namespace of the Http
+ * Service. This exception indicates that the requested alias already is in use.
+ * 
+ * @version $Revision: 1.9 $
+ */
+public class NamespaceException extends Exception {
+    static final long serialVersionUID = 7235606031147877747L;
+	/**
+	 * Nested exception.
+	 */
+	private Throwable	cause;
+
+	/**
+	 * Construct a <code>NamespaceException</code> object with a detail message.
+	 * 
+	 * @param message the detail message
+	 */
+	public NamespaceException(String message) {
+		super(message);
+		cause = null;
+	}
+
+	/**
+	 * Construct a <code>NamespaceException</code> object with a detail message
+	 * and a nested exception.
+	 * 
+	 * @param message The detail message.
+	 * @param cause The nested exception.
+	 */
+	public NamespaceException(String message, Throwable cause) {
+		super(message);
+		this.cause = cause;
+	}
+
+	/**
+	 * Returns the nested exception.
+	 *
+     * <p>This method predates the general purpose exception chaining mechanism.
+     * The {@link #getCause()} method is now the preferred means of
+     * obtaining this information.
+	 * 
+	 * @return the nested exception or <code>null</code> if there is no nested
+	 *         exception.
+	 */
+	public Throwable getException() {
+		return cause;
+	}
+
+	/**
+	 * Returns the cause of this exception or <code>null</code> if no
+	 * cause was specified when this exception was created.
+	 *
+	 * @return  The cause of this exception or <code>null</code> if no
+	 * cause was specified.
+	 * @since 1.2 
+	 */
+	public Throwable getCause() {
+	    return cause;
+	}
+
+	/**
+	 * The cause of this exception can only be set when constructed.
+	 *
+	 * @param cause Cause of the exception.
+	 * @return This object.
+	 * @throws java.lang.IllegalStateException
+	 * This method will always throw an <code>IllegalStateException</code>
+	 * since the cause of this exception can only be set when constructed.
+	 * @since 1.2 
+	 */
+	public Throwable initCause(Throwable cause) {
+		throw new IllegalStateException();
+	}
+}