FELIX-1456: Added improved httpservice implementation

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@814549 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/http/jetty/pom.xml b/http/jetty/pom.xml
new file mode 100644
index 0000000..4458d19
--- /dev/null
+++ b/http/jetty/pom.xml
@@ -0,0 +1,107 @@
+<!--
+    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</artifactId>
+        <version>2.0.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <name>Apache Felix Http Jetty</name>
+    <artifactId>org.apache.felix.http.jetty</artifactId>
+    <packaging>bundle</packaging>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <configuration>
+                    <instructions>
+                        <Bundle-Activator>
+                            org.apache.felix.http.jetty.internal.JettyActivator
+                        </Bundle-Activator>
+                        <Export-Package>
+                            org.apache.felix.http.api;version=${pom.version},
+                            org.osgi.service.http,
+                            javax.servlet.*;version=2.5
+                        </Export-Package>
+                        <Private-Package>
+                            org.apache.felix.http.base.*,
+                            org.apache.felix.http.jetty.*,
+                            org.mortbay.*;-split-package:=merge-first                            
+                        </Private-Package>
+                        <Import-Package>
+                            javax.net.ssl; javax.security.cert;
+                            javax.xml.parsers; org.xml.sax;
+                            org.xml.sax.helpers;
+                            org.slf4j;resolution:=optional,
+                            *;resolution:=optional
+                        </Import-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mortbay.jetty</groupId>
+            <artifactId>servlet-api-2.5</artifactId>
+            <version>6.1.14</version>
+        </dependency>
+        <dependency>
+            <groupId>org.mortbay.jetty</groupId>
+            <artifactId>jetty</artifactId>
+            <version>6.1.14</version>
+        </dependency>
+        <dependency>
+            <groupId>org.mortbay.jetty</groupId>
+            <artifactId>jetty-util</artifactId>
+            <version>6.1.14</version>
+        </dependency>
+        <dependency>
+            <groupId>org.mortbay.jetty</groupId>
+            <artifactId>jetty-sslengine</artifactId>
+            <version>6.1.14</version>
+        </dependency>
+        <dependency>
+            <groupId>${pom.groupId}</groupId>
+            <artifactId>org.apache.felix.http.api</artifactId>
+            <version>${pom.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>${pom.groupId}</groupId>
+            <artifactId>org.apache.felix.http.base</artifactId>
+            <version>${pom.version}</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyActivator.java b/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyActivator.java
new file mode 100644
index 0000000..9549274
--- /dev/null
+++ b/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyActivator.java
@@ -0,0 +1,38 @@
+/*
+ * 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.jetty.internal;
+
+import org.apache.felix.http.base.internal.AbstractActivator;
+
+public final class JettyActivator
+    extends AbstractActivator 
+{
+    private JettyService jetty;
+
+    protected void doStart()
+        throws Exception
+    {
+        this.jetty = new JettyService(getBundleContext(), getDispatcherServlet());
+        this.jetty.start();
+    }
+
+    protected void doStop()
+        throws Exception
+    {
+        this.jetty.stop();
+    }
+}
diff --git a/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java b/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java
new file mode 100644
index 0000000..3251331
--- /dev/null
+++ b/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java
@@ -0,0 +1,189 @@
+/*
+ * 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.jetty.internal;
+
+import org.osgi.framework.BundleContext;
+import java.util.Dictionary;
+import java.util.Properties;
+
+public final class JettyConfig
+{
+    /** Standard OSGi port property for HTTP service */
+    private static final String HTTP_PORT = "org.osgi.service.http.port";
+
+    /** Standard OSGi port property for HTTPS service */
+    private static final String HTTPS_PORT = "org.osgi.service.http.port.secure";
+
+    /** Felix specific property to enable debug messages */
+    private static final String FELIX_HTTP_DEBUG = "org.apache.felix.http.debug";
+    private static final String HTTP_DEBUG = "org.apache.felix.http.jetty.debug";
+
+    /** Felix specific property to override the keystore file location. */
+    private static final String FELIX_KEYSTORE = "org.apache.felix.https.keystore";
+    private static final String OSCAR_KEYSTORE = "org.ungoverned.osgi.bundle.https.keystore";
+
+    /** Felix specific property to override the keystore password. */
+    private static final String FELIX_KEYSTORE_PASSWORD = "org.apache.felix.https.keystore.password";
+    private static final String OSCAR_KEYSTORE_PASSWORD = "org.ungoverned.osgi.bundle.https.password";
+
+    /** Felix specific property to override the keystore key password. */
+    private static final String FELIX_KEYSTORE_KEY_PASSWORD = "org.apache.felix.https.keystore.key.password";
+    private static final String OSCAR_KEYSTORE_KEY_PASSWORD = "org.ungoverned.osgi.bundle.https.key.password";
+
+    /** Felix specific property to control whether to enable HTTPS. */
+    private static final String FELIX_HTTPS_ENABLE = "org.apache.felix.https.enable";
+    private static final String  OSCAR_HTTPS_ENABLE   = "org.ungoverned.osgi.bundle.https.enable";
+
+    /** Felix specific property to control whether to enable HTTP. */
+    private static final String FELIX_HTTP_ENABLE = "org.apache.felix.http.enable";
+
+    /** Felix specific property to override the truststore file location. */
+    private static final String FELIX_TRUSTSTORE = "org.apache.felix.https.truststore";
+
+    /** Felix specific property to override the truststore password. */
+    private static final String FELIX_TRUSTSTORE_PASSWORD = "org.apache.felix.https.truststore.password";
+
+    /** Felix specific property to control whether to want or require HTTPS client certificates. Valid values are "none", "wants", "needs". Default is "none". */
+    private static final String FELIX_HTTPS_CLIENT_CERT = "org.apache.felix.https.clientcertificate";
+    
+    private final BundleContext context;
+    private boolean debug;
+    private int httpPort;
+    private int httpsPort;
+    private String keystore;
+    private String password;
+    private String keyPassword;
+    private boolean useHttps;
+    private String truststore;
+    private String trustPassword;
+    private boolean useHttp;
+    private String clientcert;
+
+    public JettyConfig(BundleContext context)
+    {
+        this.context = context;
+        reset();
+    }
+
+    public boolean isDebug()
+    {
+        return this.debug;
+    }
+
+    public boolean isUseHttp()
+    {
+        return this.useHttp;
+    }
+
+    public boolean isUseHttps()
+    {
+        return this.useHttps;
+    }
+
+    public int getHttpPort()
+    {
+        return this.httpPort;
+    }
+
+    public int getHttpsPort()
+    {
+        return this.httpsPort;
+    }
+
+    public String getKeystore()
+    {
+        return this.keystore;
+    }
+
+    public String getPassword()
+    {
+        return this.password;
+    }
+
+    public String getTruststore()
+    {
+        return this.truststore;
+    }
+
+    public String getTrustPassword()
+    {
+        return this.trustPassword;
+    }
+
+    public String getKeyPassword()
+    {
+        return this.keyPassword;
+    }
+
+    public String getClientcert()
+    {
+        return this.clientcert;
+    }
+
+    public void reset()
+    {
+        update(null);
+    }
+
+    public void update(Dictionary props)
+    {
+        if (props == null) {
+            props = new Properties();
+        }
+
+        this.debug = getBooleanProperty(props, FELIX_HTTP_DEBUG, getBooleanProperty(props, HTTP_DEBUG, false));
+        this.httpPort = getIntProperty(props, HTTP_PORT, 8080);
+        this.httpsPort = getIntProperty(props, HTTPS_PORT, 433);
+        this.keystore = getProperty(props, FELIX_KEYSTORE, this.context.getProperty(OSCAR_KEYSTORE));
+        this.password = getProperty(props, FELIX_KEYSTORE_PASSWORD, this.context.getProperty(OSCAR_KEYSTORE_PASSWORD));
+        this.keyPassword = getProperty(props, FELIX_KEYSTORE_KEY_PASSWORD, this.context.getProperty(OSCAR_KEYSTORE_KEY_PASSWORD));
+        this.useHttps = getBooleanProperty(props, FELIX_HTTPS_ENABLE, getBooleanProperty(props, OSCAR_HTTPS_ENABLE, false));
+        this.useHttp = getBooleanProperty(props, FELIX_HTTP_ENABLE, true);
+        this.truststore = getProperty(props, FELIX_TRUSTSTORE, null);
+        this.trustPassword = getProperty(props, FELIX_TRUSTSTORE_PASSWORD, null);
+        this.clientcert = getProperty(props, FELIX_HTTPS_CLIENT_CERT, "none");
+    }
+
+    private String getProperty(Dictionary props, String name, String defValue)
+    {
+        String value = (String)props.get(name);
+        if (value == null) {
+            value = this.context.getProperty(name);
+        }
+
+        return value != null ? value : defValue;
+    }
+
+    private boolean getBooleanProperty(Dictionary props, String name, boolean defValue)
+    {
+        String value = getProperty(props, name, null);
+        if (value != null) {
+            return (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("yes"));
+        }
+
+        return defValue;
+    }
+
+    private int getIntProperty(Dictionary props, String name, int defValue)
+    {
+        try {
+            return Integer.parseInt(getProperty(props, name, null));
+        } catch (Exception e) {
+            return defValue;
+        }
+    }
+}
diff --git a/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyLogger.java b/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyLogger.java
new file mode 100644
index 0000000..13f025c
--- /dev/null
+++ b/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyLogger.java
@@ -0,0 +1,111 @@
+/*
+ * 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.jetty.internal;
+
+import org.mortbay.log.Logger;
+import org.apache.felix.http.base.internal.util.SystemLogger;
+
+import java.util.Map;
+import java.util.HashMap;
+
+public final class JettyLogger
+    implements Logger
+{
+    private final static Map<String, Logger> LOGGERS =
+        new HashMap<String, Logger>();
+
+    private final String name;
+    private boolean debugEnabled;
+
+    public JettyLogger()
+    {
+        this("org.mortbay.log");
+    }
+
+    public JettyLogger(String name)
+    {
+        this.name = name;
+    }
+
+    public org.mortbay.log.Logger getLogger(String name)
+    {
+        Logger logger = LOGGERS.get(name);
+        if (logger == null) {
+            logger = new JettyLogger(name);
+            logger.setDebugEnabled(isDebugEnabled());
+            LOGGERS.put(name, logger);
+        }
+
+        return logger;
+    }
+
+    public boolean isDebugEnabled()
+    {
+        return this.debugEnabled;
+    }
+
+    public void setDebugEnabled(boolean enabled)
+    {
+        this.debugEnabled = enabled;
+    }
+
+    public void debug(String msg, Throwable cause)
+    {
+        SystemLogger.get().debug(msg);
+    }
+
+    public void debug(String msg, Object arg0, Object arg1)
+    {
+        SystemLogger.get().debug(format(msg, arg0, arg1));
+    }
+
+    public void info(String msg, Object arg0, Object arg1)
+    {
+        SystemLogger.get().info(format(msg, arg0, arg1));
+    }
+
+    public void warn(String msg, Throwable cause)
+    {
+        SystemLogger.get().warning(msg, cause);
+    }
+
+    public void warn( String msg, Object arg0, Object arg1 )
+    {
+        SystemLogger.get().warning(format(msg, arg0, arg1), null);
+    }
+
+    public String toString()
+    {
+        return this.name;
+    }
+
+    private String format(String msg, Object arg0, Object arg1)
+    {
+        int i0 = msg.indexOf("{}");
+        int i1 = i0 < 0 ? -1 : msg.indexOf("{}", i0 + 2);
+
+        if (arg1 != null && i1 >= 0) {
+            msg = msg.substring(0, i1) + arg1 + msg.substring(i1 + 2);
+        }
+
+        if (arg0 != null && i0 >= 0) {
+            msg = msg.substring(0, i0) + arg0 + msg.substring(i0 + 2);
+        }
+
+        return msg;
+    }
+}
diff --git a/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java b/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java
new file mode 100644
index 0000000..7cfffe9
--- /dev/null
+++ b/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java
@@ -0,0 +1,213 @@
+/*
+ * 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.jetty.internal;
+
+import org.osgi.service.cm.ManagedService;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+import org.mortbay.jetty.security.HashUserRealm;
+import org.mortbay.jetty.security.SslSelectChannelConnector;
+import org.mortbay.jetty.Server;
+import org.mortbay.jetty.Connector;
+import org.mortbay.jetty.nio.SelectChannelConnector;
+import org.mortbay.jetty.servlet.*;
+import org.mortbay.log.Log;
+import org.mortbay.log.StdErrLog;
+import org.apache.felix.http.base.internal.util.SystemLogger;
+import org.apache.felix.http.base.internal.DispatcherServlet;
+import java.util.Properties;
+import java.util.Dictionary;
+
+public final class JettyService
+    implements ManagedService, Runnable
+{
+    /** PID for configuration of the HTTP service. */
+    private static final String PID = "org.apache.felix.http";
+
+    private final JettyConfig config;
+    private final BundleContext context;
+    private boolean running;
+    private Thread thread;
+    private ServiceRegistration configServiceReg;
+    private Server server;
+    private DispatcherServlet dispatcher;
+
+    public JettyService(BundleContext context, DispatcherServlet dispatcher)
+    {
+        this.context = context;
+        this.config = new JettyConfig(this.context);
+        this.dispatcher = dispatcher;
+    }
+    
+    public void start()
+        throws Exception
+    {
+        this.running = true;
+        this.thread = new Thread(this, "Jetty HTTP Service");
+        this.thread.start();
+
+        Properties props = new Properties();
+        props.put(Constants.SERVICE_PID, PID);
+
+        this.configServiceReg = this.context.registerService(ManagedService.class.getName(), this, props);
+    }
+
+    public void stop()
+        throws Exception
+    {
+        if (this.configServiceReg != null) {
+            this.configServiceReg.unregister();
+        }
+
+        this.running = false;
+        this.thread.interrupt();
+
+        try {
+            this.thread.join(3000);
+        } catch (InterruptedException e) {
+            // Do nothing
+        }
+    }
+    
+    public void updated(Dictionary props)
+        throws ConfigurationException
+    {
+        this.config.update(props);
+        if (this.thread != null) {
+            this.thread.interrupt();
+        }
+    }
+
+    private void startJetty()
+    {
+        try {
+            initializeJetty();
+        } catch (Exception e) {
+            SystemLogger.get().error("Exception while initializing Jetty.", e);
+        }
+    }
+
+    private void stopJetty()
+    {
+        try {
+            this.server.stop();
+        } catch (Exception e) {
+            SystemLogger.get().error("Exception while stopping Jetty.", e);
+        }
+    }
+
+    protected void initializeJettyLogger()
+    {
+        Log.setLog(new JettyLogger());
+    }
+
+    private void destroyJettyLogger()
+    {
+        Log.setLog(new StdErrLog());
+    }
+
+    private void initializeJetty()
+        throws Exception
+    {
+        HashUserRealm realm = new HashUserRealm("OSGi HTTP Service Realm");
+        this.server = new Server();
+        this.server.addUserRealm(realm);
+
+        if (this.config.isUseHttp()) {
+            initializeHttp();
+        }
+
+        if (this.config.isUseHttps()) {
+            initializeHttps();
+        }
+
+        Context context = new Context(this.server, "/", Context.SESSIONS);
+        context.addServlet(new ServletHolder(this.dispatcher), "/*");
+
+        this.server.start();
+    }
+
+    private void initializeHttp()
+        throws Exception
+    {
+        Connector connector = new SelectChannelConnector();
+        connector.setPort(this.config.getHttpPort());
+        connector.setMaxIdleTime(60000);
+        this.server.addConnector(connector);
+    }
+
+    private void initializeHttps()
+        throws Exception
+    {
+        SslSelectChannelConnector connector = new SslSelectChannelConnector();
+        connector.setPort(this.config.getHttpsPort());
+        connector.setMaxIdleTime(60000);
+        
+        if (this.config.getKeystore() != null) {
+            connector.setKeystore(this.config.getKeystore());
+        }
+        
+        if (this.config.getPassword() != null) {
+            System.setProperty(SslSelectChannelConnector.PASSWORD_PROPERTY, this.config.getPassword());
+            connector.setPassword(this.config.getPassword());
+        }
+        
+        if (this.config.getKeyPassword() != null) {
+            System.setProperty(SslSelectChannelConnector.KEYPASSWORD_PROPERTY, this.config.getKeyPassword());
+            connector.setKeyPassword(this.config.getKeyPassword());
+        }
+        
+        if (this.config.getTruststore() != null) {
+            connector.setTruststore(this.config.getTruststore());
+        }
+        
+        if (this.config.getTrustPassword() != null) {
+            connector.setTrustPassword(this.config.getTrustPassword());
+        }
+        
+        if ("wants".equals(this.config.getClientcert())) {
+            connector.setWantClientAuth(true);
+        } else if ("needs".equals(this.config.getClientcert())) {
+            connector.setNeedClientAuth(true);
+        }
+
+        this.server.addConnector(connector);
+    }
+
+    public void run()
+    {
+        Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
+
+        while (this.running) {
+            initializeJettyLogger();
+            startJetty();
+
+            synchronized (this) {
+                try {
+                    wait();
+                } catch (InterruptedException e) {
+                    // we will definitely be interrupted
+                }
+            }
+
+            stopJetty();
+            destroyJettyLogger();
+        }
+    }
+}