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);
+ }
+ }
+}