FELIX-2246 Allow for lazy loading of WebConsole plugins
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@931511 13f79535-47bb-0310-9956-ffa450edef68
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 a0950b8..daaf9aa 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/WebConsoleConstants.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/WebConsoleConstants.java
@@ -43,12 +43,18 @@
/**
* The title under which the OSGi Manager plugin is called by
- * the OSGi Manager (value is "felix.webconsole.label").
+ * the OSGi Manager (value is "felix.webconsole.title").
* <p>
* 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.
+ * <p>
+ * For {@link #SERVICE_NAME Servlet} services extending from the
+ * {@link AbstractWebConsolePlugin} abstract class this property is not
+ * technically required. To support lazy service access, e.g. for plugins
+ * implemented using the OSGi <i>Service Factory</i> pattern, the use
+ * of this service registration property is encouraged.
*
* @since 2.0.0
*/
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 3f841d1..0ad4a6e 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
@@ -48,9 +48,6 @@
// the plugin label (aka address)
private final String label;
- // the plugin title rendered in the top bar
- private final String title;
-
// the actual plugin to forward rendering requests to
private final Servlet plugin;
@@ -65,10 +62,9 @@
* @param plugin the plugin itself
* @param serviceReference reference to the plugin
*/
- public WebConsolePluginAdapter( String label, String title, Servlet plugin, ServiceReference serviceReference )
+ public WebConsolePluginAdapter( String label, Servlet plugin, ServiceReference serviceReference )
{
this.label = label;
- this.title = title;
this.plugin = plugin;
this.cssReferences = toStringArray( serviceReference.getProperty( WebConsoleConstants.PLUGIN_CSS_REFERENCES ) );
@@ -91,13 +87,17 @@
/**
- * Returns the title of this plugin page as defined in the constructor.
+ * Returns the title of this plugin page as defined by the
+ * {@link WebConsoleConstants#PLUGIN_TITLE} service registration attribute
+ * which is exposed as the servlet name in the servlet configuration.
*
* @see org.apache.felix.webconsole.AbstractWebConsolePlugin#getTitle()
*/
public String getTitle()
{
- return title;
+ // return the servlet name from the configuration but don't call
+ // the base class implementation, which calls getTitle()
+ return getServletConfig().getServletName();
}
@@ -190,6 +190,7 @@
return requestUri.endsWith( ".html" ) || requestUri.lastIndexOf( '.' ) < 0;
}
+
/**
* Directly refer to the plugin's service method unless the request method
* is <code>GET</code> in which case we defer the call into the service method
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 294ac6b..f5df6a4 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
@@ -22,7 +22,6 @@
import java.util.Collection;
import java.util.Dictionary;
import java.util.Enumeration;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
@@ -32,7 +31,6 @@
import java.util.ResourceBundle;
import java.util.Set;
import javax.servlet.GenericServlet;
-import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
@@ -43,12 +41,10 @@
import org.apache.felix.webconsole.BrandingPlugin;
import org.apache.felix.webconsole.WebConsoleConstants;
import org.apache.felix.webconsole.internal.OsgiManagerPlugin;
-import org.apache.felix.webconsole.internal.WebConsolePluginAdapter;
import org.apache.felix.webconsole.internal.core.BundlesServlet;
import org.apache.felix.webconsole.internal.filter.FilteringResponseWrapper;
import org.apache.felix.webconsole.internal.i18n.ResourceBundleManager;
import org.apache.felix.webconsole.internal.misc.ConfigurationRender;
-import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
@@ -125,8 +121,7 @@
static final String DEFAULT_MANAGER_ROOT = "/system/console";
static final String[] PLUGIN_CLASSES =
- {
- "org.apache.felix.webconsole.internal.compendium.ComponentConfigurationPrinter",
+ { "org.apache.felix.webconsole.internal.compendium.ComponentConfigurationPrinter",
"org.apache.felix.webconsole.internal.compendium.ComponentsServlet",
"org.apache.felix.webconsole.internal.compendium.ConfigManager",
"org.apache.felix.webconsole.internal.compendium.ConfigurationAdminConfigurationPrinter",
@@ -140,8 +135,7 @@
"org.apache.felix.webconsole.internal.misc.SystemPropertiesPrinter",
"org.apache.felix.webconsole.internal.misc.ThreadPrinter",
"org.apache.felix.webconsole.internal.obr.BundleRepositoryRender",
- "org.apache.felix.webconsole.internal.system.VMStatPlugin"
- };
+ "org.apache.felix.webconsole.internal.system.VMStatPlugin" };
private BundleContext bundleContext;
@@ -149,24 +143,16 @@
private HttpService httpService;
- private ServiceTracker pluginsTracker;
+ private PluginHolder holder;
private ServiceTracker brandingTracker;
private ServiceRegistration configurationListener;
- // map of plugins: indexed by the plugin label (String), values are
- // AbstractWebConsolePlugin instances
- private Map plugins = new HashMap();
-
// list of OsgiManagerPlugin instances activated during init. All these
// instances will have to be deactivated during destroy
private List osgiManagerPlugins = new ArrayList();
- private AbstractWebConsolePlugin defaultPlugin;
-
- private String defaultRenderName;
-
private String webManagerRoot;
// true if the OsgiManager is registered as a Servlet with the HttpService
@@ -183,9 +169,11 @@
private int logLevel = DEFAULT_LOG_LEVEL;
+
public OsgiManager( BundleContext bundleContext )
{
this.bundleContext = bundleContext;
+ this.holder = new PluginHolder( bundleContext );
updateConfiguration( null );
@@ -230,7 +218,6 @@
configurationListener = null;
}
- this.defaultPlugin = null;
this.bundleContext = null;
}
@@ -242,6 +229,8 @@
// base class initialization not needed, since the GenericServlet.init
// is an empty method
+ holder.setServletContext( getServletContext() );
+
// setup the included plugins
ClassLoader classLoader = getClass().getClassLoader();
for ( int i = 0; i < PLUGIN_CLASSES.length; i++ )
@@ -267,7 +256,7 @@
}
if ( plugin instanceof AbstractWebConsolePlugin )
{
- bindServlet( ( AbstractWebConsolePlugin ) plugin );
+ holder.addOsgiManagerPlugin( ( AbstractWebConsolePlugin ) plugin );
}
else if ( plugin instanceof BrandingPlugin )
{
@@ -299,18 +288,20 @@
resourceBundleManager = new ResourceBundleManager( getBundleContext() );
// start the configuration render, providing the resource bundle manager
- ConfigurationRender cr = new ConfigurationRender(resourceBundleManager);
+ ConfigurationRender cr = new ConfigurationRender( resourceBundleManager );
cr.activate( bundleContext );
- osgiManagerPlugins.add(cr);
- bindServlet( cr );
+ osgiManagerPlugins.add( cr );
+ holder.addOsgiManagerPlugin( cr );
// start tracking external plugins after setting up our own plugins
- pluginsTracker = new PluginServiceTracker( this );
- pluginsTracker.open();
- brandingTracker = new BrandingServiceTracker(this);
+ holder.open();
+
+ // accept new console branding service
+ brandingTracker = new BrandingServiceTracker( this );
brandingTracker.open();
}
+
public void service( ServletRequest req, ServletResponse res ) throws ServletException, IOException
{
@@ -326,7 +317,7 @@
{
path = path.concat( "/" );
}
- path = path.concat( defaultRenderName );
+ path = path.concat( holder.getDefaultPluginLabel() );
response.sendRedirect( path );
return;
}
@@ -338,24 +329,25 @@
}
final String label = pathInfo.substring( 1, slash );
- AbstractWebConsolePlugin plugin = ( AbstractWebConsolePlugin ) plugins.get( label );
+ AbstractWebConsolePlugin plugin = holder.getPlugin( label );
if ( plugin == null )
{
if ( "install".equals( label ) )
{
- plugin = ( AbstractWebConsolePlugin ) plugins.get( BundlesServlet.NAME );
+ plugin = holder.getPlugin( BundlesServlet.NAME );
}
}
if ( plugin != null )
{
- final Map labelMap = getLocalizedLabelMap( request.getLocale() );
+ final Map labelMap = holder.getLocalizedLabelMap( resourceBundleManager, request.getLocale() );
// the official request attributes
req.setAttribute( WebConsoleConstants.ATTR_LABEL_MAP, labelMap );
req.setAttribute( WebConsoleConstants.ATTR_APP_ROOT, request.getContextPath() + request.getServletPath() );
- req.setAttribute( WebConsoleConstants.ATTR_PLUGIN_ROOT, request.getContextPath() + request.getServletPath() + '/' + label);
+ req.setAttribute( WebConsoleConstants.ATTR_PLUGIN_ROOT, request.getContextPath() + request.getServletPath()
+ + '/' + label );
// deprecated request attributes
req.setAttribute( ATTR_LABEL_MAP_OLD, labelMap );
@@ -374,11 +366,15 @@
}
+
public void destroy()
{
// base class destroy not needed, since the GenericServlet.destroy
// is an empty method
+ // dispose off held plugins
+ holder.close();
+
// dispose off the resource bundle manager
if ( resourceBundleManager != null )
{
@@ -386,13 +382,8 @@
resourceBundleManager = null;
}
- // stop listening for plugins
- if ( pluginsTracker != null )
- {
- pluginsTracker.close();
- pluginsTracker = null;
- }
- if( brandingTracker != null )
+ // stop listening for brandings
+ if ( brandingTracker != null )
{
brandingTracker.close();
brandingTracker = null;
@@ -406,7 +397,6 @@
}
// simply remove all operations, we should not be used anymore
- this.plugins.clear();
this.osgiManagerPlugins.clear();
}
@@ -516,113 +506,34 @@
}
}
- private static class PluginServiceTracker extends ServiceTracker
+ private static class BrandingServiceTracker extends ServiceTracker
{
-
private final OsgiManager osgiManager;
- PluginServiceTracker( OsgiManager osgiManager )
+ BrandingServiceTracker( OsgiManager osgiManager )
{
- super( osgiManager.getBundleContext(), WebConsoleConstants.SERVICE_NAME, null );
+ super( osgiManager.getBundleContext(), BrandingPlugin.class.getName(), null );
this.osgiManager = osgiManager;
}
public Object addingService( ServiceReference reference )
{
- Object label = reference.getProperty( WebConsoleConstants.PLUGIN_LABEL );
- if ( label instanceof String )
+ Object plugin = super.addingService( reference );
+ if ( plugin instanceof BrandingPlugin )
{
- Object operation = super.addingService( reference );
- if ( operation instanceof Servlet )
- {
- // wrap the servlet if it is not an AbstractWebConsolePlugin
- // but has a title in the service properties
- final AbstractWebConsolePlugin plugin;
- if ( operation instanceof AbstractWebConsolePlugin )
- {
- plugin = ( AbstractWebConsolePlugin ) operation;
- }
- else
- {
-
- // define the title from the PLUGIN_TITLE registration
- // property, the servlet name or the servlet "toString"
- Object title = reference.getProperty( WebConsoleConstants.PLUGIN_TITLE );
- if ( !( title instanceof String ) )
- {
- if ( operation instanceof GenericServlet )
- {
- title = ( ( GenericServlet ) operation ).getServletName();
- }
-
- if ( !( title instanceof String ) )
- {
- title = operation.toString();
- }
- }
-
- plugin = new WebConsolePluginAdapter( ( String ) label, ( String ) title,
- ( Servlet ) operation, reference );
-
- // ensure the AbstractWebConsolePlugin is correctly setup
- Bundle pluginBundle = reference.getBundle();
- plugin.activate( pluginBundle.getBundleContext() );
- }
-
- osgiManager.bindServlet( plugin );
- }
- return operation;
+ AbstractWebConsolePlugin.setBrandingPlugin( ( BrandingPlugin ) plugin );
}
-
- return null;
+ return plugin;
}
public void removedService( ServiceReference reference, Object service )
{
- Object label = reference.getProperty( WebConsoleConstants.PLUGIN_LABEL );
- if ( label instanceof String )
- {
- // TODO: check reference properties !!
- osgiManager.unbindServlet( ( String ) label );
-
- // check whether the service is a WebConsolePluginAdapter in
- // which case we have to deactivate it here (as we activated it
- // while adding the service
- if ( service instanceof WebConsolePluginAdapter )
- {
- ( ( WebConsolePluginAdapter ) service ).deactivate();
- }
- }
-
- super.removedService( reference, service );
- }
- }
-
- private static class BrandingServiceTracker extends ServiceTracker
- {
- private final OsgiManager osgiManager;
-
- BrandingServiceTracker( OsgiManager osgiManager ){
- super( osgiManager.getBundleContext(), BrandingPlugin.class.getName(), null );
- this.osgiManager = osgiManager;
- }
-
- public Object addingService( ServiceReference reference ){
- Object plugin = super.addingService( reference );
- if ( plugin instanceof BrandingPlugin )
- {
- AbstractWebConsolePlugin.setBrandingPlugin((BrandingPlugin) plugin);
- }
- return plugin;
- }
-
- public void removedService( ServiceReference reference, Object service ){
if ( service instanceof BrandingPlugin )
{
- AbstractWebConsolePlugin.setBrandingPlugin(null);
+ AbstractWebConsolePlugin.setBrandingPlugin( null );
}
super.removedService( reference, service );
}
@@ -635,8 +546,7 @@
// do not bind service, when we are already bound
if ( this.httpService != null )
{
- log( LogService.LOG_DEBUG,
- "bindHttpService: Already bound to an HTTP Service, ignoring further services" );
+ log( LogService.LOG_DEBUG, "bindHttpService: Already bound to an HTTP Service, ignoring further services" );
return;
}
@@ -712,54 +622,6 @@
}
- private void bindServlet( final AbstractWebConsolePlugin plugin )
- {
- final String label = plugin.getLabel();
- final String title = plugin.getTitle();
- try
- {
- plugin.init( getServletConfig() );
- plugins.put( label, plugin );
-
- if ( this.defaultPlugin == null )
- {
- this.defaultPlugin = plugin;
- }
- else if ( label.equals( this.defaultRenderName ) )
- {
- this.defaultPlugin = plugin;
- }
- }
- catch ( ServletException se )
- {
- log( LogService.LOG_WARNING, "Initialization of plugin '" + title + "' (" + label
- + ") failed; not using this plugin", se );
- }
- }
-
-
- private void unbindServlet( String label )
- {
- AbstractWebConsolePlugin plugin = ( AbstractWebConsolePlugin ) plugins.remove( label );
- if ( plugin != null )
- {
- if ( this.defaultPlugin == plugin )
- {
- if ( this.plugins.isEmpty() )
- {
- this.defaultPlugin = null;
- }
- else
- {
- this.defaultPlugin = ( AbstractWebConsolePlugin ) plugins.values().iterator().next();
- }
- }
-
- plugin.destroy();
- }
- }
-
-
private Dictionary getConfiguration()
{
return configuration;
@@ -778,11 +640,8 @@
logLevel = getProperty( config, PROP_LOG_LEVEL, DEFAULT_LOG_LEVEL );
AbstractWebConsolePlugin.setLogLevel( logLevel );
- defaultRenderName = getProperty( config, PROP_DEFAULT_RENDER, DEFAULT_PAGE );
- if ( defaultRenderName != null && plugins.get( defaultRenderName ) != null )
- {
- defaultPlugin = ( AbstractWebConsolePlugin ) plugins.get( defaultRenderName );
- }
+ // default plugin page configuration
+ holder.setDefaultPluginLabel( getProperty( config, PROP_DEFAULT_RENDER, DEFAULT_PAGE ) );
// get the web manager root path
String newWebManagerRoot = this.getProperty( config, PROP_MANAGER_ROOT, DEFAULT_MANAGER_ROOT );
@@ -923,43 +782,4 @@
return stringConfig;
}
-
- /**
- * Builds the map of labels to plugin titles to be stored as the
- * <code>felix.webconsole.labelMap</code> request attribute. This map
- * optionally localizes the plugin title using the providing bundle's
- * resource bundle if the first character of the title is a percent
- * sign (%). Titles not prefixed with a percent sign are added to the
- * map unmodified.
- *
- * @param locale The locale to which the titles are to be localized
- *
- * @return The localized map of labels to titles
- */
- private final Map getLocalizedLabelMap( final Locale locale )
- {
- final Map map = new HashMap();
- for ( Iterator pi = plugins.values().iterator(); pi.hasNext(); )
- {
- final AbstractWebConsolePlugin plugin = ( AbstractWebConsolePlugin ) pi.next();
- final String label = plugin.getLabel();
- String title = plugin.getTitle();
- if ( title.startsWith( "%" ) )
- {
- try
- {
- final ResourceBundle resourceBundle = resourceBundleManager.getResourceBundle( plugin.getBundle(),
- locale );
- title = resourceBundle.getString( title.substring( 1 ) );
- }
- catch ( Throwable e )
- {
- /* ignore missing resource - use default title */
- }
- }
- map.put( label, title );
- }
-
- return map;
- }
}
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/PluginHolder.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/PluginHolder.java
new file mode 100644
index 0000000..5cc921d
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/PluginHolder.java
@@ -0,0 +1,571 @@
+/*
+ * 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.webconsole.internal.servlet;
+
+
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.ResourceBundle;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+
+import org.apache.felix.webconsole.AbstractWebConsolePlugin;
+import org.apache.felix.webconsole.WebConsoleConstants;
+import org.apache.felix.webconsole.internal.WebConsolePluginAdapter;
+import org.apache.felix.webconsole.internal.i18n.ResourceBundleManager;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+
+
+/**
+ * The <code>PluginHolder</code> class implements the maintenance and lazy
+ * access to web console plugin services.
+ */
+class PluginHolder implements ServiceListener
+{
+
+ // The Web Console's bundle context to access the plugin services
+ private final BundleContext bundleContext;
+
+ // registered plugins (Map<String label, Plugin plugin>)
+ private final Map plugins;
+
+ // The servlet context used to initialize plugin services
+ private ServletContext servletContext;
+
+ // the label of the default plugin
+ private String defaultPluginLabel;
+
+
+ PluginHolder( final BundleContext context )
+ {
+ this.bundleContext = context;
+ this.plugins = new HashMap();
+ }
+
+
+ //---------- OsgiManager support API
+
+ /**
+ * Start using the plugin manager with registration as a service listener
+ * and getting references to all plugins already registered in the
+ * framework.
+ */
+ void open()
+ {
+ try
+ {
+ bundleContext.addServiceListener( this, "(" + Constants.OBJECTCLASS + "="
+ + WebConsoleConstants.SERVICE_NAME + ")" );
+ }
+ catch ( InvalidSyntaxException ise )
+ {
+ // not expected, thus fail hard
+ throw new InternalError( "Failed registering for Servlet service events: " + ise.getMessage() );
+ }
+
+ try
+ {
+ ServiceReference[] refs = bundleContext.getServiceReferences( WebConsoleConstants.SERVICE_NAME, null );
+ if ( refs != null )
+ {
+ for ( int i = 0; i < refs.length; i++ )
+ {
+ serviceAdded( refs[i] );
+ }
+ }
+ }
+ catch ( InvalidSyntaxException ise )
+ {
+ // not expected, thus fail hard
+ throw new InternalError( "Failed getting existing Servlet services: " + ise.getMessage() );
+ }
+ }
+
+
+ /**
+ * Stop using the plugin manager by removing as a service listener and
+ * releasing all held plugins, which includes ungetting and destroying any
+ * held plugin services.
+ */
+ void close()
+ {
+ bundleContext.removeServiceListener( this );
+
+ Plugin[] plugin = ( Plugin[] ) plugins.values().toArray( new Plugin[plugins.size()] );
+ for ( int i = 0; i < plugin.length; i++ )
+ {
+ plugin[i].ungetService();
+ }
+
+ plugins.clear();
+ defaultPluginLabel = null;
+ }
+
+
+ /**
+ * Returns label of the default plugin
+ * @return label of the default plugin
+ */
+ String getDefaultPluginLabel()
+ {
+ return defaultPluginLabel;
+ }
+
+
+ /**
+ * Sets the label of the default plugin
+ * @param defaultPluginLabel
+ */
+ void setDefaultPluginLabel( String defaultPluginLabel )
+ {
+ this.defaultPluginLabel = defaultPluginLabel;
+ }
+
+
+ /**
+ * Returns the default plugin as identified by the {@link #getDefaultPlugin()}
+ * or any plugin if no plugin is registered with that label
+ *
+ * @return The default plugin or <code>null</code> if no plugin is
+ * registered at all
+ */
+ AbstractWebConsolePlugin getDefaultPlugin()
+ {
+ return getPlugin( defaultPluginLabel );
+ }
+
+
+ /**
+ * Adds an internal Web Console plugin
+ * @param consolePlugin The internal Web Console plugin to add
+ */
+ void addOsgiManagerPlugin( final AbstractWebConsolePlugin consolePlugin )
+ {
+ final String label = consolePlugin.getLabel();
+ final Plugin plugin = new Plugin( this, null, label );
+ plugin.setTitle( consolePlugin.getTitle() );
+ plugin.setConsolePlugin( consolePlugin );
+ addPlugin( label, plugin );
+ }
+
+
+ /**
+ * Remove the internal Web Console plugin registered under the given label
+ * @param label The label of the Web Console internal plugin to remove
+ */
+ void removeOsgiManagerPlugin( final String label )
+ {
+ removePlugin( label );
+ }
+
+
+ /**
+ * Returns the plugin registered under the given label or <code>null</code>
+ * if none is registered under that label. If the label is <code>null</code>
+ * or empty, any registered plugin is returned or <code>null</code> if
+ * no plugin is registered
+ *
+ * @param label The label of the plugin to return
+ * @return The plugin or <code>null</code> if no plugin is registered with
+ * the given label.
+ */
+ AbstractWebConsolePlugin getPlugin( final String label )
+ {
+ if ( label == null || label.length() == 0 )
+ {
+ if ( !plugins.isEmpty() )
+ {
+ return ( ( Plugin ) plugins.values().iterator().next() ).getConsolePlugin();
+ }
+ }
+ else
+ {
+ Plugin plugin = ( Plugin ) plugins.get( label );
+ if ( plugin != null )
+ {
+ return plugin.getConsolePlugin();
+ }
+ }
+
+ // no such plugin (or not any more)
+ return null;
+ }
+
+
+ /**
+ * Builds the map of labels to plugin titles to be stored as the
+ * <code>felix.webconsole.labelMap</code> request attribute. This map
+ * optionally localizes the plugin title using the providing bundle's
+ * resource bundle if the first character of the title is a percent
+ * sign (%). Titles not prefixed with a percent sign are added to the
+ * map unmodified.
+ *
+ * @param resourceBundleManager The ResourceBundleManager providing
+ * localized titles
+ * @param locale The locale to which the titles are to be localized
+ *
+ * @return The localized map of labels to titles
+ */
+ Map getLocalizedLabelMap( final ResourceBundleManager resourceBundleManager, final Locale locale )
+ {
+ final Map map = new HashMap();
+ for ( Iterator pi = plugins.values().iterator(); pi.hasNext(); )
+ {
+ final Plugin plugin = ( Plugin ) pi.next();
+ final String label = plugin.getLabel();
+ String title = plugin.getTitle();
+ if ( title.startsWith( "%" ) )
+ {
+ try
+ {
+ final ResourceBundle resourceBundle = resourceBundleManager.getResourceBundle( plugin.getBundle(),
+ locale );
+ title = resourceBundle.getString( title.substring( 1 ) );
+ }
+ catch ( Throwable e )
+ {
+ /* ignore missing resource - use default title */
+ }
+ }
+ map.put( label, title );
+ }
+
+ return map;
+ }
+
+
+ /**
+ * Returns the bundle context of the Web Console itself.
+ * @return the bundle context of the Web Console itself.
+ */
+ BundleContext getBundleContext()
+ {
+ return bundleContext;
+ }
+
+
+ /**
+ * Sets the servlet context to be used to initialize plugin services
+ * @param servletContext
+ */
+ void setServletContext( ServletContext servletContext )
+ {
+ this.servletContext = servletContext;
+ }
+
+
+ /**
+ * Returns the servlet context to be used to initialize plugin services
+ * @return the servlet context to be used to initialize plugin services
+ */
+ ServletContext getServletContext()
+ {
+ return servletContext;
+ }
+
+
+ //---------- ServletListener
+
+ /**
+ * Called when plugin services are registered or unregistered (or modified,
+ * which is currently ignored)
+ */
+ public void serviceChanged( ServiceEvent event )
+ {
+ final String label = getProperty( event.getServiceReference(), WebConsoleConstants.PLUGIN_LABEL );
+ if ( label != null )
+ {
+ switch ( event.getType() )
+ {
+ case ServiceEvent.REGISTERED:
+ // add service
+ serviceAdded( event.getServiceReference() );
+ break;
+
+ case ServiceEvent.UNREGISTERING:
+ // remove service
+ serviceRemoved( event.getServiceReference() );
+ break;
+
+ default:
+ // update service
+ break;
+ }
+ }
+ }
+
+
+ private void serviceAdded( final ServiceReference serviceReference )
+ {
+ final String label = getProperty( serviceReference, WebConsoleConstants.PLUGIN_LABEL );
+ if ( label != null )
+ {
+ addPlugin( label, new Plugin( this, serviceReference, label ) );
+ }
+ }
+
+
+ private void serviceRemoved( final ServiceReference serviceReference )
+ {
+ final String label = getProperty( serviceReference, WebConsoleConstants.PLUGIN_LABEL );
+ if ( label != null )
+ {
+ removePlugin( label );
+ }
+ }
+
+
+ private void addPlugin( final String label, final Plugin plugin )
+ {
+ plugins.put( label, plugin );
+ }
+
+
+ private void removePlugin( final String label )
+ {
+ final Plugin oldPlugin = ( Plugin ) plugins.remove( label );
+ if ( oldPlugin != null )
+ {
+ oldPlugin.ungetService();
+ }
+ }
+
+
+ static String getProperty( final ServiceReference service, final String propertyName )
+ {
+ final Object property = service.getProperty( propertyName );
+ if ( property instanceof String )
+ {
+ return ( String ) property;
+ }
+
+ return null;
+ }
+
+ private static final class Plugin implements ServletConfig
+ {
+ private final PluginHolder holder;
+ private final ServiceReference serviceReference;
+ private final String label;
+ private String title;
+ private AbstractWebConsolePlugin consolePlugin;
+
+
+ Plugin( final PluginHolder holder, final ServiceReference serviceReference, final String label )
+ {
+ this.holder = holder;
+ this.serviceReference = serviceReference;
+ this.label = label;
+ }
+
+
+ final Bundle getBundle()
+ {
+ if ( serviceReference != null )
+ {
+ return serviceReference.getBundle();
+ }
+ return holder.getBundleContext().getBundle();
+ }
+
+
+ final String getLabel()
+ {
+ return label;
+ }
+
+
+ void setTitle( String title )
+ {
+ this.title = title;
+ }
+
+
+ final String getTitle()
+ {
+ if ( title == null )
+ {
+ // assumption: serviceReference is only null for WebConsole
+ // internal plugins, for which the title field will always be set
+
+ // check service Reference
+ title = getProperty( serviceReference, WebConsoleConstants.PLUGIN_TITLE );
+ if ( title == null )
+ {
+ // temporarily set the title to a non-null value to prevent
+ // recursion issues if this method or the getServletName
+ // method is called while the servlet is being acquired
+ title = label;
+
+ // get the service now
+ acquireServlet();
+
+ // reset the title:
+ // - null if the servlet cannot be loaded
+ // - to the servlet's actual title if the servlet is loaded
+ title = ( consolePlugin != null ) ? consolePlugin.getTitle() : null;
+ }
+ }
+ return title;
+ }
+
+
+ final AbstractWebConsolePlugin getConsolePlugin()
+ {
+ acquireServlet();
+ return consolePlugin;
+ }
+
+
+ void setConsolePlugin( AbstractWebConsolePlugin service )
+ {
+ try
+ {
+ service.init( this );
+ this.consolePlugin = service;
+ }
+ catch ( ServletException se )
+ {
+ // TODO:
+ // log( LogService.LOG_WARNING, "Initialization of plugin '" + plugin.getTitle() + "' (" + plugin.getLabel()
+ // + ") failed; not using this plugin", se );
+ }
+
+ }
+
+
+ final void ungetService()
+ {
+ if ( consolePlugin != null )
+ {
+ try
+ {
+ consolePlugin.destroy();
+ }
+ catch ( Exception e )
+ {
+ // TODO: handle
+ }
+ consolePlugin = null;
+
+ // service reference may be null for WebConsole internal plugins
+ if ( serviceReference != null )
+ {
+ holder.getBundleContext().ungetService( serviceReference );
+ }
+ }
+ }
+
+
+ //---------- ServletConfig interface
+
+ public String getInitParameter( String name )
+ {
+ if ( serviceReference != null )
+ {
+ Object property = serviceReference.getProperty( name );
+ if ( property != null && !property.getClass().isArray() )
+ {
+ return property.toString();
+ }
+ }
+
+ return null;
+ }
+
+
+ public Enumeration getInitParameterNames()
+ {
+ final String[] keys = ( serviceReference == null ) ? new String[0] : serviceReference.getPropertyKeys();
+ return new Enumeration()
+ {
+ int idx = 0;
+
+
+ public boolean hasMoreElements()
+ {
+ return idx < keys.length;
+ }
+
+
+ public Object nextElement()
+ {
+ if ( hasMoreElements() )
+ {
+ return keys[idx++];
+ }
+ throw new NoSuchElementException();
+ }
+
+ };
+ }
+
+
+ public ServletContext getServletContext()
+ {
+ return holder.getServletContext();
+ }
+
+
+ public String getServletName()
+ {
+ return getTitle();
+ }
+
+
+ private void acquireServlet()
+ {
+ if ( consolePlugin == null )
+ {
+ // assumption: serviceReference is only null for WebConsole
+ // internal plugins, for which the consolePlugin field will
+ // always be set
+
+ Object service = holder.getBundleContext().getService( serviceReference );
+ if ( service instanceof Servlet )
+ {
+ final AbstractWebConsolePlugin servlet;
+ if ( service instanceof AbstractWebConsolePlugin )
+ {
+ servlet = ( AbstractWebConsolePlugin ) service;
+ }
+ else
+ {
+ servlet = new WebConsolePluginAdapter( label, ( Servlet ) service, serviceReference );
+ }
+
+ setConsolePlugin( servlet );
+ }
+ }
+ }
+ }
+}