org.apache.felix.webconsole.internal.servlet.OsgiManager.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.felix.webconsole.internal.servlet.OsgiManager.java

Source

/*
 * 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.io.IOException;
import java.net.URL;
import java.text.MessageFormat;
import java.util.ArrayList;
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;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.FilenameUtils;
import org.apache.felix.webconsole.AbstractWebConsolePlugin;
import org.apache.felix.webconsole.BrandingPlugin;
import org.apache.felix.webconsole.WebConsoleConstants;
import org.apache.felix.webconsole.WebConsoleSecurityProvider;
import org.apache.felix.webconsole.internal.OsgiManagerPlugin;
import org.apache.felix.webconsole.internal.Util;
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.Constants;
import org.osgi.framework.Filter;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.http.HttpContext;
import org.osgi.service.http.HttpService;
import org.osgi.service.log.LogService;
import org.osgi.util.tracker.ServiceTracker;

/**
 * The <code>OSGi Manager</code> is the actual Web Console Servlet which
 * is registered with the OSGi Http Service and which maintains registered
 * console plugins.
 */
public class OsgiManager extends GenericServlet {

    /** Pseudo class version ID to keep the IDE quite. */
    private static final long serialVersionUID = 1L;

    /**
     * Old name of the request attribute providing the root to the web console.
     * This attribute is no deprecated and replaced by
     * {@link WebConsoleConstants#ATTR_APP_ROOT}.
     *
     * @deprecated use {@link WebConsoleConstants#ATTR_APP_ROOT} instead
     */
    private static final String ATTR_APP_ROOT_OLD = OsgiManager.class.getName() + ".appRoot";

    /**
     * Old name of the request attribute providing the mappings from label to
     * page title. This attribute is no deprecated and replaced by
     * {@link WebConsoleConstants#ATTR_LABEL_MAP}.
     *
     * @deprecated use {@link WebConsoleConstants#ATTR_LABEL_MAP} instead
     */
    private static final String ATTR_LABEL_MAP_OLD = OsgiManager.class.getName() + ".labelMap";

    /**
     * The name and value of a parameter which will prevent redirection to a
     * render after the action has been executed (value is "_noredir_"). This
     * may be used by programmatic action submissions.
     */
    public static final String PARAM_NO_REDIRECT_AFTER_ACTION = "_noredir_";

    /**
     * The name of the cookie storing user-configured locale
     * See https://issues.apache.org/jira/browse/FELIX-2267
     */
    private static final String COOKIE_LOCALE = "felix.webconsole.locale";

    static final String PROP_MANAGER_ROOT = "manager.root";

    static final String PROP_DEFAULT_RENDER = "default.render";

    static final String PROP_REALM = "realm";

    static final String PROP_USER_NAME = "username";

    static final String PROP_PASSWORD = "password";

    static final String PROP_ENABLED_PLUGINS = "plugins";

    static final String PROP_LOG_LEVEL = "loglevel";

    static final String PROP_LOCALE = "locale";

    static final String PROP_HTTP_SERVICE_SELECTOR = "http.service.filter";

    public static final int DEFAULT_LOG_LEVEL = LogService.LOG_WARNING;

    static final String DEFAULT_PAGE = BundlesServlet.NAME;

    static final String DEFAULT_REALM = "OSGi Management Console";

    static final String DEFAULT_USER_NAME = "admin";

    static final String DEFAULT_PASSWORD = "admin";

    static final String DEFAULT_HTTP_SERVICE_SELECTOR = "";

    /**
     * The default value for the {@link #PROP_MANAGER_ROOT} configuration
     * property (value is "/system/console").
     */
    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.ComponentsServlet",
            "org.apache.felix.webconsole.internal.compendium.ConfigManager",
            "org.apache.felix.webconsole.internal.compendium.ConfigurationAdminConfigurationPrinter",
            "org.apache.felix.webconsole.internal.compendium.LogServlet",
            "org.apache.felix.webconsole.internal.compendium.PreferencesConfigurationPrinter",
            "org.apache.felix.webconsole.internal.compendium.WireAdminConfigurationPrinter",
            "org.apache.felix.webconsole.internal.core.BundlesServlet",
            "org.apache.felix.webconsole.internal.core.PermissionsConfigurationPrinter",
            "org.apache.felix.webconsole.internal.core.ServicesConfigurationPrinter",
            "org.apache.felix.webconsole.internal.core.ServicesServlet",
            "org.apache.felix.webconsole.internal.deppack.DepPackServlet",
            "org.apache.felix.webconsole.internal.misc.LicenseServlet",
            "org.apache.felix.webconsole.internal.misc.ShellServlet",
            "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" };

    private BundleContext bundleContext;

    private HttpServiceTracker httpServiceTracker;

    private HttpService httpService;

    private PluginHolder holder;

    private ServiceTracker brandingTracker;

    private ServiceTracker securityProviderTracker;

    private ServiceRegistration configurationListener;

    // list of OsgiManagerPlugin instances activated during init. All these
    // instances will have to be deactivated during destroy
    private List osgiManagerPlugins = new ArrayList();

    private String webManagerRoot;

    // true if the OsgiManager is registered as a Servlet with the HttpService
    private boolean httpServletRegistered;

    // true if the resources have been registered with the HttpService
    private boolean httpResourcesRegistered;

    private Dictionary configuration;

    // See https://issues.apache.org/jira/browse/FELIX-2267
    private Locale configuredLocale;

    private Set enabledPlugins;

    ResourceBundleManager resourceBundleManager;

    private int logLevel = DEFAULT_LOG_LEVEL;

    public OsgiManager(BundleContext bundleContext) {
        this.bundleContext = bundleContext;
        this.holder = new PluginHolder(bundleContext);

        // setup the included plugins
        ClassLoader classLoader = getClass().getClassLoader();
        for (int i = 0; i < PLUGIN_CLASSES.length; i++) {
            String pluginClassName = PLUGIN_CLASSES[i];

            try {
                Class pluginClass = classLoader.loadClass(pluginClassName);
                Object plugin = pluginClass.newInstance();

                // check whether enabled by configuration
                if (isPluginDisabled(pluginClassName, plugin)) {
                    log(LogService.LOG_INFO, "Ignoring plugin " + pluginClassName + ": Disabled by configuration");
                    continue;
                }

                if (plugin instanceof OsgiManagerPlugin) {
                    ((OsgiManagerPlugin) plugin).activate(bundleContext);
                    osgiManagerPlugins.add(plugin);
                }
                if (plugin instanceof AbstractWebConsolePlugin) {
                    holder.addOsgiManagerPlugin((AbstractWebConsolePlugin) plugin);
                } else if (plugin instanceof BrandingPlugin) {
                    AbstractWebConsolePlugin.setBrandingPlugin((BrandingPlugin) plugin);
                }
            } catch (NoClassDefFoundError ncdfe) {
                String message = ncdfe.getMessage();
                if (message == null) {
                    // no message, construct it
                    message = "Class definition not found (NoClassDefFoundError)";
                } else if (message.indexOf(' ') < 0) {
                    // message is just a class name, try to be more descriptive
                    message = "Class " + message + " missing";
                }
                log(LogService.LOG_INFO, pluginClassName + " not enabled. Reason: " + message);
            } catch (Throwable t) {
                log(LogService.LOG_INFO, "Failed to instantiate plugin " + pluginClassName + ". Reason: " + t);
            }
        }

        // the resource bundle manager
        resourceBundleManager = new ResourceBundleManager(getBundleContext());

        // start the configuration render, providing the resource bundle manager
        ConfigurationRender cr = new ConfigurationRender(resourceBundleManager);
        cr.activate(bundleContext);
        osgiManagerPlugins.add(cr);
        holder.addOsgiManagerPlugin(cr);

        // start tracking external plugins after setting up our own plugins
        holder.open();

        // accept new console branding service
        brandingTracker = new BrandingServiceTracker(this);
        brandingTracker.open();

        // add support for pluggable security
        securityProviderTracker = new ServiceTracker(bundleContext, WebConsoleSecurityProvider.class.getName(),
                null);
        securityProviderTracker.open();

        // configure and start listening for configuration
        updateConfiguration(null);

        try {
            this.configurationListener = ConfigurationListener2.create(this);
        } catch (Throwable t2) {
            // might be caused by Metatype API not available
            // try without MetaTypeProvider
            try {
                this.configurationListener = ConfigurationListener.create(this);
            } catch (Throwable t) {
                // might be caused by CM API not available
            }
        }
    }

    public void dispose() {
        // dispose off held plugins
        holder.close();

        // dispose off the resource bundle manager
        if (resourceBundleManager != null) {
            resourceBundleManager.dispose();
            resourceBundleManager = null;
        }

        // stop listening for brandings
        if (brandingTracker != null) {
            brandingTracker.close();
            brandingTracker = null;
        }

        // deactivate any remaining plugins
        for (Iterator pi = osgiManagerPlugins.iterator(); pi.hasNext();) {
            Object plugin = pi.next();
            ((OsgiManagerPlugin) plugin).deactivate();
        }

        // simply remove all operations, we should not be used anymore
        this.osgiManagerPlugins.clear();

        // now drop the HttpService and continue with further destroyals
        if (httpServiceTracker != null) {
            httpServiceTracker.close();
            httpServiceTracker = null;
        }

        // stop listening for configuration
        if (configurationListener != null) {
            configurationListener.unregister();
            configurationListener = null;
        }

        this.bundleContext = null;
    }

    //---------- Servlet API

    /**
     * @see javax.servlet.GenericServlet#init()
     */
    public void init() {
        // base class initialization not needed, since the GenericServlet.init
        // is an empty method

        holder.setServletContext(getServletContext());

    }

    /**
     * @see javax.servlet.GenericServlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
     */
    public void service(final ServletRequest req, final ServletResponse res) throws ServletException, IOException {
        // don't really expect to be called within a non-HTTP environment
        service((HttpServletRequest) req, (HttpServletResponse) res);

        // ensure response has been sent back and response is committed
        // (we are authorative for our URL space and no other servlet should interfere)
        res.flushBuffer();
    }

    private void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // check whether we are not at .../{webManagerRoot}
        final String pathInfo = request.getPathInfo();
        if (pathInfo == null || pathInfo.equals("/")) {
            String path = request.getRequestURI();
            if (!path.endsWith("/")) {
                path = path.concat("/");
            }
            path = path.concat(holder.getDefaultPluginLabel());
            response.sendRedirect(path);
            return;
        }

        int slash = pathInfo.indexOf("/", 1);
        if (slash < 2) {
            slash = pathInfo.length();
        }

        final Locale locale = getConfiguredLocale(request);
        final String label = pathInfo.substring(1, slash);
        AbstractWebConsolePlugin plugin = getConsolePlugin(label);
        if (plugin != null) {
            final Map labelMap = holder.getLocalizedLabelMap(resourceBundleManager, locale);

            // the official request attributes
            request.setAttribute(WebConsoleConstants.ATTR_LANG_MAP, getLangMap());
            request.setAttribute(WebConsoleConstants.ATTR_LABEL_MAP, labelMap);
            request.setAttribute(WebConsoleConstants.ATTR_APP_ROOT,
                    request.getContextPath() + request.getServletPath());
            request.setAttribute(WebConsoleConstants.ATTR_PLUGIN_ROOT,
                    request.getContextPath() + request.getServletPath() + '/' + label);

            // deprecated request attributes
            request.setAttribute(ATTR_LABEL_MAP_OLD, labelMap);
            request.setAttribute(ATTR_APP_ROOT_OLD, request.getContextPath() + request.getServletPath());

            // wrap the response for localization and template variable replacement
            request = wrapRequest(request, locale);
            response = wrapResponse(request, response, plugin);

            plugin.service(request, response);
        } else {
            final String body404 = MessageFormat.format(
                    resourceBundleManager.getResourceBundle(bundleContext.getBundle(), locale).getString("404"),
                    new Object[] {
                            request.getContextPath() + request.getServletPath() + '/' + BundlesServlet.NAME });
            response.setCharacterEncoding("utf-8");
            response.setContentType("text/html");
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            response.getWriter().println(body404);
        }
    }

    private final AbstractWebConsolePlugin getConsolePlugin(final String label) {
        // backwards compatibility for the former "install" action which is
        // used by the Maven Sling Plugin
        if ("install".equals(label)) {
            return holder.getPlugin(BundlesServlet.NAME);
        }

        return holder.getPlugin(label);
    }

    // See https://issues.apache.org/jira/browse/FELIX-2267
    private final Locale getConfiguredLocale(HttpServletRequest request) {
        Locale locale = null;

        Cookie[] cookies = request.getCookies();
        for (int i = 0; cookies != null && i < cookies.length; i++) {
            if (COOKIE_LOCALE.equals(cookies[i].getName())) {
                locale = Util.parseLocaleString(cookies[i].getValue());
                break;
            }
        }

        // TODO: check UserAdmin ?

        if (locale == null)
            locale = configuredLocale;
        if (locale == null)
            locale = request.getLocale();

        return locale;
    }

    /**
     * @see javax.servlet.GenericServlet#destroy()
     */
    public void destroy() {
        // base class destroy not needed, since the GenericServlet.destroy
        // is an empty method

        holder.setServletContext(null);
    }

    //---------- internal

    BundleContext getBundleContext() {
        return bundleContext;
    }

    /**
     * Returns the Service PID used to retrieve configuration and to describe
     * the configuration properties.
     */
    String getConfigurationPid() {
        return getClass().getName();
    }

    /**
     * Calls the <code>GenericServlet.log(String)</code> method if the
     * configured log level is less than or equal to the given <code>level</code>.
     * <p>
     * Note, that the <code>level</code> parameter is only used to decide whether
     * the <code>GenericServlet.log(String)</code> method is called or not. The
     * actual implementation of the <code>GenericServlet.log</code> method is
     * outside of the control of this method.
     *
     * @param level The log level at which to log the message
     * @param message The message to log
     */
    private void log(int level, String message) {
        if (logLevel >= level) {
            log(message);
        }
    }

    /**
     * Calls the <code>GenericServlet.log(String, Throwable)</code> method if
     * the configured log level is less than or equal to the given
     * <code>level</code>.
     * <p>
     * Note, that the <code>level</code> parameter is only used to decide whether
     * the <code>GenericServlet.log(String, Throwable)</code> method is called
     * or not. The actual implementation of the <code>GenericServlet.log</code>
     * method is outside of the control of this method.
     *
     * @param level The log level at which to log the message
     * @param message The message to log
     * @param t The <code>Throwable</code> to log with the message
     */
    private void log(int level, String message, Throwable t) {
        if (logLevel >= level) {
            log(message, t);
        }
    }

    private HttpServletRequest wrapRequest(final HttpServletRequest request, final Locale locale) {
        return new HttpServletRequestWrapper(request) {
            /**
             * @see javax.servlet.ServletRequestWrapper#getLocale()
             */
            public Locale getLocale() {
                return locale;
            }
        };
    }

    private HttpServletResponse wrapResponse(final HttpServletRequest request, final HttpServletResponse response,
            final AbstractWebConsolePlugin plugin) {
        final Locale locale = request.getLocale();
        final ResourceBundle resourceBundle = resourceBundleManager.getResourceBundle(plugin.getBundle(), locale);
        return new FilteringResponseWrapper(response, resourceBundle, request);
    }

    private static class HttpServiceTracker extends ServiceTracker {

        private static final String HTTP_SERVICE = "org.osgi.service.http.HttpService";

        private final OsgiManager osgiManager;

        private final String httpServiceSelector;

        static HttpServiceTracker create(OsgiManager osgiManager, String httpServiceSelector) {
            // got a service selector filter
            if (httpServiceSelector != null && httpServiceSelector.length() > 0) {
                try {
                    final String filterString = "(&(" + Constants.OBJECTCLASS + "=" + HTTP_SERVICE + ")("
                            + httpServiceSelector + "))";
                    Filter filter = osgiManager.getBundleContext().createFilter(filterString);
                    return new HttpServiceTracker(osgiManager, httpServiceSelector, filter);
                } catch (InvalidSyntaxException ise) {
                    // TODO: log or throw or ignore ....
                }
            }

            // no filter or illegal filter string
            return new HttpServiceTracker(osgiManager);
        }

        private HttpServiceTracker(final OsgiManager osgiManager) {
            super(osgiManager.getBundleContext(), HTTP_SERVICE, null);
            this.osgiManager = osgiManager;
            this.httpServiceSelector = null;
        }

        private HttpServiceTracker(final OsgiManager osgiManager, final String httpServiceSelector,
                final Filter httpServiceFilter) {
            super(osgiManager.getBundleContext(), httpServiceFilter, null);
            this.osgiManager = osgiManager;
            this.httpServiceSelector = httpServiceSelector;
        }

        boolean isSameSelector(final String newHttpServiceSelector) {
            if (newHttpServiceSelector != null) {
                return newHttpServiceSelector.equals(httpServiceSelector);
            }
            return httpServiceSelector == null;
        }

        public Object addingService(ServiceReference reference) {
            Object service = super.addingService(reference);
            if (service instanceof HttpService) {
                osgiManager.bindHttpService((HttpService) service);
            }
            return service;
        }

        public void removedService(ServiceReference reference, Object service) {
            if (service instanceof HttpService) {
                osgiManager.unbindHttpService((HttpService) service);
            }

            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);
            }
            super.removedService(reference, service);
        }

    }

    protected synchronized void bindHttpService(HttpService httpService) {
        // 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");
            return;
        }

        Dictionary config = getConfiguration();

        // get authentication details
        String realm = this.getProperty(config, PROP_REALM, DEFAULT_REALM);
        String userId = this.getProperty(config, PROP_USER_NAME, DEFAULT_USER_NAME);
        String password = this.getProperty(config, PROP_PASSWORD, DEFAULT_PASSWORD);

        // register the servlet and resources
        try {
            HttpContext httpContext = new OsgiManagerHttpContext(httpService, realm,
                    new SecurityProvider(securityProviderTracker, userId, password));

            Dictionary servletConfig = toStringConfig(config);

            // register this servlet and take note of this
            httpService.registerServlet(this.webManagerRoot, this, servletConfig, httpContext);
            httpServletRegistered = true;

            // register resources and take of this
            httpService.registerResources(this.webManagerRoot + "/res", "/res", httpContext);
            httpResourcesRegistered = true;

        } catch (Exception e) {
            log(LogService.LOG_ERROR, "bindHttpService: Problem setting up", e);
        }

        this.httpService = httpService;
    }

    protected synchronized void unbindHttpService(HttpService httpService) {
        if (this.httpService != httpService) {
            log(LogService.LOG_DEBUG,
                    "unbindHttpService: Ignoring unbind of an HttpService to which we are not registered");
            return;
        }

        // drop the service reference
        this.httpService = null;

        if (httpResourcesRegistered) {
            try {
                httpService.unregister(this.webManagerRoot + "/res");
            } catch (Throwable t) {
                log(LogService.LOG_WARNING, "unbindHttpService: Failed unregistering Resources", t);
            }
            httpResourcesRegistered = false;
        }

        if (httpServletRegistered) {
            try {
                httpService.unregister(this.webManagerRoot);
            } catch (Throwable t) {
                log(LogService.LOG_WARNING, "unbindHttpService: Failed unregistering Servlet", t);
            }
            httpServletRegistered = false;
        }
    }

    private Dictionary getConfiguration() {
        return configuration;
    }

    synchronized void updateConfiguration(Dictionary config) {
        if (config == null) {
            config = new Hashtable();
        }

        configuration = config;

        final Object locale = config.get(PROP_LOCALE);
        configuredLocale = locale == null || locale.toString().trim().length() == 0 //
                ? null
                : Util.parseLocaleString(locale.toString().trim());

        logLevel = getProperty(config, PROP_LOG_LEVEL, DEFAULT_LOG_LEVEL);
        AbstractWebConsolePlugin.setLogLevel(logLevel);

        // 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);
        if (!newWebManagerRoot.startsWith("/")) {
            newWebManagerRoot = "/" + newWebManagerRoot;
        }

        // get the HTTP Service selector (and dispose tracker for later
        // recreation)
        final String newHttpServiceSelector = getProperty(config, PROP_HTTP_SERVICE_SELECTOR,
                DEFAULT_HTTP_SERVICE_SELECTOR);
        if (httpServiceTracker != null && !httpServiceTracker.isSameSelector(newHttpServiceSelector)) {
            httpServiceTracker.close();
            httpServiceTracker = null;
        }

        // get enabled plugins
        Object pluginValue = config.get(PROP_ENABLED_PLUGINS);
        if (pluginValue == null) {
            enabledPlugins = null;
        } else if (pluginValue.getClass().isArray()) {
            final Object[] names = (Object[]) pluginValue;
            enabledPlugins = new HashSet();
            for (int i = 0; i < names.length; i++) {
                enabledPlugins.add(String.valueOf(names[i]));
            }
        } else if (pluginValue instanceof Collection) {
            enabledPlugins = new HashSet();
            enabledPlugins.addAll((Collection) pluginValue);
        }

        // might update http service registration
        HttpService httpService = this.httpService;
        if (httpService != null) {
            // unbind old location first
            unbindHttpService(httpService);

            // switch location
            this.webManagerRoot = newWebManagerRoot;

            // bind new location now
            bindHttpService(httpService);
        } else {
            // just set the configured location (FELIX-2034)
            this.webManagerRoot = newWebManagerRoot;
        }

        // create or recreate the HTTP service tracker with the new selector
        if (httpServiceTracker == null) {
            httpServiceTracker = HttpServiceTracker.create(this, newHttpServiceSelector);
            httpServiceTracker.open();
        }
    }

    /**
     * Returns the named property from the configuration. If the property does
     * not exist, the default value <code>def</code> is returned.
     *
     * @param config The properties from which to returned the named one
     * @param name The name of the property to return
     * @param def The default value if the named property does not exist
     * @return The value of the named property as a string or <code>def</code>
     *         if the property does not exist
     */
    private String getProperty(Dictionary config, String name, String def) {
        Object value = config.get(name);
        if (value instanceof String) {
            return (String) value;
        }

        if (value == null) {
            return def;
        }

        return String.valueOf(value);
    }

    /**
     * Returns the named property from the configuration. If the property does
     * not exist, the default value <code>def</code> is returned.
     *
     * @param config The properties from which to returned the named one
     * @param name The name of the property to return
     * @param def The default value if the named property does not exist
     * @return The value of the named property as a string or <code>def</code>
     *         if the property does not exist
     */
    private int getProperty(Dictionary config, String name, int def) {
        Object value = config.get(name);
        if (value instanceof Number) {
            return ((Number) value).intValue();
        }

        // try to convert the value to a number
        if (value != null) {
            try {
                return Integer.parseInt(value.toString());
            } catch (NumberFormatException nfe) {
                // don't care
            }
        }

        // not a number, not convertible, not set, use default
        return def;
    }

    /**
     * Returns <code>true</code> if the plugin is an
     * {@link AbstractWebConsolePlugin} and a list of enabled plugins is
     * configured but the plugin is not contained in that list.
     * <p>
     * This method is intended to be used only for plugins contained in the
     * web console bundle itself, namely plugins listed in the
     * {@value #PLUGIN_CLASSES} list.
     */
    private boolean isPluginDisabled(String pluginClass, Object plugin) {
        return enabledPlugins != null && !enabledPlugins.contains(pluginClass)
                && (plugin instanceof AbstractWebConsolePlugin);
    }

    private Dictionary toStringConfig(Dictionary config) {
        Dictionary stringConfig = new Hashtable();
        for (Enumeration ke = config.keys(); ke.hasMoreElements();) {
            Object key = ke.nextElement();
            stringConfig.put(key.toString(), String.valueOf(config.get(key)));
        }
        return stringConfig;
    }

    private Map langMap;

    private final Map getLangMap() {
        if (null != langMap)
            return langMap;
        final Map map = new HashMap();
        final Bundle bundle = bundleContext.getBundle();
        final Enumeration e = bundle.findEntries("res/flags", null, false);
        while (e != null && e.hasMoreElements()) {
            final URL img = (URL) e.nextElement();
            final String name = FilenameUtils.getBaseName(img.getFile());
            try {
                final String locale = new Locale(name).getDisplayLanguage();
                map.put(name, null != locale ? locale : name);
            } catch (Throwable t) {
                t.printStackTrace();
                /* ignore invalid locale? */
            }
        }
        return langMap = map;
    }

    static class SecurityProvider implements WebConsoleSecurityProvider {

        final ServiceTracker tracker;
        final String username;
        final String password;

        SecurityProvider(ServiceTracker tracker, String username, String password) {
            this.tracker = tracker;
            this.username = username;
            this.password = password;
        }

        public Object authenticate(String username, String password) {
            WebConsoleSecurityProvider provider = (WebConsoleSecurityProvider) tracker.getService();
            if (provider != null) {
                return provider.authenticate(username, password);
            }
            if (this.username.equals(username) && this.password.equals(password)) {
                return username;
            }
            return null;
        }

        public boolean authorize(Object user, String role) {
            // no op: authorize everything
            return true;
        }
    }

}