FELIX-1637 support additional CSS references in plugins:
   * AbstractWebConsolePlugin extensions overwrite the getCssReferences() method
   * Plain Servlet plugins provide a felix.webconsole.css service registration
      property

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@818501 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/AbstractWebConsolePlugin.java b/webconsole/src/main/java/org/apache/felix/webconsole/AbstractWebConsolePlugin.java
index cf190de..bba66c2 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/AbstractWebConsolePlugin.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/AbstractWebConsolePlugin.java
@@ -167,6 +167,25 @@
         IOException;
 
 
+    /**
+     * Returns a list of CSS reference paths or <code>null</code> if no
+     * additional CSS files are provided by the plugin.
+     * <p>
+     * The result is an array of strings which are used as the value of
+     * the <code>href</code> attribute of the <code>&lt;link&gt;</code> elements
+     * placed in the head section of the HTML generated. If the reference is
+     * a relative path, it is turned into an absolute path by prepending the
+     * value of the {@link WebConsoleConstants#ATTR_APP_ROOT} request attribute.
+     *
+     * @return The list of additional CSS files to reference in the head
+     *      section or <code>null</code> if no such CSS files are required.
+     */
+    protected String[] getCssReferences()
+    {
+        return null;
+    }
+
+
     protected BundleContext getBundleContext()
     {
         return bundleContext;
@@ -373,12 +392,14 @@
         response.setCharacterEncoding( "utf-8" );
         response.setContentType( "text/html" );
 
-        PrintWriter pw = response.getWriter();
+        final PrintWriter pw = response.getWriter();
+
+        final String appRoot = ( String ) request.getAttribute( WebConsoleConstants.ATTR_APP_ROOT );
 
         String header = MessageFormat.format( getHeader(), new Object[]
-            { adminTitle, getTitle(), ( String ) request.getAttribute( WebConsoleConstants.ATTR_APP_ROOT ), getLabel(),
-                brandingPlugin.getFavIcon(), brandingPlugin.getMainStyleSheet(), brandingPlugin.getProductURL(),
-                brandingPlugin.getProductName(), brandingPlugin.getProductImage() } );
+            { adminTitle, getTitle(), appRoot, getLabel(), brandingPlugin.getFavIcon(),
+                brandingPlugin.getMainStyleSheet(), brandingPlugin.getProductURL(), brandingPlugin.getProductName(),
+                brandingPlugin.getProductImage(), getCssLinks( appRoot ) } );
         pw.println( header );
 
         return pw;
@@ -574,10 +595,12 @@
         //  2 application root path (ATTR_APP_ROOT)
         //  3 console plugin label (from the URI)
         //  4 branding favourite icon (BrandingPlugin.getFavIcon())
-        //  5 branding favourite icon (BrandingPlugin.getMainStyleSheet())
-        //  6 branding favourite icon (BrandingPlugin.getProductURL())
-        //  7 branding favourite icon (BrandingPlugin.getProductName())
-        //  8 branding favourite icon (BrandingPlugin.getProductImage())
+        //  5 branding main style sheet (BrandingPlugin.getMainStyleSheet())
+        //  6 branding product URL (BrandingPlugin.getProductURL())
+        //  7 branding product name (BrandingPlugin.getProductName())
+        //  8 branding product image (BrandingPlugin.getProductImage())
+        //  9 additional HTML code to be inserted into the <head> section
+        //    (for example plugin provided CSS links)
 
         final String header = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">"
 
@@ -589,6 +612,7 @@
 
         + "    <link href=\"{2}/res/ui/admin.css\" rel=\"stylesheet\" type=\"text/css\">"
         + "    <link href=\"{2}{5}\" rel=\"stylesheet\" type=\"text/css\">"
+        + "    {9}"
 
         + "    <script language=\"JavaScript\">"
         + "      appRoot = \"{2}\";"
@@ -624,4 +648,32 @@
             + "</html>";
         return footer;
     }
+
+
+    private String getCssLinks( final String appRoot )
+    {
+        // get the CSS references and return nothing if there are none
+        final String[] cssRefs = getCssReferences();
+        if ( cssRefs == null )
+        {
+            return "";
+        }
+
+        // build the CSS links from the references
+        final StringBuffer buf = new StringBuffer();
+        for ( int i = 0; i < cssRefs.length; i++ )
+        {
+            buf.append( "<link href='" );
+
+            final String cssRef = cssRefs[i];
+            if ( cssRef.startsWith( "/" ) )
+            {
+                buf.append( appRoot ).append( '/' );
+            }
+
+            buf.append( cssRef ).append( "' rel='stylesheet' type='text/css'>" );
+        }
+
+        return buf.toString();
+    }
 }
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/WebConsoleConstants.java b/webconsole/src/main/java/org/apache/felix/webconsole/WebConsoleConstants.java
index a1b7050..20f05fe 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/WebConsoleConstants.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/WebConsoleConstants.java
@@ -32,9 +32,9 @@
      * The URI address label under which the OSGi Manager plugin is called by
      * the OSGi Manager (value is "felix.webconsole.label").
      * <p>
-     * Only {@link #SERVICE_NAME} services with this service registration
-     * property set to a non-empty String values are accepted by the OSGi
-     * Manager as a plugin.
+     * This service registration property must be set to a single non-empty
+     * String value. Otherwise the {@link #SERVICE_NAME Servlet} services will
+     * be ignored by the Felix Web Console and not be used as a plugin.
      */
     public static final String PLUGIN_LABEL = "felix.webconsole.label";
 
@@ -42,15 +42,35 @@
      * The title under which the OSGi Manager plugin is called by
      * the OSGi Manager (value is "felix.webconsole.label").
      * <p>
-     * Only {@link #SERVICE_NAME} services with this service registration
-     * property set to a non-empty String values are accepted by the OSGi
-     * Manager as a plugin.
+     * For {@link #SERVICE_NAME Servlet} services not extending the
+     * {@link AbstractWebConsolePlugin} this property is required for the
+     * service to be used as a plugin. Otherwise the service is just ignored
+     * by the Felix Web Console.
      *
      * @since 2.0.0
      */
     public static final String PLUGIN_TITLE = "felix.webconsole.title";
 
     /**
+     * The name of the service registration properties providing references
+     * to addition CSS files that should be loaded when rendering the header
+     * for a registered plugin.
+     * <p>
+     * This property is expected to be a single string value, array of string
+     * values or a Collection (or Vector) of string values.
+     * <p>
+     * This service registration property is only used for plugins registered
+     * as {@link #SERVICE_NAME} services which do not extend the
+     * {@link AbstractWebConsolePlugin}. Extensions of the
+     * {@link AbstractWebConsolePlugin} should overwrite the
+     * {@link AbstractWebConsolePlugin#getCssReferences()} method to provide
+     * additional CSS resources.
+     *
+     * @since 2.0.0
+     */
+    public static final String PLUGIN_CSS_REFERENCES = "felix.webconsole.css";
+
+    /**
      * The name of the request attribute providing the absolute path of the
      * Web Console root (value is "felix.webconsole.appRoot"). This consists of
      * the servlet context path (from <code>HttpServletRequest.getContextPath()</code>)
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/WebConsolePluginAdapter.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/WebConsolePluginAdapter.java
index 94d517d..2a79580 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/WebConsolePluginAdapter.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/WebConsolePluginAdapter.java
@@ -20,12 +20,17 @@
 
 
 import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
 
 import javax.servlet.*;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import org.apache.felix.webconsole.AbstractWebConsolePlugin;
+import org.apache.felix.webconsole.WebConsoleConstants;
+import org.osgi.framework.ServiceReference;
 
 
 /**
@@ -49,12 +54,16 @@
     // the actual plugin to forward rendering requests to
     private final Servlet plugin;
 
+    // the CSS references (null if none)
+    private final String[] cssReferences;
 
-    public WebConsolePluginAdapter( String label, String title, Servlet plugin )
+
+    public WebConsolePluginAdapter( String label, String title, Servlet plugin, ServiceReference serviceReference )
     {
         this.label = label;
         this.title = title;
         this.plugin = plugin;
+        this.cssReferences = toStringArray( serviceReference.getProperty( WebConsoleConstants.PLUGIN_CSS_REFERENCES ) );
     }
 
 
@@ -79,6 +88,17 @@
 
 
     /**
+     * Returns the CSS references from the
+     * {@link WebConsoleConstants#PLUGIN_CSS_REFERENCES felix.webconsole.css}
+     * service registration property of the plugin.
+     */
+    protected String[] getCssReferences()
+    {
+        return cssReferences;
+    }
+
+
+    /**
      * Call the plugin servlet's service method to render the content of this
      * page.
      */
@@ -161,4 +181,45 @@
         plugin.destroy();
         super.destroy();
     }
+
+
+    //---------- internal
+
+    private String[] toStringArray( final Object value )
+    {
+        if ( value instanceof String )
+        {
+            return new String[]
+                { ( String ) value };
+        }
+        else if ( value != null )
+        {
+            final Collection cssListColl;
+            if ( value.getClass().isArray() )
+            {
+                cssListColl = Arrays.asList( ( Object[] ) value );
+            }
+            else if ( value instanceof Collection )
+            {
+                cssListColl = ( Collection ) value;
+            }
+            else
+            {
+                cssListColl = null;
+            }
+
+            if ( cssListColl != null && !cssListColl.isEmpty() )
+            {
+                String[] entries = new String[cssListColl.size()];
+                int i = 0;
+                for ( Iterator cli = cssListColl.iterator(); cli.hasNext(); i++ )
+                {
+                    entries[i] = String.valueOf( cli.next() );
+                }
+                return entries;
+            }
+        }
+
+        return null;
+    }
 }
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/ConfigurationRender.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/ConfigurationRender.java
index 8d7714e..5da56b6 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/ConfigurationRender.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/ConfigurationRender.java
@@ -60,6 +60,9 @@
 
     public static final String TITLE = "Configuration Status";
 
+    private static final String[] CSS_REFS =
+        { "res/ui/configurationrender.css" };
+
     /**
      * Formatter pattern to generate a relative path for the generation
      * of the plain text or zip file representation of the status. The file
@@ -129,14 +132,19 @@
     }
 
 
+    protected String[] getCssReferences()
+    {
+        return CSS_REFS;
+    }
+
+
     protected void renderContent( HttpServletRequest request, HttpServletResponse response ) throws IOException
     {
 
         ConfigurationWriter pw = new HtmlConfigurationWriter( response.getWriter() );
 
         String appRoot = ( String ) request.getAttribute( WebConsoleConstants.ATTR_APP_ROOT );
-        pw.println( "<link href='" + appRoot + "/res/ui/configurationrender.css' rel='stylesheet' type='text/css'>" );
-        pw.println( "<script src='" + appRoot + "/res/ui/tw-1.1.js' language='JavaScript'></script>" );
+        Util.script( pw, appRoot, "tw-1.1.js" );
 
         Util.startScript( pw );
         pw.println( "    $(document).ready(function(){" );
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/LicenseServlet.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/LicenseServlet.java
index 46a6dd4..29cdf36 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/LicenseServlet.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/LicenseServlet.java
@@ -34,6 +34,7 @@
 import javax.servlet.http.HttpServletResponse;
 
 import org.apache.felix.webconsole.AbstractWebConsolePlugin;
+import org.apache.felix.webconsole.WebConsoleConstants;
 import org.apache.felix.webconsole.internal.OsgiManagerPlugin;
 import org.apache.felix.webconsole.internal.Util;
 import org.json.JSONException;
@@ -48,6 +49,10 @@
 public class LicenseServlet extends AbstractWebConsolePlugin implements OsgiManagerPlugin
 {
 
+    private static final String[] CSS_REFS =
+        { "res/ui/license.css" };
+
+
     public String getLabel()
     {
         return "licenses";
@@ -60,13 +65,18 @@
     }
 
 
+    protected String[] getCssReferences()
+    {
+        return CSS_REFS;
+    }
+
+
     protected void renderContent( HttpServletRequest req, HttpServletResponse res ) throws IOException
     {
         PrintWriter pw = res.getWriter();
 
-        String appRoot = req.getContextPath() + req.getServletPath();
-        pw.println( "<link href='" + appRoot + "/res/ui/license.css' rel='stylesheet' type='text/css'>" );
-        pw.println( "<script src='" + appRoot + "/res/ui/license.js' language='JavaScript'></script>" );
+        final String appRoot = ( String ) req.getAttribute( WebConsoleConstants.ATTR_APP_ROOT );
+        Util.script( pw, appRoot, "license.js" );
 
         Bundle[] bundles = getBundleContext().getBundles();
         Util.sort( bundles );
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/ShellServlet.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/ShellServlet.java
index 06fcc52..77de81a 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/ShellServlet.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/ShellServlet.java
@@ -31,13 +31,18 @@
 
 import org.apache.felix.shell.ShellService;
 import org.apache.felix.webconsole.AbstractWebConsolePlugin;
+import org.apache.felix.webconsole.WebConsoleConstants;
 import org.apache.felix.webconsole.internal.OsgiManagerPlugin;
+import org.apache.felix.webconsole.internal.Util;
 import org.osgi.framework.BundleContext;
 import org.osgi.util.tracker.ServiceTracker;
 
 
 public class ShellServlet extends AbstractWebConsolePlugin implements OsgiManagerPlugin
 {
+    private static final String[] CSS_REFS =
+        { "res/ui/shell.css" };
+
     private ServiceTracker shellTracker;
 
 
@@ -109,14 +114,18 @@
     }
 
 
-    protected void renderContent( HttpServletRequest request, HttpServletResponse response ) throws ServletException,
-        IOException
+    protected String[] getCssReferences()
+    {
+        return CSS_REFS;
+    }
+
+
+    protected void renderContent( HttpServletRequest request, HttpServletResponse response ) throws IOException
     {
         PrintWriter pw = response.getWriter();
 
-        String appRoot = request.getContextPath() + request.getServletPath();
-        pw.println( "<link href=\"" + appRoot + "/res/ui/shell.css\" rel=\"stylesheet\" type=\"text/css\" />" );
-        pw.println( "<script src=\"" + appRoot + "/res/ui/shell.js\" type=\"text/javascript\"></script>" );
+        final String appRoot = ( String ) request.getAttribute( WebConsoleConstants.ATTR_APP_ROOT );
+        Util.script( pw, appRoot, "shell.js" );
 
         pw.println( "<br />" );
 
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java
index 6590479..dd28596 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java
@@ -516,7 +516,7 @@
                         if ( title instanceof String )
                         {
                             WebConsolePluginAdapter pluginAdapter = new WebConsolePluginAdapter( ( String ) label,
-                                ( String ) title, ( Servlet ) operation );
+                                ( String ) title, ( Servlet ) operation, reference );
 
                             // ensure the AbstractWebConsolePlugin is correctly setup
                             Bundle pluginBundle = reference.getBundle();