| /* |
| * 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.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.OsgiManagerPlugin; |
| 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; |
| import org.osgi.service.log.LogService; |
| |
| |
| /** |
| * 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 = getPlugins(); |
| for ( int i = 0; i < plugin.length; i++ ) |
| { |
| plugin[i].dispose(); |
| } |
| |
| 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; |
| } |
| |
| void addInternalPlugin( final OsgiManager osgiManager, final String pluginClassName, final String label) |
| { |
| final Plugin plugin = new InternalPlugin(this, osgiManager, pluginClassName, label); |
| addPlugin( label, plugin ); |
| } |
| |
| /** |
| * 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, consolePlugin, label ); |
| 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 ) |
| { |
| AbstractWebConsolePlugin consolePlugin = null; |
| if ( label != null && label.length() > 0 ) |
| { |
| final Plugin plugin; |
| synchronized ( plugins ) |
| { |
| plugin = ( Plugin ) plugins.get( label ); |
| } |
| |
| if ( plugin != null ) |
| { |
| consolePlugin = plugin.getConsolePlugin(); |
| } |
| } |
| else |
| { |
| Plugin[] plugins = getPlugins(); |
| for ( int i = 0; i < plugins.length && consolePlugin == null; i++ ) |
| { |
| consolePlugin = plugins[i].getConsolePlugin(); |
| } |
| } |
| |
| return consolePlugin; |
| } |
| |
| |
| /** |
| * 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. |
| * <p> |
| * The special entry {@code felix.webconsole.labelMap} is the flat, |
| * unstructured map of labels to titles which is used as the |
| * respective request attribute (see FELIX-3833). |
| * |
| * @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 String defaultCategory ) |
| { |
| final Map map = new HashMap(); |
| final Map flatMap = new HashMap(); |
| Plugin[] plugins = getPlugins(); |
| for ( int i = 0; i < plugins.length; i++ ) |
| { |
| final Plugin plugin = plugins[i]; |
| |
| if ( !plugin.isEnabled() ) |
| { |
| continue; |
| } |
| |
| // support only one level for now |
| Map categoryMap = null; |
| String category = plugin.getCategory(); |
| if ( category == null || category.trim().length() == 0 ) |
| { |
| // FELIX-3798 configured default category |
| category = defaultCategory; |
| } |
| |
| // TODO: FELIX-3769; translate the Category |
| |
| categoryMap = findCategoryMap( map, category ); |
| |
| 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 */ |
| } |
| } |
| |
| categoryMap.put( label, title ); |
| flatMap.put( label, title ); |
| } |
| |
| // flat map of labels to titles (FELIX-3833) |
| map.put( WebConsoleConstants.ATTR_LABEL_MAP, flatMap ); |
| |
| return map; |
| } |
| |
| |
| private Map findCategoryMap( Map map, String categoryPath ) |
| { |
| Map categoryMap = null; |
| Map searchMap = map; |
| |
| String categories[] = categoryPath.split( "/" ); |
| |
| for ( int i = 0; i < categories.length; i++ ) |
| { |
| String categoryKey = "category." + categories[i]; |
| if ( searchMap.containsKey( categoryKey ) ) |
| { |
| categoryMap = ( Map ) searchMap.get( categoryKey ); |
| } |
| else |
| { |
| categoryMap = new HashMap(); |
| searchMap.put( categoryKey, categoryMap ); |
| } |
| searchMap = categoryMap; |
| } |
| |
| return categoryMap; |
| } |
| |
| |
| /** |
| * 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 ) |
| { |
| final Plugin[] plugin = getPlugins(); |
| if ( servletContext != null ) |
| { |
| this.servletContext = servletContext; |
| for ( int i = 0; i < plugin.length; i++ ) |
| { |
| try |
| { |
| plugin[i].init(); |
| } |
| catch ( ServletException se ) |
| { |
| // TODO: log !! |
| } |
| } |
| } |
| else |
| { |
| for ( int i = 0; i < plugin.length; i++ ) |
| { |
| try { |
| plugin[i].destroy(); |
| } catch (Throwable t) { |
| // TODO: log !! |
| } |
| } |
| this.servletContext = null; |
| } |
| } |
| |
| |
| /** |
| * 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) |
| * |
| * @see org.osgi.framework.ServiceListener#serviceChanged(org.osgi.framework.ServiceEvent) |
| */ |
| public void serviceChanged( ServiceEvent event ) |
| { |
| 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 ServletPlugin( 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 ) |
| { |
| synchronized ( plugins ) |
| { |
| plugins.put( label, plugin ); |
| } |
| } |
| |
| |
| private void removePlugin( final String label ) |
| { |
| final Plugin oldPlugin; |
| synchronized ( plugins ) |
| { |
| oldPlugin = ( Plugin ) plugins.remove( label ); |
| } |
| |
| if ( oldPlugin != null ) |
| { |
| oldPlugin.dispose(); |
| } |
| } |
| |
| |
| private Plugin[] getPlugins() |
| { |
| synchronized ( plugins ) |
| { |
| return ( Plugin[] ) plugins.values().toArray( new Plugin[plugins.size()] ); |
| } |
| } |
| |
| |
| 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 class Plugin implements ServletConfig |
| { |
| private final PluginHolder holder; |
| private final String label; |
| private String title; |
| private AbstractWebConsolePlugin consolePlugin; |
| |
| protected Plugin( final PluginHolder holder, final String label ) |
| { |
| this.holder = holder; |
| this.label = label; |
| } |
| |
| |
| protected Plugin( final PluginHolder holder, final AbstractWebConsolePlugin plugin, final String label ) |
| { |
| this( holder, label ); |
| |
| if ( plugin == null ) |
| { |
| throw new NullPointerException( "plugin" ); |
| } |
| |
| this.consolePlugin = plugin; |
| } |
| |
| void init() throws ServletException { |
| if (consolePlugin != null) { |
| consolePlugin.init( this ); |
| } |
| } |
| |
| void destroy() |
| { |
| if (consolePlugin != null) { |
| consolePlugin.destroy(); |
| } |
| } |
| |
| /** |
| * Cleans up this plugin when it is not used any longer. This means |
| * destroying the plugin servlet and, if it was registered as an OSGi |
| * service, ungetting the service. |
| */ |
| final void dispose() |
| { |
| if ( consolePlugin != null ) |
| { |
| try |
| { |
| consolePlugin.destroy(); |
| } |
| catch ( Exception e ) |
| { |
| // TODO: handle |
| } |
| |
| doUngetConsolePlugin( consolePlugin ); |
| |
| consolePlugin = null; |
| } |
| } |
| |
| |
| protected PluginHolder getHolder() |
| { |
| return holder; |
| } |
| |
| |
| Bundle getBundle() |
| { |
| return getHolder().getBundleContext().getBundle(); |
| } |
| |
| |
| final String getLabel() |
| { |
| return label; |
| } |
| |
| |
| protected void setTitle( String title ) |
| { |
| this.title = title; |
| } |
| |
| |
| final String getTitle() |
| { |
| if ( title == null ) |
| { |
| final String title = doGetTitle(); |
| this.title = ( title == null ) ? getLabel() : title; |
| } |
| return title; |
| } |
| |
| protected String doGetTitle() |
| { |
| // get the service now |
| final AbstractWebConsolePlugin consolePlugin = getConsolePlugin(); |
| |
| // reset the title: |
| // - null if the servlet cannot be loaded |
| // - to the servlet's actual title if the servlet is loaded |
| return ( consolePlugin != null ) ? consolePlugin.getTitle() : null; |
| } |
| |
| // methods added to support categories |
| |
| final String getCategory() { |
| return doGetCategory(); |
| } |
| |
| protected String doGetCategory() { |
| // get the service now |
| final AbstractWebConsolePlugin consolePlugin = getConsolePlugin(); |
| return ( consolePlugin != null ) ? consolePlugin.getCategory() : null; |
| } |
| |
| final AbstractWebConsolePlugin getConsolePlugin() |
| { |
| if ( consolePlugin == null ) |
| { |
| final AbstractWebConsolePlugin consolePlugin = doGetConsolePlugin(); |
| if ( consolePlugin != null ) |
| { |
| try |
| { |
| this.consolePlugin = consolePlugin; |
| init(); |
| } |
| catch ( ServletException se ) |
| { |
| // TODO: log |
| this.consolePlugin = null; |
| } |
| } else { |
| // TODO: log !! |
| } |
| } |
| return consolePlugin; |
| } |
| |
| protected boolean isEnabled() { |
| return true; |
| } |
| |
| protected AbstractWebConsolePlugin doGetConsolePlugin() |
| { |
| return consolePlugin; |
| } |
| |
| |
| protected void doUngetConsolePlugin( AbstractWebConsolePlugin consolePlugin ) |
| { |
| } |
| |
| |
| //---------- ServletConfig interface |
| |
| public String getInitParameter( String name ) |
| { |
| return null; |
| } |
| |
| |
| public Enumeration getInitParameterNames() |
| { |
| return new Enumeration() |
| { |
| public boolean hasMoreElements() |
| { |
| return false; |
| } |
| |
| |
| public Object nextElement() |
| { |
| throw new NoSuchElementException(); |
| } |
| }; |
| } |
| |
| |
| public ServletContext getServletContext() |
| { |
| return getHolder().getServletContext(); |
| } |
| |
| |
| public String getServletName() |
| { |
| return getTitle(); |
| } |
| |
| |
| } |
| |
| private static class ServletPlugin extends Plugin |
| { |
| private final ServiceReference serviceReference; |
| |
| |
| ServletPlugin( final PluginHolder holder, final ServiceReference serviceReference, final String label ) |
| { |
| super(holder, label); |
| this.serviceReference = serviceReference; |
| } |
| |
| |
| |
| |
| Bundle getBundle() |
| { |
| return serviceReference.getBundle(); |
| } |
| |
| |
| protected String doGetTitle() { |
| // check service Reference |
| final String title = getProperty( serviceReference, WebConsoleConstants.PLUGIN_TITLE ); |
| if ( title != null ) |
| { |
| return title; |
| } |
| |
| // 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 |
| setTitle(getLabel()); |
| |
| return super.doGetTitle(); |
| } |
| |
| // added to support categories |
| protected String doGetCategory() { |
| // check service Reference |
| final String category = getProperty( serviceReference, WebConsoleConstants.PLUGIN_CATEGORY ); |
| if ( category != null ) |
| { |
| return category; |
| } |
| |
| return super.doGetCategory(); |
| } |
| |
| protected AbstractWebConsolePlugin doGetConsolePlugin() |
| { |
| Object service = getHolder().getBundleContext().getService( serviceReference ); |
| if ( service instanceof Servlet ) |
| { |
| final AbstractWebConsolePlugin servlet; |
| if ( service instanceof AbstractWebConsolePlugin ) |
| { |
| servlet = ( AbstractWebConsolePlugin ) service; |
| } |
| else |
| { |
| servlet = new WebConsolePluginAdapter( getLabel(), ( Servlet ) service, serviceReference ); |
| } |
| |
| return servlet; |
| } |
| return null; |
| } |
| |
| protected void doUngetConsolePlugin( AbstractWebConsolePlugin consolePlugin ) |
| { |
| getHolder().getBundleContext().ungetService( serviceReference ); |
| } |
| |
| //---------- ServletConfig overwrite (based on ServletReference) |
| |
| public String getInitParameter( String name ) |
| { |
| Object property = serviceReference.getProperty( name ); |
| if ( property != null && !property.getClass().isArray() ) |
| { |
| return property.toString(); |
| } |
| |
| return super.getInitParameter( name ); |
| } |
| |
| |
| public Enumeration getInitParameterNames() |
| { |
| final String[] keys = 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(); |
| } |
| |
| }; |
| } |
| |
| } |
| |
| static class InternalPlugin extends Plugin |
| { |
| final String pluginClassName; |
| final OsgiManager osgiManager; |
| AbstractWebConsolePlugin plugin; |
| boolean doLog = true; |
| |
| protected InternalPlugin(PluginHolder holder, OsgiManager osgiManager, String pluginClassName, String label) |
| { |
| super(holder, label); |
| this.osgiManager = osgiManager; |
| this.pluginClassName = pluginClassName; |
| } |
| |
| protected final boolean isEnabled() { |
| // check if the plugin is enabled |
| return !osgiManager.isPluginDisabled(pluginClassName); |
| } |
| |
| protected AbstractWebConsolePlugin doGetConsolePlugin() |
| { |
| if (null == plugin) { |
| if (!isEnabled()) |
| { |
| if (doLog) |
| { |
| osgiManager.log( LogService.LOG_INFO, "Ignoring plugin " + pluginClassName + ": Disabled by configuration" ); |
| doLog = false; |
| } |
| return null; |
| } |
| |
| try |
| { |
| Class pluginClass = getClass().getClassLoader().loadClass(pluginClassName); |
| plugin = (AbstractWebConsolePlugin) pluginClass.newInstance(); |
| |
| if (plugin instanceof OsgiManagerPlugin) |
| { |
| ((OsgiManagerPlugin) plugin).activate(getBundle().getBundleContext()); |
| } |
| doLog = true; // reset logging if it succeeded |
| } |
| catch (Throwable t) |
| { |
| plugin = null; // in case only activate has faled! |
| if (doLog) |
| { |
| osgiManager.log( LogService.LOG_WARNING, "Failed to instantiate plugin " + pluginClassName, t ); |
| doLog = false; |
| } |
| } |
| } |
| |
| return plugin; |
| } |
| |
| protected void doUngetConsolePlugin(AbstractWebConsolePlugin consolePlugin) |
| { |
| if (consolePlugin == plugin) plugin = null; |
| if (consolePlugin instanceof OsgiManagerPlugin) |
| { |
| ((OsgiManagerPlugin) consolePlugin).deactivate(); |
| } |
| super.doUngetConsolePlugin(consolePlugin); |
| } |
| |
| } |
| } |