You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by ma...@apache.org on 2009/02/18 14:08:17 UTC

svn commit: r745505 - /felix/trunk/http.jetty/src/main/java/org/apache/felix/http/jetty/Activator.java

Author: marrs
Date: Wed Feb 18 13:08:17 2009
New Revision: 745505

URL: http://svn.apache.org/viewvc?rev=745505&view=rev
Log:
FELIX-323 Added support for ConfigAdmin. Also added a couple of extra configuration settings. Documented on the wiki.

Modified:
    felix/trunk/http.jetty/src/main/java/org/apache/felix/http/jetty/Activator.java

Modified: felix/trunk/http.jetty/src/main/java/org/apache/felix/http/jetty/Activator.java
URL: http://svn.apache.org/viewvc/felix/trunk/http.jetty/src/main/java/org/apache/felix/http/jetty/Activator.java?rev=745505&r1=745504&r2=745505&view=diff
==============================================================================
--- felix/trunk/http.jetty/src/main/java/org/apache/felix/http/jetty/Activator.java (original)
+++ felix/trunk/http.jetty/src/main/java/org/apache/felix/http/jetty/Activator.java Wed Feb 18 13:08:17 2009
@@ -18,6 +18,7 @@
  */
 package org.apache.felix.http.jetty;
 
+import java.util.Dictionary;
 import java.util.Properties;
 
 import org.mortbay.component.LifeCycle;
@@ -36,8 +37,11 @@
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
 import org.osgi.framework.ServiceFactory;
 import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
 import org.osgi.service.http.HttpService;
 import org.osgi.service.log.LogService;
 import org.osgi.util.tracker.ServiceTracker;
@@ -65,14 +69,16 @@
  *        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
-{
+public class Activator implements BundleActivator, ManagedService, Runnable {
+    private static final Properties EMPTY_PROPS = new Properties();
+    
     public static final boolean DEFAULT_HTTP_ENABLE = true;
     public static final boolean DEFAULT_HTTPS_ENABLE = false;
     public static final boolean DEFAULT_USE_NIO = true;
     public static final int DEFAULT_HTTPS_PORT = 443;
     public static final int DEFAULT_HTTP_PORT = 80;
     public static final String DEFAULT_SSL_PROVIDER = "org.mortbay.http.SunJsseListener";
+    public static final String DEFAULT_HTTPS_CLIENT_CERT = "none";
     
     /** Felix specific property to override the SSL provider. */
     public static final String FELIX_SSL_PROVIDER = "org.apache.felix.https.provider";
@@ -121,6 +127,18 @@
     /** Felix specific property to control whether to enable HTTP. */
     public static final String FELIX_HTTP_ENABLE = "org.apache.felix.http.enable";
     
+    /** Felix specific property to control whether to want or require HTTPS client certificates. Valid values are "none", "wants", "needs". Default is "none". */
+    public static final String FELIX_HTTPS_CLIENT_CERT = "org.apache.felix.https.clientcertificate";
+
+    /** Felix specific property to override the truststore file location. */
+    public static final String FELIX_TRUSTSTORE = "org.apache.felix.https.truststore";
+    
+    /** Felix specific property to override the truststore password. */
+    public static final String FELIX_TRUSTSTORE_PASSWORD = "org.apache.felix.https.truststore.password";
+    
+    /** PID for configuration of the HTTP service. */
+    protected static final String PID = "org.apache.felix.http";
+    
     protected static boolean debug = false;
     private static ServiceTracker m_logTracker = null;
 
@@ -140,38 +158,48 @@
     private String m_keyPasswd;
     private boolean m_useHttps;
     private String m_httpPortProperty;
+    private String m_truststore;
+    private String m_trustpasswd;
 
     private Properties m_svcProperties = new Properties();
     private boolean m_useHttp;
+    private String m_clientcert;
+    private ServiceRegistration m_configSvcReg;
+    private volatile boolean m_running;
+    private volatile Thread m_thread;
 
-    //
-    // Main class instance code
-    //
-
-    public void start( BundleContext bundleContext ) throws BundleException
-    {
+    public void start(BundleContext bundleContext) throws BundleException {
         m_bundleContext = bundleContext;
 
-
-        setConfiguration();
-
         m_logTracker = new ServiceTracker( bundleContext, LogService.class.getName(), null );
         m_logTracker.open();
 
-        // org.mortbay.util.Loader needs this (used for JDK 1.4 log classes)
-        Thread.currentThread().setContextClassLoader( this.getClass().getClassLoader() );
-        // set the Jetty logger to be LogService based
-        initializeJettyLogger();
-
-        try
-        {
+        setConfiguration(EMPTY_PROPS);
+        
+        m_running = true;
+        m_thread = new Thread(this, "Jetty HTTP Service Launcher");
+        m_thread.start();
+        
+        m_configSvcReg = m_bundleContext.registerService(ManagedService.class.getName(), this, new Properties() {{ put(Constants.SERVICE_PID, PID); }} );
+    }
+    
+    public void stop(BundleContext bundleContext) throws BundleException {
+        if (m_configSvcReg != null) {
+            m_configSvcReg.unregister();
+        }
+        
+        m_running = false;
+        m_thread.interrupt();
+        
+        m_logTracker.close();
+    }
+    
+    private void startJetty() {
+        try {
             initializeJetty();
-
         }
-        catch ( Exception ex )
-        {
-            //TODO: maybe throw a bundle exception in here?
-            log( LogService.LOG_INFO, "Http2", ex );
+        catch (Exception ex) {
+            log(LogService.LOG_ERROR, "Exception while initializing Jetty.", ex);
             return;
         }
 
@@ -182,102 +210,125 @@
         m_svcProperties = new Properties(m_svcProperties);
     }
 
-
-    private void setConfiguration() {
-        debug = getBooleanProperty(FELIX_HTTP_DEBUG, getBooleanProperty(HTTP_DEBUG, false));
-        
-        // get default HTTP and HTTPS ports as per the OSGi spec
-        m_httpPort = getIntProperty(HTTP_PORT, DEFAULT_HTTP_PORT);
-        m_httpsPort = getIntProperty(HTTPS_PORT, DEFAULT_HTTPS_PORT);
-        // collect other properties, default to legacy names only if new ones are not available
-        m_useNIO = getBooleanProperty(HTTP_NIO, DEFAULT_USE_NIO);
-        m_sslProvider = getStringProperty(FELIX_SSL_PROVIDER, getStringProperty(OSCAR_SSL_PROVIDER, DEFAULT_SSL_PROVIDER));
-        m_httpsPortProperty = getStringProperty(HTTPS_SVCPROP_PORT, HTTPS_PORT);
-        m_keystore = getStringProperty(FELIX_KEYSTORE, m_bundleContext.getProperty(OSCAR_KEYSTORE));
-        m_passwd = getStringProperty(FELIX_KEYSTORE_PASSWORD, m_bundleContext.getProperty(OSCAR_KEYSTORE_PASSWORD));
-        m_keyPasswd = getStringProperty(FELIX_KEYSTORE_KEY_PASSWORD, m_bundleContext.getProperty(OSCAR_KEYSTORE_KEY_PASSWORD));
-        m_useHttps = getBooleanProperty(FELIX_HTTPS_ENABLE, getBooleanProperty(OSCAR_HTTPS_ENABLE, DEFAULT_HTTPS_ENABLE));
-        m_httpPortProperty = getStringProperty(HTTP_SVCPROP_PORT, HTTP_PORT);
-        m_useHttp = getBooleanProperty(FELIX_HTTP_ENABLE, DEFAULT_HTTP_ENABLE);
-    }
-
-
-    public void stop( BundleContext bundleContext ) throws BundleException
-    {
-        //TODO: wonder if we need to closedown service factory ???
-
-        if ( m_svcReg != null )
-        {
+    private void stopJetty() {
+        if (m_svcReg != null) {
             m_svcReg.unregister();
+            // null the registration, because the listener assumes a non-null registration is valid
+            m_svcReg = null;
         }
 
-        try
-        {
+        try {
             m_server.stop();
         }
-        catch ( Exception e )
-        {
-            //TODO: log some form of error
+        catch (Exception e) {
+            log(LogService.LOG_ERROR, "Exception while stopping Jetty.", e);
         }
+    }
 
-        // replace non-LogService logger for jetty
-        Log.setLog( new StdErrLog() );
+    /**
+     * The main loop for running Jetty. We run Jetty in its own thread now because we need to
+     * modify this thread's context classloader. The main loop starts Jetty and then waits until
+     * it is interrupted. Then it stops Jetty, and either quits or restarts (depending on the
+     * reason why the thread was interrupted, because the bundle was stopped or the configuration
+     * was updated).
+     */
+    public void run() {
+        // org.mortbay.util.Loader needs this (used for JDK 1.4 log classes)
+        Thread.currentThread().setContextClassLoader( this.getClass().getClassLoader() );
         
-        m_logTracker.close();
+        while (m_running) {
+            // start jetty
+            initializeJettyLogger();
+            startJetty();
+            
+            // wait
+            synchronized (this) {
+                try {
+                    wait();
+                }
+                catch (InterruptedException e) {
+                    // we will definitely be interrupted
+                }
+            }
+            
+            // stop jetty
+            stopJetty();
+            destroyJettyLogger();
+        }
     }
-
     
-    public int getIntProperty(String name, int dflt_val)
-    {
-        int retval = dflt_val;
+    
+    public void updated(Dictionary props) throws ConfigurationException {
+        if (props == null) {
+            // fall back to default configuration
+            setConfiguration(EMPTY_PROPS);
+        }
+        else {
+            // let's see what we've got
+            setConfiguration(props);
+        }
+        // notify the thread that the configuration was updated, causing Jetty to
+        // restart
+        if (m_thread != null) {
+            m_thread.interrupt();
+        }
+    }
+    
+    private void setConfiguration(Dictionary props) {
+        debug = getBooleanProperty(props, FELIX_HTTP_DEBUG, getBooleanProperty(props, HTTP_DEBUG, false));
         
-        try
-        {
-            retval = Integer.parseInt( m_bundleContext.getProperty( name ) );
+        // get default HTTP and HTTPS ports as per the OSGi spec
+        m_httpPort = getIntProperty(props, HTTP_PORT, DEFAULT_HTTP_PORT);
+        m_httpsPort = getIntProperty(props, HTTPS_PORT, DEFAULT_HTTPS_PORT);
+        // collect other properties, default to legacy names only if new ones are not available
+        m_useNIO = getBooleanProperty(props, HTTP_NIO, DEFAULT_USE_NIO);
+        m_sslProvider = getStringProperty(props, FELIX_SSL_PROVIDER, getStringProperty(props, OSCAR_SSL_PROVIDER, DEFAULT_SSL_PROVIDER));
+        m_httpsPortProperty = getStringProperty(props, HTTPS_SVCPROP_PORT, HTTPS_PORT);
+        m_keystore = getStringProperty(props, FELIX_KEYSTORE, m_bundleContext.getProperty(OSCAR_KEYSTORE));
+        m_passwd = getStringProperty(props, FELIX_KEYSTORE_PASSWORD, m_bundleContext.getProperty(OSCAR_KEYSTORE_PASSWORD));
+        m_keyPasswd = getStringProperty(props, FELIX_KEYSTORE_KEY_PASSWORD, m_bundleContext.getProperty(OSCAR_KEYSTORE_KEY_PASSWORD));
+        m_useHttps = getBooleanProperty(props, FELIX_HTTPS_ENABLE, getBooleanProperty(props, OSCAR_HTTPS_ENABLE, DEFAULT_HTTPS_ENABLE));
+        m_httpPortProperty = getStringProperty(props, HTTP_SVCPROP_PORT, HTTP_PORT);
+        m_useHttp = getBooleanProperty(props, FELIX_HTTP_ENABLE, DEFAULT_HTTP_ENABLE);
+        m_truststore = getStringProperty(props, FELIX_TRUSTSTORE, null);
+        m_trustpasswd = getStringProperty(props, FELIX_TRUSTSTORE_PASSWORD, null);
+        m_clientcert = getStringProperty(props, FELIX_HTTPS_CLIENT_CERT, DEFAULT_HTTPS_CLIENT_CERT);
+    }
+
+    private String getProperty(Dictionary props, String name) {
+        String result = (String) props.get(name);
+        if (result == null) {
+            result = m_bundleContext.getProperty(name);
         }
-        catch ( Exception e )
-        {
-            // maybe log a message saying using default?
+        return result;
+    }
+    
+    private int getIntProperty(Dictionary props, String name, int dflt_val) {
+        int retval = dflt_val;
+        try {
+            retval = Integer.parseInt(getProperty(props, name));
+        }
+        catch (Exception e) {
             retval = dflt_val;
         }
-        
         return retval;
     }
-    
-    
-    public boolean getBooleanProperty(String name, boolean dflt_val)
-    {
-        boolean retval = dflt_val;
         
-        String strval = m_bundleContext.getProperty( name );
-        if ( strval != null)
-        {
-            if (strval.toLowerCase().equals( "true" ) ||
-                strval.toLowerCase().equals( "yes" ))
-            {
-                retval = true;
-            }
-            else
-            {
-                // poss should raise error/warn here
-                retval = false;
-            }
+    private boolean getBooleanProperty(Dictionary props, String name, boolean dflt_val) {
+        boolean retval = dflt_val;
+        String strval = getProperty(props, name);
+        if (strval != null) {
+            retval = (strval.toLowerCase().equals("true") || strval.toLowerCase().equals("yes"));
         }
-        
         return retval;
     }
-    
-    
-    public String getStringProperty(String name, String dflt_val)
-    {
+   
+    private String getStringProperty(Dictionary props, String name, String dflt_val) {
         String retval = dflt_val;
-        
-        String strval = m_bundleContext.getProperty( name );
-        if ( strval != null)
-        {
+        String strval = getProperty(props, name);
+        if (strval != null) {
             retval = strval;
         }
-        
         return retval;
     }
 
@@ -296,6 +347,11 @@
         }
     }
     
+    private void destroyJettyLogger() {
+        // replace non-LogService logger for jetty
+        Log.setLog( new StdErrLog() );
+    }
+    
     protected void initializeJetty() throws Exception
     {
         //TODO: Maybe create a separate "JettyServer" object here?
@@ -337,7 +393,6 @@
         m_server.start();
     }
 
-
     private void initializeHTTP() {
         Connector connector = m_useNIO ? 
                               (Connector) new SelectChannelConnector() : (Connector) new SocketConnector();
@@ -350,15 +405,12 @@
         m_server.addConnector( connector );
     }
 
-
     //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
     {
         if (m_useNIO) {
-            // we do not want to create a compile time dependency on Java 5 classes
-            // so we use a bit of reflection here
             SelectChannelConnector s_listener = (SelectChannelConnector) Class.forName("org.mortbay.jetty.security.SslSelectChannelConnector").newInstance();
             s_listener.addLifeCycleListener(new ConnectorListener(m_httpsPortProperty));
             s_listener.setPort(m_httpsPort);
@@ -374,6 +426,18 @@
                 System.setProperty("jetty.ssl.keypassword" /* SslSelectChannelConnector.KEYPASSWORD_PROPERTY */, m_keyPasswd);
                 s_listener.getClass().getMethod("setKeyPassword", new Class[] {String.class}).invoke(s_listener, new Object[] { m_keyPasswd });
             }
+            if (m_truststore != null) {
+                s_listener.getClass().getMethod("setTruststore", new Class[] {String.class}).invoke(s_listener, new Object[] { m_truststore });
+            }
+            if (m_trustpasswd != null) {
+                s_listener.getClass().getMethod("setTrustPassword", new Class[] {String.class}).invoke(s_listener, new Object[] { m_trustpasswd });
+            }
+            if ("wants".equals(m_clientcert)) {
+                s_listener.getClass().getMethod("setWantClientAuth", new Class[] { Boolean.TYPE }).invoke(s_listener, new Object[] { Boolean.TRUE });
+            }
+            else if ("needs".equals(m_clientcert)) {
+                s_listener.getClass().getMethod("setNeedClientAuth", new Class[] { Boolean.TYPE }).invoke(s_listener, new Object[] { Boolean.TRUE });
+            }
             m_server.addConnector(s_listener);
         }
         else {
@@ -392,11 +456,23 @@
                 System.setProperty(SslSocketConnector.KEYPASSWORD_PROPERTY, m_keyPasswd);
                 s_listener.setKeyPassword(m_keyPasswd);
             }
+            if (m_truststore != null) {
+                s_listener.setTruststore(m_truststore);
+            }
+            if (m_trustpasswd != null) {
+                s_listener.setTrustPassword(m_trustpasswd);
+            }
+            if ("wants".equals(m_clientcert)) {
+                s_listener.setWantClientAuth(true);
+                s_listener.getClass().getMethod("setWantClientAuth", new Class[] {Boolean.class}).invoke(s_listener, new Object[] { Boolean.TRUE });
+            }
+            else if ("needs".equals(m_clientcert)) {
+                s_listener.setNeedClientAuth(true);
+            }
             m_server.addConnector(s_listener);
         }
     }
 
-
     public static void debug( String txt )
     {
         if ( debug )
@@ -405,7 +481,6 @@
         }
     }
 
-
     public static void log( int level, String message, Throwable throwable )
     {
         LogService log = ( LogService ) m_logTracker.getService();
@@ -424,7 +499,6 @@
     }
 
     // Inner class to provide basic service factory functionality
-
     public class HttpServiceFactory implements ServiceFactory
     {
         public HttpServiceFactory()
@@ -433,7 +507,6 @@
             HttpServiceImpl.initializeStatics();
         }
 
-
         public Object getService( Bundle bundle, ServiceRegistration registration )
         {
             Object srv = new HttpServiceImpl( bundle, m_server, m_hdlr );
@@ -441,7 +514,6 @@
             return srv;
         }
 
-
         public void ungetService( Bundle bundle, ServiceRegistration registration, Object service )
         {
             debug( "** http service unget:" + bundle + ", service: " + service );
@@ -452,7 +524,6 @@
     // Innner class to listen for connector startup and register service
     // properties for actual ports used. Possible connections may have deferred
     // startup, so this should ensure "port" is retrieved once available
-    
     public class ConnectorListener implements LifeCycle.Listener
     {
         String m_svcPropName;
@@ -479,13 +550,11 @@
                 m_svcProperties = new Properties(m_svcProperties);
             }
         }
-           
+        
         public void lifeCycleStarting(LifeCycle event) {}
            
         public void lifeCycleStopped(LifeCycle event) {}
            
         public void lifeCycleStopping(LifeCycle event) {}         
-        
     }
-
 }
\ No newline at end of file