This is an automated email from the ASF dual-hosted git repository.

cziegeler pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/felix-dev.git


The following commit(s) were added to refs/heads/master by this push:
     new 9e827e1eff FELIX-6626 : Support jakarta servlet registration
9e827e1eff is described below

commit 9e827e1eff91af1d757edd6933527ec671593ae1
Author: Carsten Ziegeler <[email protected]>
AuthorDate: Mon Aug 21 10:24:58 2023 +0200

    FELIX-6626 : Support jakarta servlet registration
---
 webconsole/pom.xml                                 |   7 +
 .../felix/webconsole/AbstractWebConsolePlugin.java |  36 ++-
 .../felix/webconsole/DefaultBrandingPlugin.java    |   6 +-
 .../felix/webconsole/DefaultVariableResolver.java  |  29 +-
 .../felix/webconsole/SimpleWebConsolePlugin.java   |   2 +-
 .../apache/felix/webconsole/VariableResolver.java  |   4 +-
 .../felix/webconsole/WebConsoleConstants.java      |   6 +-
 .../webconsole/WebConsoleSecurityProvider2.java    |   2 +-
 .../apache/felix/webconsole/WebConsoleUtil.java    |  93 ++++---
 .../felix/webconsole/i18n/LocalizationHelper.java  |  10 +-
 .../internal/configuration/ConfigManager.java      |  31 ++-
 .../webconsole/internal/core/BundlesServlet.java   |  45 ++-
 .../core/PermissionsConfigurationPrinter.java      |   6 +-
 .../core/ServicesConfigurationPrinter.java         |  64 +----
 .../webconsole/internal/core/ServicesServlet.java  |  14 +-
 .../internal/core/ServicesUsedInfoProvider.java    |   9 +-
 .../internal/filter/FilteringResponseWrapper.java  |   6 +-
 .../internal/filter/ResourceFilteringWriter.java   |   9 +-
 .../internal/i18n/CombinedEnumeration.java         |  24 +-
 .../internal/i18n/CombinedResourceBundle.java      |   2 +-
 .../i18n/ConsolePropertyResourceBundle.java        |  26 +-
 .../internal/i18n/ResourceBundleCache.java         |  87 +++---
 .../webconsole/internal/misc/LicenseServlet.java   |   5 +-
 .../internal/misc/SystemPropertiesPrinter.java     |   3 +-
 .../internal/servlet/JakartaServletAdapter.java    | 303 +++++++++++++++++++++
 .../internal/servlet/JakartaServletTracker.java    |   6 +
 .../webconsole/internal/servlet/PluginHolder.java  |  20 +-
 .../webconsole/internal/system/VMStatPlugin.java   |   6 +-
 .../felix/webconsole/servlet/AbstractServlet.java  | 201 ++++++++++++++
 .../servlet/RequestVariableResolver.java           |  62 +++++
 .../felix/webconsole/servlet/ServletConstants.java |  75 +++++
 .../webconsole/{spi => servlet}/package-info.java  |   4 +-
 .../apache/felix/webconsole/spi/package-info.java  |   2 +-
 .../webconsole/AbstractWebConsolePluginTest.java   |   4 +
 .../internal/servlet/OsgiManagerTest.java          |   4 +-
 35 files changed, 888 insertions(+), 325 deletions(-)

diff --git a/webconsole/pom.xml b/webconsole/pom.xml
index a373d50a11..3a3258d334 100644
--- a/webconsole/pom.xml
+++ b/webconsole/pom.xml
@@ -384,6 +384,13 @@
             <scope>provided</scope>
         </dependency>
 
+        <dependency>
+            <groupId>org.owasp.encoder</groupId>
+            <artifactId>encoder</artifactId>
+            <version>1.2.3</version>
+            <scope>provided</scope>
+        </dependency>
+
         <dependency>
             <groupId>org.osgi</groupId>
             <artifactId>org.osgi.service.log</artifactId>
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 25932644a5..374d6f305f 100644
--- 
a/webconsole/src/main/java/org/apache/felix/webconsole/AbstractWebConsolePlugin.java
+++ 
b/webconsole/src/main/java/org/apache/felix/webconsole/AbstractWebConsolePlugin.java
@@ -45,6 +45,7 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import org.apache.felix.webconsole.internal.servlet.OsgiManager;
+import org.apache.felix.webconsole.servlet.RequestVariableResolver;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.service.log.LogService;
@@ -638,11 +639,9 @@ public abstract class AbstractWebConsolePlugin extends 
HttpServlet {
      * @throws IOException on I/O error
      * @see #endResponse(PrintWriter)
      */
-    @SuppressWarnings({ "unchecked" })
-    protected PrintWriter startResponse( HttpServletRequest request, 
HttpServletResponse response ) throws IOException
-    {
-        response.setCharacterEncoding( "utf-8" ); //$NON-NLS-1$
-        response.setContentType( "text/html" ); //$NON-NLS-1$
+    protected PrintWriter startResponse( HttpServletRequest request, 
HttpServletResponse response ) throws IOException {
+        response.setCharacterEncoding( "utf-8" );
+        response.setContentType( "text/html" );
 
         final PrintWriter pw = response.getWriter();
 
@@ -650,24 +649,21 @@ public abstract class AbstractWebConsolePlugin extends 
HttpServlet {
 
         // support localization of the plugin title
         String title = getTitle();
-        if ( title.startsWith( "%" ) ) //$NON-NLS-1$
+        if ( title.startsWith( "%" ) )
         {
-            title = "${" + title.substring( 1 ) + "}"; //$NON-NLS-1$ 
//$NON-NLS-2$
+            title = "${" + title.substring( 1 ) + "}";
         }
 
-        VariableResolver resolver = 
WebConsoleUtil.getVariableResolver(request);
-        if (resolver instanceof DefaultVariableResolver) {
-            DefaultVariableResolver r = (DefaultVariableResolver) resolver;
-            r.put("head.title", title); //$NON-NLS-1$
-            r.put("head.label", getLabel()); //$NON-NLS-1$
-            r.put("head.cssLinks", getCssLinks(appRoot)); //$NON-NLS-1$
-            r.put("brand.name", brandingPlugin.getBrandName()); //$NON-NLS-1$
-            r.put("brand.product.url", brandingPlugin.getProductURL()); 
//$NON-NLS-1$
-            r.put("brand.product.name", brandingPlugin.getProductName()); 
//$NON-NLS-1$
-            r.put("brand.product.img", toUrl( 
brandingPlugin.getProductImage(), appRoot )); //$NON-NLS-1$
-            r.put("brand.favicon", toUrl( brandingPlugin.getFavIcon(), appRoot 
)); //$NON-NLS-1$
-            r.put("brand.css", toUrl( brandingPlugin.getMainStyleSheet(), 
appRoot )); //$NON-NLS-1$
-        }
+        final RequestVariableResolver r = 
WebConsoleUtil.getRequestVariableResolver(request);
+        r.put("head.title", title);
+        r.put("head.label", getLabel());
+        r.put("head.cssLinks", getCssLinks(appRoot));
+        r.put("brand.name", brandingPlugin.getBrandName());
+        r.put("brand.product.url", brandingPlugin.getProductURL());
+        r.put("brand.product.name", brandingPlugin.getProductName());
+        r.put("brand.product.img", toUrl( brandingPlugin.getProductImage(), 
appRoot ));
+        r.put("brand.favicon", toUrl( brandingPlugin.getFavIcon(), appRoot ));
+        r.put("brand.css", toUrl( brandingPlugin.getMainStyleSheet(), appRoot 
));
         pw.println( getHeader() );
 
         return pw;
diff --git 
a/webconsole/src/main/java/org/apache/felix/webconsole/DefaultBrandingPlugin.java
 
b/webconsole/src/main/java/org/apache/felix/webconsole/DefaultBrandingPlugin.java
index 0da020b530..c4e1ed66bf 100644
--- 
a/webconsole/src/main/java/org/apache/felix/webconsole/DefaultBrandingPlugin.java
+++ 
b/webconsole/src/main/java/org/apache/felix/webconsole/DefaultBrandingPlugin.java
@@ -63,7 +63,7 @@ import java.util.Properties;
  * <tr>
  *  <td>Vendor URL</td>
  *  <td>webconsole.vendor.url</td>
- *  <td>http://www.apache.org</td>
+ *  <td>https://www.apache.org</td>
  * </tr>
  * <tr>
  *  <td>Vendor Image</td>
@@ -133,10 +133,10 @@ public class DefaultBrandingPlugin implements 
BrandingPlugin
         // set the fields from the properties now
         brandName = props.getProperty( "webconsole.brand.name", "Apache Felix 
Web Console" ); //$NON-NLS-1$
         productName = props.getProperty( "webconsole.product.name", "Apache 
Felix" ); //$NON-NLS-1$
-        productURL = props.getProperty( "webconsole.product.url", 
"http://felix.apache.org"; ); //$NON-NLS-1$
+        productURL = props.getProperty( "webconsole.product.url", 
"https://felix.apache.org"; ); //$NON-NLS-1$
         productImage = props.getProperty( "webconsole.product.image", 
"/res/imgs/logo.png" ); //$NON-NLS-1$
         vendorName = props.getProperty( "webconsole.vendor.name", "The Apache 
Software Foundation" ); //$NON-NLS-1$
-        vendorURL = props.getProperty( "webconsole.vendor.url", 
"http://www.apache.org"; ); //$NON-NLS-1$
+        vendorURL = props.getProperty( "webconsole.vendor.url", 
"https://www.apache.org"; ); //$NON-NLS-1$
         vendorImage = props.getProperty( "webconsole.vendor.image", 
"/res/imgs/logo.png" ); //$NON-NLS-1$
         favIcon = props.getProperty( "webconsole.favicon", 
"/res/imgs/favicon.ico" ); //$NON-NLS-1$
         mainStyleSheet = props.getProperty( "webconsole.stylesheet", 
"/res/ui/webconsole.css" ); //$NON-NLS-1$
diff --git 
a/webconsole/src/main/java/org/apache/felix/webconsole/DefaultVariableResolver.java
 
b/webconsole/src/main/java/org/apache/felix/webconsole/DefaultVariableResolver.java
index 9fa37de286..1c422fc948 100644
--- 
a/webconsole/src/main/java/org/apache/felix/webconsole/DefaultVariableResolver.java
+++ 
b/webconsole/src/main/java/org/apache/felix/webconsole/DefaultVariableResolver.java
@@ -18,10 +18,10 @@
  */
 package org.apache.felix.webconsole;
 
-
 import java.util.HashMap;
 import java.util.Map;
 
+import org.apache.felix.webconsole.servlet.RequestVariableResolver;
 
 /**
  * The <code>DefaultVariableResolver</code> is a <code>HashMap</code> based
@@ -30,9 +30,11 @@ import java.util.Map;
  * used by the
  * {@link WebConsoleUtil#getVariableResolver(javax.servlet.ServletRequest)}
  * as the variable resolver if none has yet been assigned to the request.
+ * @deprecated Use {@link RequestVariableResolver} instead.
  */
 @SuppressWarnings({ "rawtypes" })
-public class DefaultVariableResolver extends HashMap implements 
VariableResolver {
+@Deprecated
+public class DefaultVariableResolver extends RequestVariableResolver 
implements VariableResolver {
 
     private static final long serialVersionUID = 4148807223433047780L;
 
@@ -51,7 +53,7 @@ public class DefaultVariableResolver extends HashMap 
implements VariableResolver
      * @see HashMap#HashMap(int, float)
      */
     public DefaultVariableResolver( final int initialCapacity, final float 
loadFactor ) {
-        super( initialCapacity, loadFactor );
+        this();
     }
 
     /**
@@ -61,7 +63,7 @@ public class DefaultVariableResolver extends HashMap 
implements VariableResolver
      * @see HashMap#HashMap(int)
      */
     public DefaultVariableResolver( final int initialCapacity ) {
-        super( initialCapacity );
+        this();
     }
 
     /**
@@ -72,23 +74,6 @@ public class DefaultVariableResolver extends HashMap 
implements VariableResolver
      */
     @SuppressWarnings({"unchecked"})
     public DefaultVariableResolver( final Map source ) {
-        super( source );
-    }
-
-    /**
-     * Returns the string representation of the value stored under the variable
-     * name in this map. If no value is stored under the variable name,
-     * <code>null</code> is returned.
-     *
-     * @param variable The name of the variable whose value is to be returned.
-     * @return The variable value or <code>null</code> if there is no entry
-     *      with the given name in this map.
-     */
-    public String resolve( final String variable ) {
-        Object value = get( variable );
-        if ( value != null ) {
-            return value.toString();
-        }
-        return null;
+        this.putAll(source);
     }
 }
diff --git 
a/webconsole/src/main/java/org/apache/felix/webconsole/SimpleWebConsolePlugin.java
 
b/webconsole/src/main/java/org/apache/felix/webconsole/SimpleWebConsolePlugin.java
index f91e666625..178bb662e8 100644
--- 
a/webconsole/src/main/java/org/apache/felix/webconsole/SimpleWebConsolePlugin.java
+++ 
b/webconsole/src/main/java/org/apache/felix/webconsole/SimpleWebConsolePlugin.java
@@ -133,7 +133,7 @@ public abstract class SimpleWebConsolePlugin extends 
AbstractWebConsolePlugin
             LocalizationHelper localization = new LocalizationHelper( bundle );
             ResourceBundle rb = 
localization.getResourceBundle(Locale.getDefault());
             if (rb != null) {
-                if ( this.title != null && this.title.startsWith( "%" ) ) { 
//$NON-NLS-1$
+                if ( this.title != null && this.title.startsWith( "%" ) ) {
                     String key = this.title.substring(1);
                     if (rb.containsKey(key)) {
                         this.servletName = rb.getString(key);
diff --git 
a/webconsole/src/main/java/org/apache/felix/webconsole/VariableResolver.java 
b/webconsole/src/main/java/org/apache/felix/webconsole/VariableResolver.java
index 7065437a91..99ead1c58e 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/VariableResolver.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/VariableResolver.java
@@ -18,8 +18,8 @@
  */
 package org.apache.felix.webconsole;
 
+import org.apache.felix.webconsole.servlet.RequestVariableResolver;
 import org.osgi.annotation.versioning.ConsumerType;
-import org.osgi.annotation.versioning.ProviderType;
 
 /**
  * The <code>VariableResolver</code> interface defines the API for an object
@@ -41,8 +41,10 @@ import org.osgi.annotation.versioning.ProviderType;
  *
  * @see WebConsoleUtil#getVariableResolver(javax.servlet.ServletRequest)
  * @see WebConsoleUtil#setVariableResolver(javax.servlet.ServletRequest, 
VariableResolver)
+ * @deprecated Use {@link RequestVariableResolver} instead.
  */
 @ConsumerType
+@Deprecated
 public interface VariableResolver {
 
     /**
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 4831d9ff91..4992106561 100644
--- 
a/webconsole/src/main/java/org/apache/felix/webconsole/WebConsoleConstants.java
+++ 
b/webconsole/src/main/java/org/apache/felix/webconsole/WebConsoleConstants.java
@@ -18,6 +18,8 @@
  */
 package org.apache.felix.webconsole;
 
+import org.apache.felix.webconsole.servlet.RequestVariableResolver;
+
 /**
  * WebConsoleConstants provides some common constants that are used by plugin
  * developers.
@@ -173,9 +175,11 @@ public interface WebConsoleConstants
      * @see VariableResolver
      * @see WebConsoleUtil#getVariableResolver(javax.servlet.ServletRequest)
      * @see WebConsoleUtil#setVariableResolver(javax.servlet.ServletRequest, 
VariableResolver)
+     * @deprecated Use {@link RequestVariableResolver#REQUEST_ATTRIBUTE} 
instead
      * @since 3.0
      */
-    static final String ATTR_CONSOLE_VARIABLE_RESOLVER = 
"felix.webconsole.variable.resolver"; //$NON-NLS-1$
+    @Deprecated
+    static final String ATTR_CONSOLE_VARIABLE_RESOLVER = 
RequestVariableResolver.REQUEST_ATTRIBUTE;
 
     /**
      * The name of the request attribute holding the language {@link 
java.util.Map}
diff --git 
a/webconsole/src/main/java/org/apache/felix/webconsole/WebConsoleSecurityProvider2.java
 
b/webconsole/src/main/java/org/apache/felix/webconsole/WebConsoleSecurityProvider2.java
index 3a78b8aa9d..62b2d533ca 100644
--- 
a/webconsole/src/main/java/org/apache/felix/webconsole/WebConsoleSecurityProvider2.java
+++ 
b/webconsole/src/main/java/org/apache/felix/webconsole/WebConsoleSecurityProvider2.java
@@ -53,7 +53,7 @@ public interface WebConsoleSecurityProvider2 extends 
WebConsoleSecurityProvider
      * Authenticates the given request or asks the client for credentials.
      * <p>
      * Implementations of this method are expected to respect and implement
-     * the semantics of the <code>HttpContext.handleSecurity</code> method
+     * the semantics of the <code>ServletContextHelper.handleSecurity</code> 
method
      * as specified in the OSGi HTTP Service specification.
      * <p>
      * If this method returns <code>true</code> it is assumed the request
diff --git 
a/webconsole/src/main/java/org/apache/felix/webconsole/WebConsoleUtil.java 
b/webconsole/src/main/java/org/apache/felix/webconsole/WebConsoleUtil.java
index e48421e589..408859482b 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/WebConsoleUtil.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/WebConsoleUtil.java
@@ -38,6 +38,7 @@ import org.apache.commons.fileupload.FileUploadException;
 import org.apache.commons.fileupload.disk.DiskFileItemFactory;
 import org.apache.commons.fileupload.servlet.ServletFileUpload;
 import org.apache.commons.fileupload.servlet.ServletRequestContext;
+import org.apache.felix.webconsole.servlet.RequestVariableResolver;
 
 
 /**
@@ -72,23 +73,42 @@ public final class WebConsoleUtil
      * @param request The request whose attribute is returned (or set)
      *
      * @return The {@link VariableResolver} for the given request.
+     * @deprecated Use the {@link getRequestVariableResolver} instead.
      */
-    @SuppressWarnings("unchecked")
-    public static VariableResolver getVariableResolver( final ServletRequest 
request )
-    {
+    @Deprecated
+    public static VariableResolver getVariableResolver( final ServletRequest 
request ) {
         final Object resolverObj = request.getAttribute( 
WebConsoleConstants.ATTR_CONSOLE_VARIABLE_RESOLVER );
-        if ( resolverObj instanceof VariableResolver )
-        {
+        if ( resolverObj instanceof VariableResolver ) {
             return ( VariableResolver ) resolverObj;
         }
 
         final DefaultVariableResolver resolver = new DefaultVariableResolver();
-        resolver.put( "appRoot", request.getAttribute( 
WebConsoleConstants.ATTR_APP_ROOT ) );
-        resolver.put( "pluginRoot", request.getAttribute( 
WebConsoleConstants.ATTR_PLUGIN_ROOT ) );
+        resolver.put( "appRoot", (String) request.getAttribute( 
WebConsoleConstants.ATTR_APP_ROOT ) );
+        resolver.put( "pluginRoot", (String) request.getAttribute( 
WebConsoleConstants.ATTR_PLUGIN_ROOT ) );
         setVariableResolver( request, resolver );
         return resolver;
     }
 
+    /**
+     * Returns the {@link RequestVariableResolver} for the given request.
+     * <p>
+     * The resolver is added to the request attributes via the web console main
+     * servlet before it invokes any plugins.
+     * The preset properties are
+     * <code>appRoot</code> set to the value of the
+     * {@link WebConsoleConstants#ATTR_APP_ROOT} request attribute and
+     * <code>pluginRoot</code> set to the value of the
+     * {@link WebConsoleConstants#ATTR_PLUGIN_ROOT} request attribute.
+     * <p>
+     *
+     * @param request The request whose attribute is returned 
+     *
+     * @return The {@link RequestVariableResolver} for the given request.
+     * @since 3.5.0
+     */
+    public static RequestVariableResolver getRequestVariableResolver( final 
ServletRequest request) {
+        return (RequestVariableResolver) request.getAttribute( 
RequestVariableResolver.REQUEST_ATTRIBUTE );
+    }
 
     /**
      * Sets the {@link VariableResolver} as the
@@ -98,9 +118,10 @@ public final class WebConsoleUtil
      *
      * @param request The request whose attribute is set
      * @param resolver The {@link VariableResolver} to place into the request
+     * @deprecated Use the {@link RequestVaraibleResolver} instead.
      */
-    public static void setVariableResolver( final ServletRequest request, 
final VariableResolver resolver )
-    {
+    @Deprecated
+    public static void setVariableResolver( final ServletRequest request, 
final VariableResolver resolver )  {
         request.setAttribute( 
WebConsoleConstants.ATTR_CONSOLE_VARIABLE_RESOLVER, resolver );
     }
 
@@ -243,13 +264,13 @@ public final class WebConsoleUtil
      *
      * @param text the text to escape
      * @return the escaped text
+     * @deprecated It is better to use specialized encoders instead
      */
-    public static final String escapeHtml(String text)
-    {
+    @Deprecated
+    public static final String escapeHtml(String text) {
         StringBuilder sb = new StringBuilder(text.length() * 4 / 3);
         char ch, oldch = '_';
-        for (int i = 0; i < text.length(); i++)
-        {
+        for (int i = 0; i < text.length(); i++) {
             switch (ch = text.charAt(i))
             {
             case '<':
@@ -349,46 +370,36 @@ public final class WebConsoleUtil
      * @param value the value to convert
      * @return the string representation of the value
      */
-    public static final String toString(Object value)
-    {
-        if (value == null)
-        {
-            return "n/a"; //$NON-NLS-1$
-        }
-        else if (value.getClass().isArray())
-        {
+    public static final String toString(Object value) {
+        if (value == null) {
+            return "n/a";
+        } else if (value.getClass().isArray()) {
             final StringBuilder sb = new StringBuilder();
-            final int len = Array.getLength(value);
+            int len = Array.getLength(value);
             sb.append('[');
-            for (int i = 0; i < len; i++)
-            {
+
+            for(int i = 0; i < len; ++i) {
                 final Object element = Array.get(value, i);
-                if (element instanceof Byte)
-                {
-                    // convert byte[] to hex string
-                    sb.append("0x"); //$NON-NLS-1$
-                    final String x = Integer.toHexString(((Byte) 
element).intValue() & 0xff);
-                    if (1 == x.length())
-                    {
+                if (element instanceof Byte) {
+                    sb.append("0x");
+                    final String x = 
Integer.toHexString(((Byte)element).intValue() & 255);
+                    if (1 == x.length()) {
                         sb.append('0');
                     }
+
                     sb.append(x);
-                }
-                else
-                {
+                } else {
                     sb.append(toString(element));
                 }
 
-                if (i < len - 1)
-                {
-                    sb.append(", "); //$NON-NLS-1$
+                if (i < len - 1) {
+                    sb.append(", ");
                 }
             }
+
             return sb.append(']').toString();
-        }
-        else
-        {
+        } else {
             return value.toString();
         }
-    }
+     }
 }
diff --git 
a/webconsole/src/main/java/org/apache/felix/webconsole/i18n/LocalizationHelper.java
 
b/webconsole/src/main/java/org/apache/felix/webconsole/i18n/LocalizationHelper.java
index a29b6c1c04..b830e38caa 100644
--- 
a/webconsole/src/main/java/org/apache/felix/webconsole/i18n/LocalizationHelper.java
+++ 
b/webconsole/src/main/java/org/apache/felix/webconsole/i18n/LocalizationHelper.java
@@ -40,10 +40,10 @@ public class LocalizationHelper {
      *            the bundle that provides the localization resources. See the
      *            standard OSGi-type localization support.
      */
-    public LocalizationHelper(Bundle bundle) {
-       if (null == bundle)
-           throw new NullPointerException();
-       this.cache = new ResourceBundleCache(bundle);
+    public LocalizationHelper(final Bundle bundle) {
+           if (null == bundle)
+               throw new NullPointerException();
+           this.cache = new ResourceBundleCache(bundle);
     }
 
     /**
@@ -54,6 +54,6 @@ public class LocalizationHelper {
      * @return the resource bundle (could be empty, but never 
<code>null</code>)
      */
     public ResourceBundle getResourceBundle(final Locale locale) {
-       return cache.getResourceBundle(locale);
+           return cache.getResourceBundle(locale);
     }
 }
diff --git 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/ConfigManager.java
 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/ConfigManager.java
index d9ec9da064..416edf8627 100644
--- 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/ConfigManager.java
+++ 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/ConfigManager.java
@@ -35,6 +35,7 @@ import org.apache.felix.webconsole.WebConsoleUtil;
 import org.apache.felix.webconsole.internal.OsgiManagerPlugin;
 import org.apache.felix.webconsole.internal.Util;
 import org.apache.felix.webconsole.internal.misc.ServletSupport;
+import org.apache.felix.webconsole.servlet.RequestVariableResolver;
 import org.apache.felix.webconsole.spi.ConfigurationHandler;
 import org.apache.felix.webconsole.spi.ValidationException;
 import org.osgi.framework.BundleContext;
@@ -423,9 +424,9 @@ public class ConfigManager extends SimpleWebConsolePlugin 
implements OsgiManager
             cas.getJsonSupport().listFactoryConfigurations( jw, pidFilter, 
locale );
         }
         if ( !hasConfigs && !hasMetatype && cas != null ) {
-            jw.key("noconfigs").value(true); //$NON-NLS-1$
+            jw.key("noconfigs").value(true);
         } else {
-            jw.key("noconfigs").value(false); //$NON-NLS-1$
+            jw.key("noconfigs").value(false);
         }
 
         jw.endObject();
@@ -438,19 +439,19 @@ public class ConfigManager extends SimpleWebConsolePlugin 
implements OsgiManager
 
         // prepare variables
         final String referer = request.getParameter( REFERER );
-        final boolean factoryCreate = "true".equals( 
request.getParameter(FACTORY_CREATE) ); //$NON-NLS-1$
-        @SuppressWarnings("unchecked")
-        final Map<String, Object> vars = ( ( Map<String, Object> ) 
WebConsoleUtil.getVariableResolver( request ) );
-        vars.put( "__data__", json.toString() ); //$NON-NLS-1$
-        vars.put( "selectedPid", pid != null ? pid : "" ); //$NON-NLS-1$ 
//$NON-NLS-2$
-        vars.put( "configurationReferer", referer != null ? referer : "" ); 
//$NON-NLS-1$ //$NON-NLS-2$
-        vars.put( "factoryCreate", Boolean.valueOf(factoryCreate) ); 
//$NON-NLS-1$
-        vars.put( "param.apply", ACTION_APPLY ); //$NON-NLS-1$
-        vars.put( "param.create", ACTION_CREATE ); //$NON-NLS-1$
-        vars.put( "param.unbind", ACTION_UNBIND ); //$NON-NLS-1$
-        vars.put( "param.delete", ACTION_DELETE ); //$NON-NLS-1$
-        vars.put( "param.propertylist", PROPERTY_LIST ); //$NON-NLS-1$
-        vars.put( "param.pidFilter", PID_FILTER ); //$NON-NLS-1$
+        final boolean factoryCreate = "true".equals( 
request.getParameter(FACTORY_CREATE) );
+
+        final RequestVariableResolver vars = 
WebConsoleUtil.getRequestVariableResolver(request);
+        vars.put( "__data__", json.toString() ); 
+        vars.put( "selectedPid", pid != null ? pid : "" );
+        vars.put( "configurationReferer", referer != null ? referer : "" );
+        vars.put( "factoryCreate", Boolean.valueOf(factoryCreate) );
+        vars.put( "param.apply", ACTION_APPLY );
+        vars.put( "param.create", ACTION_CREATE );
+        vars.put( "param.unbind", ACTION_UNBIND );
+        vars.put( "param.delete", ACTION_DELETE );
+        vars.put( "param.propertylist", PROPERTY_LIST );
+        vars.put( "param.pidFilter", PID_FILTER );
 
         response.getWriter().print(TEMPLATE);
     }
diff --git 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/BundlesServlet.java
 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/BundlesServlet.java
index f0bebf8bd2..e564c24e28 100644
--- 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/BundlesServlet.java
+++ 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/BundlesServlet.java
@@ -57,7 +57,6 @@ import org.apache.felix.utils.json.JSONWriter;
 import org.apache.felix.utils.manifest.Clause;
 import org.apache.felix.utils.manifest.Parser;
 import org.apache.felix.webconsole.ConfigurationPrinter;
-import org.apache.felix.webconsole.DefaultVariableResolver;
 import org.apache.felix.webconsole.SimpleWebConsolePlugin;
 import org.apache.felix.webconsole.WebConsoleConstants;
 import org.apache.felix.webconsole.WebConsoleUtil;
@@ -65,6 +64,7 @@ import org.apache.felix.webconsole.bundleinfo.BundleInfo;
 import org.apache.felix.webconsole.bundleinfo.BundleInfoProvider;
 import org.apache.felix.webconsole.internal.OsgiManagerPlugin;
 import org.apache.felix.webconsole.internal.Util;
+import org.apache.felix.webconsole.servlet.RequestVariableResolver;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleException;
@@ -75,12 +75,14 @@ import org.osgi.framework.ServiceReference;
 import org.osgi.framework.ServiceRegistration;
 import org.osgi.framework.Version;
 import org.osgi.framework.VersionRange;
+import org.osgi.framework.startlevel.BundleStartLevel;
+import org.osgi.framework.startlevel.FrameworkStartLevel;
+import org.osgi.framework.wiring.BundleRevision;
 import org.osgi.framework.wiring.FrameworkWiring;
 import org.osgi.service.cm.ConfigurationAdmin;
 import org.osgi.service.log.LogService;
 import org.osgi.service.packageadmin.ExportedPackage;
 import org.osgi.service.packageadmin.PackageAdmin;
-import org.osgi.service.startlevel.StartLevel;
 import org.osgi.util.tracker.ServiceTracker;
 import org.osgi.util.tracker.ServiceTrackerCustomizer;
 
@@ -518,16 +520,17 @@ public class BundlesServlet extends 
SimpleWebConsolePlugin implements OsgiManage
      * @see 
org.apache.felix.webconsole.AbstractWebConsolePlugin#renderContent(javax.servlet.http.HttpServletRequest,
 javax.servlet.http.HttpServletResponse)
      */
     @Override
-    @SuppressWarnings("unchecked")
     protected void renderContent( HttpServletRequest request, 
HttpServletResponse response ) throws IOException
     {
         // get request info from request attribute
         final RequestInfo reqInfo = getRequestInfo(request);
 
-        final int startLevel = getStartLevel().getInitialBundleStartLevel();
+        final Bundle systemBundle = 
this.getBundleContext().getBundle(Constants.SYSTEM_BUNDLE_LOCATION);
+        final FrameworkStartLevel fsl = 
systemBundle.adapt(FrameworkStartLevel.class);
+        final int startLevel = fsl.getInitialBundleStartLevel();
 
         // prepare variables
-        DefaultVariableResolver vars = ( ( DefaultVariableResolver ) 
WebConsoleUtil.getVariableResolver( request ) );
+        final RequestVariableResolver vars = 
WebConsoleUtil.getRequestVariableResolver(request);
         vars.put( "startLevel", String.valueOf(startLevel));
         vars.put( "drawDetails", reqInfo.bundleRequested ? Boolean.TRUE : 
Boolean.FALSE );
         vars.put( "currentBundle", (reqInfo.bundleRequested && reqInfo.bundle 
!= null ? String.valueOf(reqInfo.bundle.getBundleId()) : "null"));
@@ -755,15 +758,13 @@ public class BundlesServlet extends 
SimpleWebConsolePlugin implements OsgiManage
     }
 
 
-    private final boolean isFragmentBundle( Bundle bundle )
-    {
+    private final boolean isFragmentBundle(final Bundle bundle ) {
         // Workaround for FELIX-3670
-        if ( bundle.getState() == Bundle.UNINSTALLED )
-        {
+        if ( bundle.getState() == Bundle.UNINSTALLED ) {
             return bundle.getHeaders().get( Constants.FRAGMENT_HOST ) != null;
         }
-
-        return getPackageAdmin().getBundleType( bundle ) == 
PackageAdmin.BUNDLE_TYPE_FRAGMENT;
+        final BundleRevision rev = bundle.adapt(BundleRevision.class);
+        return rev != null && (rev.getTypes() & BundleRevision.TYPE_FRAGMENT) 
== BundleRevision.TYPE_FRAGMENT;
     }
 
     private void keyVal(final List<Map<String, Object>> props, final String 
key, final Object val)
@@ -865,14 +866,11 @@ public class BundlesServlet extends 
SimpleWebConsolePlugin implements OsgiManage
     }
 
 
-    private final Integer getStartLevel( Bundle bundle )
-    {
-        if ( bundle.getState() != Bundle.UNINSTALLED )
-        {
-            StartLevel sl = getStartLevel();
-            if ( sl != null )
-            {
-                return sl.getBundleStartLevel( bundle );
+    private final Integer getStartLevel( Bundle bundle ) {
+        if ( bundle.getState() != Bundle.UNINSTALLED ){
+            final BundleStartLevel bsl = bundle.adapt( BundleStartLevel.class 
);
+            if (bsl != null ) {
+                return bsl.getStartLevel();
             }
         }
 
@@ -881,8 +879,7 @@ public class BundlesServlet extends SimpleWebConsolePlugin 
implements OsgiManage
     }
 
 
-    private void listImportExport( List<Map<String, Object>> props, Bundle 
bundle, final String pluginRoot )
-    {
+    private void listImportExport( List<Map<String, Object>> props, Bundle 
bundle, final String pluginRoot ) {
         PackageAdmin packageAdmin = getPackageAdmin();
         if ( packageAdmin == null )
         {
@@ -1467,12 +1464,6 @@ public class BundlesServlet extends 
SimpleWebConsolePlugin implements OsgiManage
         return ( PackageAdmin ) getService( PackageAdmin.class.getName() );
     }
 
-    private final StartLevel getStartLevel()
-    {
-        return ( StartLevel ) getService( StartLevel.class.getName() );
-    }
-
-
     //---------- Bundle Installation handler (former InstallAction)
 
     private void installBundles( final HttpServletRequest request )
diff --git 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/PermissionsConfigurationPrinter.java
 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/PermissionsConfigurationPrinter.java
index 1701350edb..f1083874e0 100644
--- 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/PermissionsConfigurationPrinter.java
+++ 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/PermissionsConfigurationPrinter.java
@@ -59,8 +59,8 @@ public final class PermissionsConfigurationPrinter extends 
AbstractConfiguration
     public final void printConfiguration(PrintWriter pw)
     {
         final BundleContext bc = getBundleContext();
-        final ServiceReference paRef = bc.getServiceReference( 
PERMISSION_ADMIN_NAME );
-        final ServiceReference cpaRef = bc.getServiceReference( 
CONDITIONAL_PERMISSION_ADMIN_NAME );
+        final ServiceReference<?> paRef = bc.getServiceReference( 
PERMISSION_ADMIN_NAME );
+        final ServiceReference<?> cpaRef = bc.getServiceReference( 
CONDITIONAL_PERMISSION_ADMIN_NAME );
         final Object paSvc = paRef != null ? bc.getService(paRef) : null;
         final Object cpaSvc = cpaRef != null ? bc.getService(cpaRef) : null;
 
@@ -112,7 +112,7 @@ public final class PermissionsConfigurationPrinter extends 
AbstractConfiguration
                 boolean hasPermissions = false;
                 //final java.util.List list = 
cpa.newConditionalPermissionUpdate().getConditionalPermissionInfos();
                 //for (int i = 0; list != null && i < list.size(); i++)
-                for (Enumeration e = cpa.getConditionalPermissionInfos(); 
e.hasMoreElements();)
+                for (Enumeration<ConditionalPermissionInfo> e = 
cpa.getConditionalPermissionInfos(); e.hasMoreElements();)
                 {
                     hasPermissions = true;
                     //final ConditionalPermissionInfo info = 
(ConditionalPermissionInfo) list.get(i);
diff --git 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/ServicesConfigurationPrinter.java
 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/ServicesConfigurationPrinter.java
index 56d11870d4..11af2fcc19 100644
--- 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/ServicesConfigurationPrinter.java
+++ 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/ServicesConfigurationPrinter.java
@@ -47,7 +47,7 @@ public class ServicesConfigurationPrinter extends 
AbstractConfigurationPrinter
         "  Using Bundle {0} - {1} ({2}), version {3}");
 
     // don't create empty reference array all the time, create it only once - 
it is immutable
-    private static final ServiceReference[] NO_REFS = new ServiceReference[0];
+    private static final ServiceReference<?>[] NO_REFS = new 
ServiceReference[0];
 
     /**
      * @see org.apache.felix.webconsole.ConfigurationPrinter#getTitle()
@@ -65,7 +65,7 @@ public class ServicesConfigurationPrinter extends 
AbstractConfigurationPrinter
     public final void printConfiguration(PrintWriter pw)
     {
         final Object[] data = new Object[4]; // used as message formatter 
parameters
-        final ServiceReference refs[] = getServices();
+        final ServiceReference<?> refs[] = getServices();
         pw.print("Status: ");
         pw.println(ServicesServlet.getStatusLine(refs));
 
@@ -118,7 +118,7 @@ public class ServicesConfigurationPrinter extends 
AbstractConfigurationPrinter
         return data;
     }
 
-    private static final Object[] params(ServiceReference ref, Object[] data)
+    private static final Object[] params(ServiceReference<?> ref, Object[] 
data)
     {
         data[0] = ServicesServlet.propertyAsString(ref, Constants.SERVICE_ID);
         data[1] = ServicesServlet.propertyAsString(ref, Constants.OBJECTCLASS);
@@ -127,64 +127,20 @@ public class ServicesConfigurationPrinter extends 
AbstractConfigurationPrinter
         return data;
     }
 
-    private final ServiceReference[] getServices()
-    {
-        ServiceReference[] refs = null;
-        try
-        {
+    private final ServiceReference<?>[] getServices() {
+        ServiceReference<?>[] refs = null;
+        try {
             refs = 
BundleContextUtil.getWorkingBundleContext(getBundleContext()).getAllServiceReferences(null,
 null);
-        }
-        catch (InvalidSyntaxException e)
-        {
+        } catch (InvalidSyntaxException e) {
             // ignore
         }
 
         // no services or invalid filter syntax (unlikely)
-        if (refs != null)
-        {
-            Arrays.sort(refs, new ServiceReferenceComparator());
-        }
-        else
-        {
+        if (refs != null) {
+            Arrays.sort(refs);
+        } else {
             refs = NO_REFS;
         }
         return refs;
     }
-
-}
-
-class ServiceReferenceComparator implements Comparator
-{
-    private static final Long ZERO = new Long(0);
-
-    public int compare(ServiceReference p1, ServiceReference p2)
-    {
-        Long id1 = null;
-        if (p1 != null)
-        {
-            id1 = (Long) p1.getProperty(Constants.SERVICE_ID);
-        }
-        if (id1 == null)
-        {
-            id1 = ZERO;
-        }
-
-        Long id2 = null;
-        if (p2 != null)
-        {
-            id2 = (Long) p2.getProperty(Constants.SERVICE_ID);
-        }
-        if (id2 == null)
-        {
-            id2 = ZERO;
-        }
-
-        return id1.compareTo(id2);
-    }
-
-    @Override
-    public int compare(Object o1, Object o2)
-    {
-        return compare((ServiceReference) o1, (ServiceReference) o2);
-    }
 }
diff --git 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/ServicesServlet.java
 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/ServicesServlet.java
index 52a0983835..4aeea6b73f 100644
--- 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/ServicesServlet.java
+++ 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/ServicesServlet.java
@@ -30,18 +30,19 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import org.apache.felix.utils.json.JSONWriter;
-import org.apache.felix.webconsole.DefaultVariableResolver;
 import org.apache.felix.webconsole.SimpleWebConsolePlugin;
 import org.apache.felix.webconsole.WebConsoleConstants;
 import org.apache.felix.webconsole.WebConsoleUtil;
 import org.apache.felix.webconsole.internal.OsgiManagerPlugin;
 import org.apache.felix.webconsole.internal.Util;
+import org.apache.felix.webconsole.servlet.RequestVariableResolver;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
 import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.framework.ServiceReference;
 import org.osgi.framework.ServiceRegistration;
+import org.owasp.encoder.Encode;
 
 
 /**
@@ -50,12 +51,12 @@ import org.osgi.framework.ServiceRegistration;
 public class ServicesServlet extends SimpleWebConsolePlugin implements 
OsgiManagerPlugin
 {
     // don't create empty reference array all the time, create it only once - 
it is immutable
-    private static final ServiceReference[] NO_REFS = new ServiceReference[0];
+    private static final ServiceReference<?>[] NO_REFS = new 
ServiceReference[0];
 
     private final class RequestInfo
     {
         public final String extension;
-        public final ServiceReference service;
+        public final ServiceReference<?> service;
         public final boolean serviceRequested;
 
 
@@ -404,8 +405,7 @@ public class ServicesServlet extends SimpleWebConsolePlugin 
implements OsgiManag
     /**
      * @see 
org.apache.felix.webconsole.AbstractWebConsolePlugin#renderContent(javax.servlet.http.HttpServletRequest,
 javax.servlet.http.HttpServletResponse)
      */
-    protected void renderContent( HttpServletRequest request, 
HttpServletResponse response ) throws IOException
-    {
+    protected void renderContent( HttpServletRequest request, 
HttpServletResponse response ) throws IOException {
         // get request info from request attribute
         final RequestInfo reqInfo = getRequestInfo( request );
 
@@ -415,11 +415,11 @@ public class ServicesServlet extends 
SimpleWebConsolePlugin implements OsgiManag
         writeJSON(w, reqInfo.service, request.getLocale(), filter);
 
         // prepare variables
-        DefaultVariableResolver vars = ( ( DefaultVariableResolver ) 
WebConsoleUtil.getVariableResolver( request ) );
+        final RequestVariableResolver vars = 
WebConsoleUtil.getRequestVariableResolver(request);
         vars.put( "bundlePath", appRoot +  "/" + BundlesServlet.NAME + "/" );
         vars.put( "drawDetails", String.valueOf(reqInfo.serviceRequested));
         vars.put( "__data__", w.toString() );
-        vars.put( "filter", filter == null ? "" : 
WebConsoleUtil.escapeHtml(filter));
+        vars.put( "filter", filter == null ? "" : 
Encode.forHtmlContent(filter) );
 
         response.getWriter().print( TEMPLATE );
     }
diff --git 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/ServicesUsedInfoProvider.java
 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/ServicesUsedInfoProvider.java
index 8ab35886d9..2139a6117c 100644
--- 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/ServicesUsedInfoProvider.java
+++ 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/ServicesUsedInfoProvider.java
@@ -61,7 +61,7 @@ final class ServicesUsedInfoProvider implements 
BundleInfoProvider
 
     public BundleInfo[] getBundleInfo( Bundle bundle, String webConsoleRoot, 
Locale locale )
     {
-        final ServiceReference[] refs = bundle.getServicesInUse();
+        final ServiceReference<?>[] refs = bundle.getServicesInUse();
         if ( null == refs || refs.length == 0 )
             return NO_INFO;
 
@@ -74,7 +74,7 @@ final class ServicesUsedInfoProvider implements 
BundleInfoProvider
     }
 
 
-    private BundleInfo toInfo( ServiceReference ref, String webConsoleRoot, 
Locale locale )
+    private BundleInfo toInfo( ServiceReference<?> ref, String webConsoleRoot, 
Locale locale )
     {
         final String[] classes = ( String[] ) ref.getProperty( 
Constants.OBJECTCLASS );
         final Object id = ref.getProperty( Constants.SERVICE_ID );
@@ -91,9 +91,8 @@ final class ServicesUsedInfoProvider implements 
BundleInfoProvider
     }
 
 
-    ServiceRegistration register( BundleContext context )
-    {
-        return context.registerService( BundleInfoProvider.class.getName(), 
this, null );
+    ServiceRegistration<BundleInfoProvider> register( BundleContext context ) {
+        return context.registerService( BundleInfoProvider.class, this, null );
     }
 
 }
diff --git 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/filter/FilteringResponseWrapper.java
 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/filter/FilteringResponseWrapper.java
index 02880a6bf7..6b6d41b279 100644
--- 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/filter/FilteringResponseWrapper.java
+++ 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/filter/FilteringResponseWrapper.java
@@ -27,8 +27,8 @@ import javax.servlet.ServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpServletResponseWrapper;
 
-import org.apache.felix.webconsole.VariableResolver;
 import org.apache.felix.webconsole.WebConsoleUtil;
+import org.apache.felix.webconsole.servlet.RequestVariableResolver;
 
 
 /**
@@ -86,8 +86,8 @@ public class FilteringResponseWrapper extends 
HttpServletResponseWrapper
             final PrintWriter base = super.getWriter();
             if ( doWrap() )
             {
-                final VariableResolver resolver = 
WebConsoleUtil.getVariableResolver( request );
-                final ResourceFilteringWriter filter = new 
ResourceFilteringWriter( base, locale, resolver );
+                final RequestVariableResolver vars = 
WebConsoleUtil.getRequestVariableResolver(request);
+                final ResourceFilteringWriter filter = new 
ResourceFilteringWriter( base, locale, vars );
                 writer = new PrintWriter( filter );
             }
             else
diff --git 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/filter/ResourceFilteringWriter.java
 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/filter/ResourceFilteringWriter.java
index f9e2aa6560..23c34d116a 100644
--- 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/filter/ResourceFilteringWriter.java
+++ 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/filter/ResourceFilteringWriter.java
@@ -25,8 +25,7 @@ import java.io.Writer;
 import java.util.MissingResourceException;
 import java.util.ResourceBundle;
 
-import org.apache.felix.webconsole.DefaultVariableResolver;
-import org.apache.felix.webconsole.VariableResolver;
+import org.apache.felix.webconsole.servlet.RequestVariableResolver;
 
 
 /**
@@ -75,7 +74,7 @@ class ResourceFilteringWriter extends FilterWriter
      */
     private final ResourceBundle locale;
 
-    private final VariableResolver variables;
+    private final RequestVariableResolver variables;
 
     /**
      * The buffer to gather the text to be translated
@@ -88,11 +87,11 @@ class ResourceFilteringWriter extends FilterWriter
     private int state = STATE_NULL;
 
 
-    ResourceFilteringWriter( final Writer out, final ResourceBundle locale, 
final VariableResolver variables )
+    ResourceFilteringWriter( final Writer out, final ResourceBundle locale, 
final RequestVariableResolver variables )
     {
         super( out );
         this.locale = locale;
-        this.variables = ( variables != null ) ? variables : new 
DefaultVariableResolver();
+        this.variables = variables;
     }
 
 
diff --git 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/i18n/CombinedEnumeration.java
 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/i18n/CombinedEnumeration.java
index 5d0441b1c9..b9947a6231 100644
--- 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/i18n/CombinedEnumeration.java
+++ 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/i18n/CombinedEnumeration.java
@@ -32,28 +32,28 @@ import java.util.Set;
  * So if both enumerations would produce the same result, say "123", only the
  * first would be returned.
  */
-class CombinedEnumeration implements Enumeration
+class CombinedEnumeration implements Enumeration<String>
 {
 
     // the first enumeration to iterate
-    private final Enumeration first;
+    private final Enumeration<String> first;
 
     // the second enumeration to iterate once the first is exhausted
-    private final Enumeration second;
+    private final Enumeration<String> second;
 
     // the set of values already returned to prevent duplicate entries
-    private final Set seenKeys;
+    private final Set<String> seenKeys;
 
     // preview to the next return value for nextElement(), null at the end
-    private Object nextKey;
+    private String nextKey;
 
 
-    CombinedEnumeration( final Enumeration first, final Enumeration second )
+    CombinedEnumeration( final Enumeration<String> first, final 
Enumeration<String> second )
     {
         this.first = first;
         this.second = second;
 
-        this.seenKeys = new HashSet();
+        this.seenKeys = new HashSet<>();
         this.nextKey = seek();
     }
 
@@ -64,14 +64,14 @@ class CombinedEnumeration implements Enumeration
     }
 
 
-    public Object nextElement()
+    public String nextElement()
     {
         if ( !hasMoreElements() )
         {
             throw new NoSuchElementException();
         }
 
-        Object result = nextKey;
+        String result = nextKey;
         nextKey = seek();
         return result;
     }
@@ -82,11 +82,11 @@ class CombinedEnumeration implements Enumeration
      * (unique) element is available, null is returned. The element returned
      * is also added to the set of seen elements to prevent duplicate provision
      */
-    private Object seek()
+    private String seek()
     {
         while ( first.hasMoreElements() )
         {
-            final Object next = first.nextElement();
+            final String next = first.nextElement();
             if ( !seenKeys.contains( next ) )
             {
                 seenKeys.add( next );
@@ -95,7 +95,7 @@ class CombinedEnumeration implements Enumeration
         }
         while ( second.hasMoreElements() )
         {
-            final Object next = second.nextElement();
+            final String next = second.nextElement();
             if ( !seenKeys.contains( next ) )
             {
                 seenKeys.add( next );
diff --git 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/i18n/CombinedResourceBundle.java
 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/i18n/CombinedResourceBundle.java
index eafc68eecf..19130c6fc5 100644
--- 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/i18n/CombinedResourceBundle.java
+++ 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/i18n/CombinedResourceBundle.java
@@ -48,7 +48,7 @@ class CombinedResourceBundle extends ResourceBundle
     }
 
 
-    public Enumeration getKeys()
+    public Enumeration<String> getKeys()
     {
         return new CombinedEnumeration( resourceBundle.getKeys(), 
defaultResourceBundle.getKeys() );
     }
diff --git 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/i18n/ConsolePropertyResourceBundle.java
 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/i18n/ConsolePropertyResourceBundle.java
index c1d389b3e6..82d028bfd3 100644
--- 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/i18n/ConsolePropertyResourceBundle.java
+++ 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/i18n/ConsolePropertyResourceBundle.java
@@ -27,14 +27,11 @@ import java.util.Properties;
 import java.util.ResourceBundle;
 
 
-class ConsolePropertyResourceBundle extends ResourceBundle
-{
+class ConsolePropertyResourceBundle extends ResourceBundle {
 
     private final Properties props;
 
-
-    ConsolePropertyResourceBundle( final ResourceBundle parent, final URL 
source )
-    {
+    ConsolePropertyResourceBundle( final ResourceBundle parent, final URL 
source ) {
         setParent( parent );
 
         props = new Properties();
@@ -47,21 +44,18 @@ class ConsolePropertyResourceBundle extends ResourceBundle
         }
     }
 
-
-    public Enumeration getKeys()
-    {
-        Enumeration keysEnum = null;
-        if (parent == null) {
-            keysEnum = props.keys();
-        } else {
-            keysEnum = new CombinedEnumeration( props.keys(), parent.getKeys() 
);
+    @Override
+    public Enumeration<String> getKeys() {
+        @SuppressWarnings({"unchecked", "rawtypes"})
+        Enumeration<String> keysEnum = (Enumeration) props.keys();
+        if (parent != null) {
+            keysEnum = new CombinedEnumeration( keysEnum, parent.getKeys() );
         }
         return keysEnum;
     }
 
-
-    protected Object handleGetObject( String key )
-    {
+    @Override
+    protected Object handleGetObject( String key ) {
         return props.get( key );
     }
 }
diff --git 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/i18n/ResourceBundleCache.java
 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/i18n/ResourceBundleCache.java
index 9962d209c5..19bb78d1c4 100644
--- 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/i18n/ResourceBundleCache.java
+++ 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/i18n/ResourceBundleCache.java
@@ -33,8 +33,7 @@ import org.osgi.framework.Constants;
 /**
  * The <code>ResourceBundleCache</code> caches resource bundles per OSGi 
bundle.
  */
-public class ResourceBundleCache
-{
+public class ResourceBundleCache {
 
     /**
      * The default locale corresponding to the default language in the
@@ -46,9 +45,9 @@ public class ResourceBundleCache
 
     private final Bundle bundle;
 
-    private final Map resourceBundles;
+    private final Map<Locale, ResourceBundle> resourceBundles;
 
-    private Map resourceBundleEntries;
+    private volatile Map<String, URL> resourceBundleEntries;
 
 
     /**
@@ -56,10 +55,9 @@ public class ResourceBundleCache
      * 
      * @param bundle the bundle which resources should be loaded.
      */
-    public ResourceBundleCache( final Bundle bundle )
-    {
+    public ResourceBundleCache( final Bundle bundle ) {
         this.bundle = bundle;
-        this.resourceBundles = new HashMap();
+        this.resourceBundles = new HashMap<>();
     }
 
 
@@ -69,10 +67,8 @@ public class ResourceBundleCache
      * @param locale the requested locale
      * @return the resource bundle for the requested locale
      */
-    public ResourceBundle getResourceBundle( final Locale locale )
-    {
-        if ( locale == null )
-        {
+    public ResourceBundle getResourceBundle( final Locale locale ) {
+        if ( locale == null ) {
             return getResourceBundleInternal( DEFAULT_LOCALE );
         }
 
@@ -80,26 +76,21 @@ public class ResourceBundleCache
     }
 
 
-    ResourceBundle getResourceBundleInternal( final Locale locale )
-    {
-        if ( locale == null )
-        {
+    ResourceBundle getResourceBundleInternal( final Locale locale ) {
+        if ( locale == null ) {
             return null;
         }
 
-        synchronized ( resourceBundles )
-        {
-            ResourceBundle bundle = ( ResourceBundle ) resourceBundles.get( 
locale );
-            if ( bundle != null )
-            {
+        synchronized ( resourceBundles ) {
+            ResourceBundle bundle = resourceBundles.get( locale );
+            if ( bundle != null ) {
                 return bundle;
             }
         }
 
         ResourceBundle parent = getResourceBundleInternal( getParentLocale( 
locale ) );
         ResourceBundle bundle = loadResourceBundle( parent, locale );
-        synchronized ( resourceBundles )
-        {
+        synchronized ( resourceBundles ) {
             resourceBundles.put( locale, bundle );
         }
 
@@ -107,43 +98,36 @@ public class ResourceBundleCache
     }
 
 
-    private ResourceBundle loadResourceBundle( final ResourceBundle parent, 
final Locale locale )
-    {
-        final String path = "_" + locale.toString(); //$NON-NLS-1$
-        final URL source = ( URL ) getResourceBundleEntries().get( path );
+    private ResourceBundle loadResourceBundle( final ResourceBundle parent, 
final Locale locale ) {
+        final String path = "_" + locale.toString();
+        final URL source = getResourceBundleEntries().get( path );
         return new ConsolePropertyResourceBundle( parent, source );
     }
 
 
-    private synchronized Map getResourceBundleEntries()
-    {
-        if ( this.resourceBundleEntries == null )
-        {
+    private synchronized Map<String, URL> getResourceBundleEntries() {
+        if ( this.resourceBundleEntries == null ) {
             String file = ( String ) bundle.getHeaders().get( 
Constants.BUNDLE_LOCALIZATION );
-            if ( file == null )
-            {
+            if ( file == null ) {
                 file = Constants.BUNDLE_LOCALIZATION_DEFAULT_BASENAME;
             }
 
             // remove leading slash
-            if ( file.startsWith( "/" ) ) //$NON-NLS-1$
-            {
+            if ( file.startsWith( "/" ) ) {
                 file = file.substring( 1 );
             }
 
             // split path and base name
             int slash = file.lastIndexOf( '/' );
             String fileName = file.substring( slash + 1 );
-            String path = ( slash <= 0 ) ? "/" : file.substring( 0, slash ); 
//$NON-NLS-1$
+            String path = ( slash <= 0 ) ? "/" : file.substring( 0, slash );
 
-            HashMap resourceBundleEntries = new HashMap();
+            HashMap<String, URL> resourceBundleEntries = new HashMap<>();
 
-            Enumeration locales = bundle.findEntries( path, fileName + 
"*.properties", false ); //$NON-NLS-1$
-            if ( locales != null )
-            {
-                while ( locales.hasMoreElements() )
-                {
-                    URL entry = ( URL ) locales.nextElement();
+            Enumeration<URL> locales = bundle.findEntries( path, fileName + 
"*.properties", false );
+            if ( locales != null ) {
+                while ( locales.hasMoreElements() ) {
+                    URL entry = locales.nextElement();
 
                     // calculate the key
                     String entryPath = entry.getPath();
@@ -154,7 +138,7 @@ public class ResourceBundleCache
                     // the default language is "name.properties" thus the entry
                     // path is empty and must default to "_"+DEFAULT_LOCALE
                     if (entryPath.length() == 0) {
-                        entryPath = "_" + DEFAULT_LOCALE; //$NON-NLS-1$
+                        entryPath = "_" + DEFAULT_LOCALE;
                     }
 
                     // only add this entry, if the "language" is not provided
@@ -172,23 +156,16 @@ public class ResourceBundleCache
     }
 
 
-    private static final Locale getParentLocale( Locale locale )
-    {
-        if ( locale.getVariant().length() != 0 )
-        {
+    private static final Locale getParentLocale( Locale locale ) {
+        if ( locale.getVariant().length() != 0 ) {
             return new Locale( locale.getLanguage(), locale.getCountry() );
-        }
-        else if ( locale.getCountry().length() != 0 )
-        {
-            return new Locale( locale.getLanguage(), "" ); //$NON-NLS-1$
-        }
-        else if ( !locale.getLanguage().equals( DEFAULT_LOCALE.getLanguage() ) 
)
-        {
+        } else if ( locale.getCountry().length() != 0 ) {
+            return new Locale( locale.getLanguage(), "" );
+        } else if ( !locale.getLanguage().equals( DEFAULT_LOCALE.getLanguage() 
) ) {
             return DEFAULT_LOCALE;
         }
 
         // no more parents
         return null;
     }
-
 }
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 6503cdc93d..dc12107f87 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
@@ -42,11 +42,11 @@ import javax.servlet.http.HttpServletResponse;
 import org.apache.felix.utils.json.JSONWriter;
 import org.apache.felix.utils.manifest.Clause;
 import org.apache.felix.utils.manifest.Parser;
-import org.apache.felix.webconsole.DefaultVariableResolver;
 import org.apache.felix.webconsole.SimpleWebConsolePlugin;
 import org.apache.felix.webconsole.WebConsoleUtil;
 import org.apache.felix.webconsole.internal.OsgiManagerPlugin;
 import org.apache.felix.webconsole.internal.Util;
+import org.apache.felix.webconsole.servlet.RequestVariableResolver;
 import org.osgi.framework.Bundle;
 
 
@@ -109,14 +109,13 @@ public final class LicenseServlet extends 
SimpleWebConsolePlugin implements Osgi
     /**
      * @see 
org.apache.felix.webconsole.AbstractWebConsolePlugin#renderContent(javax.servlet.http.HttpServletRequest,
 javax.servlet.http.HttpServletResponse)
      */
-    @SuppressWarnings("unchecked")
     protected void renderContent( HttpServletRequest request, 
HttpServletResponse res ) throws IOException
     {
         Bundle[] bundles = getBundleContext().getBundles();
         Util.sort( bundles, request.getLocale() );
 
         // prepare variables
-        DefaultVariableResolver vars = ( ( DefaultVariableResolver ) 
WebConsoleUtil.getVariableResolver( request ) );
+        final RequestVariableResolver vars = 
WebConsoleUtil.getRequestVariableResolver(request);
         vars.put( "__data__", getBundleData( bundles, request.getLocale() ));
 
         res.getWriter().print(TEMPLATE);
diff --git 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/SystemPropertiesPrinter.java
 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/SystemPropertiesPrinter.java
index dbd71c6963..43183f7aba 100644
--- 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/SystemPropertiesPrinter.java
+++ 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/SystemPropertiesPrinter.java
@@ -28,8 +28,7 @@ import java.util.TreeSet;
 import org.apache.felix.webconsole.internal.AbstractConfigurationPrinter;
 
 
-public class SystemPropertiesPrinter extends AbstractConfigurationPrinter
-{
+public class SystemPropertiesPrinter extends AbstractConfigurationPrinter {
 
     private static final String TITLE = "System Properties";
 
diff --git 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/JakartaServletAdapter.java
 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/JakartaServletAdapter.java
new file mode 100644
index 0000000000..458d5d0498
--- /dev/null
+++ 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/JakartaServletAdapter.java
@@ -0,0 +1,303 @@
+/*
+ * 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.util.*;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.felix.http.jakartawrappers.HttpServletRequestWrapper;
+import org.apache.felix.http.jakartawrappers.HttpServletResponseWrapper;
+import org.apache.felix.http.jakartawrappers.ServletConfigWrapper;
+import org.apache.felix.http.javaxwrappers.ServletExceptionUtil;
+import org.apache.felix.webconsole.AbstractWebConsolePlugin;
+import org.apache.felix.webconsole.servlet.AbstractServlet;
+import org.apache.felix.webconsole.servlet.ServletConstants;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * The <code>JakartaServletAdapter</code> is an adapter to the
+ * {@link AbstractWebConsolePlugin} for regular servlets registered with the
+ * {@link org.apache.felix.webconsole.WebConsoleConstants#PLUGIN_TITLE}
+ * service attribute using jakarta.servlet.Servlet
+ */
+public class JakartaServletAdapter extends AbstractWebConsolePlugin {
+
+    /** serial UID */
+    private static final long serialVersionUID = 1L;
+
+    // the plugin label (aka address)
+    private final String label;
+
+    // the plugin title
+    private final String title;
+
+    // the actual plugin to forward rendering requests to
+    private final AbstractServlet plugin;
+
+    // the CSS references (null if none)
+    private final String[] cssReferences;
+
+    /**
+     * Creates a new wrapper for a Web Console Plugin
+     *
+     * @param label the label
+     * @param plugin the plugin itself
+     * @param serviceReference reference to the plugin
+     */
+    public JakartaServletAdapter( final AbstractServlet plugin, 
ServiceReference<Servlet> serviceReference ) {
+        this.label = (String) serviceReference.getProperty( 
ServletConstants.PLUGIN_LABEL );
+        this.title = (String) serviceReference.getProperty( 
ServletConstants.PLUGIN_TITLE );
+        this.plugin = plugin;
+        this.cssReferences = toStringArray( serviceReference.getProperty( 
ServletConstants.PLUGIN_CSS_REFERENCES ) );
+
+        // activate this abstract plugin (mainly to set the bundle context)
+        activate( serviceReference.getBundle().getBundleContext() );
+    }
+
+
+    //---------- AbstractWebConsolePlugin API
+
+    @Override
+    public String getLabel() {
+        return label;
+    }
+
+    @Override
+    public String getTitle() {
+        return this.title;
+    }
+
+    @Override
+    protected String[] getCssReferences() {
+        return cssReferences;
+    }
+
+    @Override
+    protected void renderContent( final HttpServletRequest req, final 
HttpServletResponse res )
+    throws ServletException, IOException {
+        try {
+            plugin.renderContent( 
(jakarta.servlet.http.HttpServletRequest)HttpServletRequestWrapper.getWrapper(req),
 
+                
(jakarta.servlet.http.HttpServletResponse)HttpServletResponseWrapper.getWrapper(res)
 );
+        } catch (final jakarta.servlet.ServletException s) {
+            throw ServletExceptionUtil.getServletException(s);
+        }
+    }
+
+    /**
+     * Returns the registered plugin class to be able to call the
+     * <code>getResource()</code> method on that object for this plugin to
+     * provide additional resources.
+     *
+     * @see 
org.apache.felix.webconsole.AbstractWebConsolePlugin#getResourceProvider()
+     */
+    protected Object getResourceProvider() {
+        return plugin;
+    }
+
+
+    //---------- Servlet API overwrite
+
+    /**
+     * Initializes this servlet as well as the plugin servlet.
+     *
+     * @see javax.servlet.GenericServlet#init(javax.servlet.ServletConfig)
+     */
+    public void init( ServletConfig config ) throws ServletException {
+        // no need to activate the plugin, this has already been done
+        // when the instance was setup
+        try {
+            // base classe initialization
+            super.init( config );
+
+            // plugin initialization
+            try {
+                plugin.init( new ServletConfigWrapper(config) );
+            } catch ( final jakarta.servlet.ServletException s) {
+                throw ServletExceptionUtil.getServletException(s);
+            }
+        } catch ( ServletException se ) {
+            // if init fails, the plugin will not be destroyed and thus
+            // the plugin not deactivated. Do it here
+            deactivate();
+
+            // rethrow the exception
+            throw se;
+        }
+    }
+
+    /**
+     * Detects whether this request is intended to have the headers and
+     * footers of this plugin be rendered or not. The decision is taken based
+     * on whether and what extension the request URI has: If the request URI
+     * has no extension or the the extension is <code>.html</code>, the request
+     * is assumed to be rendered with header and footer. Otherwise the
+     * headers and footers are omitted and the
+     * {@link #renderContent(HttpServletRequest, HttpServletResponse)}
+     * method is called without any decorations and without setting any
+     * response headers.
+     *
+     * @see 
org.apache.felix.webconsole.AbstractWebConsolePlugin#isHtmlRequest(javax.servlet.http.HttpServletRequest)
+     */
+    protected boolean isHtmlRequest( final HttpServletRequest request ) {
+        final String requestUri = request.getRequestURI();
+        if ( requestUri.endsWith( ".html" ) ) {
+            return true;
+        }
+        // check if there is an extension
+        final int lastSlash = requestUri.lastIndexOf('/');
+        final int lastDot = requestUri.indexOf('.', lastSlash + 1);
+        return lastDot < 0;
+    }
+
+    private static final class CheckHttpServletResponse extends 
HttpServletResponseWrapper {
+
+        private boolean done = false;
+        public CheckHttpServletResponse(HttpServletResponse response) {
+            super(response);
+        }
+
+        public boolean isDone() {
+            return this.done;
+        }
+
+        @Override
+        public void reset() {
+            this.done = false;
+            super.reset();
+        }
+
+        @Override
+        public void sendError(final int sc) throws IOException {
+            this.done = true;
+            super.sendError(sc);
+        }
+
+        @Override
+        public void sendError(final int sc, final String msg) throws 
IOException {
+            this.done = true;
+            super.sendError(sc, msg);
+        }
+
+        @Override
+        public void sendRedirect(final String location) throws IOException {
+            this.done = true;
+            super.sendRedirect(location);
+        }
+
+        @Override
+        public void setStatus(final int sc) {
+            this.done = true;
+            super.setStatus(sc);
+        }
+
+        @Override
+        public void setStatus(final int sc, final String sm) {
+            this.done = true;
+            super.setStatus(sc, sm);
+        }        
+    }
+
+    /**
+     * 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
+     * until the abstract web console plugin calls the
+     * {@link #renderContent(HttpServletRequest, HttpServletResponse)}
+     * method.
+     *
+     * @see 
javax.servlet.http.HttpServlet#service(javax.servlet.ServletRequest, 
javax.servlet.ServletResponse)
+     */
+    public void service( HttpServletRequest req, HttpServletResponse resp )
+    throws ServletException, IOException {
+        final CheckHttpServletResponse checkResponse = new 
CheckHttpServletResponse(resp);
+        // call plugin first
+        try {
+            plugin.service( 
(jakarta.servlet.http.HttpServletRequest)HttpServletRequestWrapper.getWrapper(req),
 
+                
(jakarta.servlet.http.HttpServletResponse)HttpServletResponseWrapper.getWrapper(resp)
 );
+        } catch (final jakarta.servlet.ServletException s) {
+            throw ServletExceptionUtil.getServletException(s);
+        }
+        // if a GET request and plugin did not create a response yet, call 
super to get full HTML response
+        if ( !checkResponse.isDone() && req.getMethod().equals( "GET" ) ) {
+            // handle the GET request here and call into plugin on 
renderContent
+            super.service( req, resp );
+        }
+    }
+
+    /**
+     * Destroys this servlet as well as the plugin servlet.
+     *
+     * @see javax.servlet.GenericServlet#destroy()
+     */
+    public void destroy() {
+        try {
+            plugin.destroy();
+            super.destroy();
+        } finally {
+            deactivate();
+        }
+    }
+
+
+    //---------- internal
+
+    @SuppressWarnings("rawtypes")
+    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/servlet/JakartaServletTracker.java
 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/JakartaServletTracker.java
index 277e5548f8..b0e1a140a1 100644
--- 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/JakartaServletTracker.java
+++ 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/JakartaServletTracker.java
@@ -25,6 +25,7 @@ import org.apache.felix.http.javaxwrappers.ServletWrapper;
 import org.apache.felix.webconsole.WebConsoleConstants;
 import org.apache.felix.webconsole.internal.Util;
 import org.apache.felix.webconsole.internal.servlet.Plugin.ServletPlugin;
+import org.apache.felix.webconsole.servlet.AbstractServlet;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
 import org.osgi.framework.Filter;
@@ -84,14 +85,19 @@ public class JakartaServletTracker implements Closeable, 
ServiceTrackerCustomize
 
     public static class JakartaServletPlugin extends ServletPlugin {
             
+        @SuppressWarnings({"unchecked", "rawtypes"})
         public JakartaServletPlugin(PluginHolder holder, 
ServiceReference<jakarta.servlet.Servlet> serviceReference,
                 String label) {
             super(holder, (ServiceReference)serviceReference, label);
         }
 
+        @SuppressWarnings({"unchecked", "rawtypes"})
         protected javax.servlet.Servlet getService() {
             final Servlet servlet = (Servlet) 
getHolder().getBundleContext().getService( 
(ServiceReference)this.getServiceReference() );
             if (servlet != null) {
+                if ( servlet instanceof AbstractServlet ) {
+                    return new JakartaServletAdapter((AbstractServlet)servlet, 
this.getServiceReference());
+                }
                 return new ServletWrapper(servlet);
             }
             return null;
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
index c5e06396c6..b6d241d85a 100644
--- 
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
@@ -242,29 +242,21 @@ class PluginHolder implements 
ServiceTrackerCustomizer<Servlet, Plugin> {
             // support only one level for now
             Map categoryMap = null;
             String category = plugin.getCategory();
-            if ( category == null || category.trim().length() == 0 )
-            {
+            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 );
+            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 */
+                } catch ( Throwable e ) {
+                    // ignore missing resource - use default title
                 }
             }
 
diff --git 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/system/VMStatPlugin.java
 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/system/VMStatPlugin.java
index 1b7c2fba0b..226a2195c9 100644
--- 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/system/VMStatPlugin.java
+++ 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/system/VMStatPlugin.java
@@ -31,12 +31,12 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import org.apache.felix.utils.json.JSONWriter;
-import org.apache.felix.webconsole.DefaultVariableResolver;
 import org.apache.felix.webconsole.SimpleWebConsolePlugin;
 import org.apache.felix.webconsole.WebConsoleConstants;
 import org.apache.felix.webconsole.WebConsoleUtil;
 import org.apache.felix.webconsole.internal.OsgiManagerPlugin;
 import org.apache.felix.webconsole.internal.servlet.OsgiManager;
+import org.apache.felix.webconsole.servlet.RequestVariableResolver;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleException;
 import org.osgi.service.startlevel.StartLevel;
@@ -190,7 +190,7 @@ public class VMStatPlugin extends SimpleWebConsolePlugin 
implements OsgiManagerP
                 jw.endObject();
                 jw.flush();
 
-                DefaultVariableResolver vars = ( ( DefaultVariableResolver ) 
WebConsoleUtil.getVariableResolver( request ) );
+                final RequestVariableResolver vars = 
WebConsoleUtil.getRequestVariableResolver(request);
                 vars.put( "data", json.toString() );
 
                 body = TPL_VM_RESTART;
@@ -250,7 +250,7 @@ public class VMStatPlugin extends SimpleWebConsolePlugin 
implements OsgiManagerP
 
         jw.flush();
 
-        DefaultVariableResolver vars = ( ( DefaultVariableResolver ) 
WebConsoleUtil.getVariableResolver( request ) );
+        final RequestVariableResolver vars = 
WebConsoleUtil.getRequestVariableResolver(request);
         vars.put( "startData", json.toString() );
 
         response.getWriter().print( body );
diff --git 
a/webconsole/src/main/java/org/apache/felix/webconsole/servlet/AbstractServlet.java
 
b/webconsole/src/main/java/org/apache/felix/webconsole/servlet/AbstractServlet.java
new file mode 100644
index 0000000000..031ad59cdc
--- /dev/null
+++ 
b/webconsole/src/main/java/org/apache/felix/webconsole/servlet/AbstractServlet.java
@@ -0,0 +1,201 @@
+/*
+ * 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.servlet;
+
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.StringWriter;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.charset.StandardCharsets;
+
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+
+/**
+ * This class can be used as a base class for a web console plugin.
+ * The plugin (servlet) needs to be registered as a servlet service
+ * with at least the label property.
+ *
+ * @see ServletConstants#PLUGIN_LABEL
+ * @see ServletConstants#PLUGIN_TITLE
+ * @see ServletConstants#PLUGIN_CATEGORY
+ */
+public abstract class AbstractServlet extends HttpServlet {
+
+    /**
+     * Called to identify resources.
+     * By default, if the path starts with "/res/" this is treated as a 
resource
+     * and the URL to the resource is tried to be loaded via the class loader.
+     * @param path the path
+     * @return the URL of the resource or <code>null</code> if not found.
+     */
+    protected URL getResource( final String path ) {
+        final int index = path.indexOf( '/', 1 );
+        if (index != -1) {
+            if (path.substring(index).startsWith("/res/") ) {
+                return getClass().getResource( path.substring(index) );
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Handle get requests. This method can be used to return resources, like 
JSON responses etc.
+     * If the plugin is serving a resource, this method call {@link 
HttpServletResponse#setStatus(int)}.
+     * This method is also allowed to send a redirect or an error.
+     * If none of the three applies, the webconsole will call {@link 
#renderContent(HttpServletRequest, HttpServletResponse)}
+     */
+    @Override
+    protected void doGet( final HttpServletRequest request, final 
HttpServletResponse response )
+    throws ServletException, IOException {
+        this.spoolResource( request, response );
+    }
+
+    /**
+     * This method is used to render the main contents of the plugin
+     * @param request The request
+     * @param response The response
+     * @throws ServletException If an error occurs
+     * @throws IOException If writing the response fails
+     */
+    public void renderContent( final HttpServletRequest request, final 
HttpServletResponse response)
+    throws ServletException, IOException {
+        throw new ServletException("Render method not implemented");
+    }
+
+   /**
+     * If the request addresses a resource , this method serves it
+     * and returns <code>true</code>. Otherwise <code>false</code> is returned.
+     * <p>
+     * If <code>true</code> is returned, the request is considered complete and
+     * request processing terminates. Otherwise request processing continues
+     * with normal plugin rendering.
+     *
+     * @param request The request object
+     * @param response The response object
+     * @return <code>true</code> if the request causes a resource to be sent 
back.
+     *
+     * @throws IOException If an error occurs accessing or spooling the 
resource.
+     */
+    protected final void spoolResource(final HttpServletRequest request, final 
HttpServletResponse response) 
+    throws IOException {
+        // check for a resource, fail if none
+        final URL url = this.getResource(request.getPathInfo());
+        if ( url == null ) {
+            return;
+        }
+
+        // open the connection and the stream (we use the stream to be able
+        // to at least hint to close the connection because there is no
+        // method to explicitly close the conneciton, unfortunately)
+        final URLConnection connection = url.openConnection();
+        try ( final InputStream ins = connection.getInputStream()) {
+            // FELIX-2017 Equinox may return an URL for a non-existing
+            // resource but then (instead of throwing) return null on
+            // getInputStream. We should account for this situation and
+            // just assume a non-existing resource in this case.
+            if (ins == null) {
+                return;
+            }
+
+            // check whether we may return 304/UNMODIFIED
+            long lastModified = connection.getLastModified();
+            if ( lastModified > 0 ) {
+                long ifModifiedSince = request.getDateHeader( 
"If-Modified-Since" );
+                if ( ifModifiedSince >= ( lastModified / 1000 * 1000 ) ) {
+                    // Round down to the nearest second for a proper compare
+                    // A ifModifiedSince of -1 will always be less
+                    response.setStatus( HttpServletResponse.SC_NOT_MODIFIED );
+
+                    return;
+                }
+
+                // have to send, so set the last modified header now
+                response.setDateHeader( "Last-Modified", lastModified ); 
//$NON-NLS-1$
+            }
+
+            // describe the contents
+            response.setContentType( getServletContext().getMimeType( 
request.getPathInfo() ) );
+            if (connection.getContentLength() != -1) {
+                response.setContentLength( connection.getContentLength() );
+            }
+            response.setStatus( HttpServletResponse.SC_OK);
+
+            // spool the actual contents
+            final OutputStream out = response.getOutputStream();
+            final byte[] buf = new byte[2048];
+            int rd;
+            while ( ( rd = ins.read( buf ) ) >= 0 ) {
+                out.write( buf, 0, rd );
+            }
+        }
+    }
+
+    /**
+     * Reads the <code>templateFile</code> as a resource through the class
+     * loader of this class converting the binary data into a string using
+     * UTF-8 encoding.
+     *
+     * @param templateFile The absolute path to the template file to read.
+     * @return The contents of the template file as a string 
+     *
+     * @throws NullPointerException if <code>templateFile</code> is
+     *      <code>null</code>
+     * @throws FileNotFoundException If template file cannot be found
+     * @throws IOException On any other error reading the template file
+     */
+    protected final String readTemplateFile( final String templateFile ) 
throws IOException {
+        return readTemplateFile( getClass(), templateFile );
+    }
+
+    private final String readTemplateFile( final Class<?> clazz, final String 
templateFile) throws IOException {
+        try(final InputStream templateStream = clazz.getResourceAsStream( 
templateFile )) {
+            if ( templateStream != null ) {
+                try ( final StringWriter w = new StringWriter()) {
+                    final byte[] buf = new byte[2048];
+                    int l;
+                    while ( ( l = templateStream.read(buf)) > 0 ) {
+                        w.write(new String(buf, 0, l, StandardCharsets.UTF_8));
+                    }
+                    String str = w.toString();
+                    switch ( str.charAt(0) ) { // skip BOM
+                        case 0xFEFF: // UTF-16/UTF-32, big-endian
+                        case 0xFFFE: // UTF-16, little-endian
+                        case 0xEFBB: // UTF-8
+                            return str.substring(1);
+                    }
+                    return str;
+                }
+            }
+        }
+
+        throw new FileNotFoundException("Template " + templateFile + " not 
found");
+    }
+
+    protected RequestVariableResolver getVariableResolver(final 
HttpServletRequest request) {
+        return (RequestVariableResolver) 
request.getAttribute(RequestVariableResolver.REQUEST_ATTRIBUTE);
+    }
+}
diff --git 
a/webconsole/src/main/java/org/apache/felix/webconsole/servlet/RequestVariableResolver.java
 
b/webconsole/src/main/java/org/apache/felix/webconsole/servlet/RequestVariableResolver.java
new file mode 100644
index 0000000000..4efdc3aacd
--- /dev/null
+++ 
b/webconsole/src/main/java/org/apache/felix/webconsole/servlet/RequestVariableResolver.java
@@ -0,0 +1,62 @@
+/*
+ * 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.servlet;
+
+import java.util.HashMap;
+
+/**
+ * The <code>RequestVariableResolver</code> is a <code>HashMap</code> that
+ * is used by the webconsole to process variables in the template.
+ * The resolver is stored as a request attribute with the name
+ * {@link #REQUEST_ATTRIBUTE}.
+ */
+public class RequestVariableResolver extends HashMap<String, Object> {
+
+    /**
+     * The name of the request attribute holding the {@link 
RequestVariableResolver}
+     * for the request (value is "felix.webconsole.variable.resolver").
+     *
+     * @since 3.0
+     */
+    public static final String REQUEST_ATTRIBUTE = 
"felix.webconsole.variable.resolver";
+
+    /**
+     * Creates a new variable resolver with default capacity.
+     */
+    public RequestVariableResolver() {
+        super();
+    }
+
+    /**
+     * Returns the string representation of the value stored under the variable
+     * name in this map. If no value is stored under the variable name,
+     * <code>null</code> is returned.
+     *
+     * @param variable The name of the variable whose value is to be returned.
+     * @return The variable value or <code>null</code> if there is no entry
+     *      with the given name in this map.
+     */
+    public String resolve( final String variable ) {
+        final Object value = this.get(variable);
+        if ( value != null ) {
+            return value.toString();
+        }
+        return null;
+    }
+}
diff --git 
a/webconsole/src/main/java/org/apache/felix/webconsole/servlet/ServletConstants.java
 
b/webconsole/src/main/java/org/apache/felix/webconsole/servlet/ServletConstants.java
new file mode 100644
index 0000000000..7b5f2f1431
--- /dev/null
+++ 
b/webconsole/src/main/java/org/apache/felix/webconsole/servlet/ServletConstants.java
@@ -0,0 +1,75 @@
+/*
+ * 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.servlet;
+
+/**
+ * Constants for servlets registered with the web console.
+ */
+public abstract class ServletConstants {
+
+    /**
+     * The URI address label under which the servlet is called by
+     * the web console (value is "felix.webconsole.label").
+     * <p>
+     * This service registration property must be set to a single non-empty
+     * String value. Otherwise the Servlet services will
+     * be ignored by the web console and not be used as a plugin.
+     */
+    public static final String PLUGIN_LABEL = "felix.webconsole.label";
+
+    /**
+     * The title under which the servlet is called by
+     * the web console (value is "felix.webconsole.title").
+     * <p>
+     * This property is required for the service to be used as a plugin. 
+     * Otherwise the service is just ignored by the web console.
+     * <p>
+     */
+    public static final String PLUGIN_TITLE = "felix.webconsole.title";
+
+    /**
+     * The category under which the servlet is listed in the top
+     * navigation by the web console (value is "felix.webconsole.category").
+     * <p>
+     * If not specified, the servlet is put into the default category.
+     */
+    public static final String PLUGIN_CATEGORY = "felix.webconsole.category";
+
+    /**
+     * 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.
+     */
+    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>)
+     * and the Web Console servlet path (from
+     * <code>HttpServletRequest.getServletPath()</code>,
+     * <code>/system/console</code> by default).
+     * <p>
+     * The type of this request attribute is <code>String</code>.
+     */
+    public static final String ATTR_APP_ROOT = "felix.webconsole.appRoot";
+}
diff --git 
a/webconsole/src/main/java/org/apache/felix/webconsole/spi/package-info.java 
b/webconsole/src/main/java/org/apache/felix/webconsole/servlet/package-info.java
similarity index 89%
copy from 
webconsole/src/main/java/org/apache/felix/webconsole/spi/package-info.java
copy to 
webconsole/src/main/java/org/apache/felix/webconsole/servlet/package-info.java
index da3a407a37..6792c7703b 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/spi/package-info.java
+++ 
b/webconsole/src/main/java/org/apache/felix/webconsole/servlet/package-info.java
@@ -16,6 +16,6 @@
  * specific language governing permissions and limitations
  * under the License.
  */
[email protected]("1.1.0")
-package org.apache.felix.webconsole.spi;
[email protected]("1.0.0")
+package org.apache.felix.webconsole.servlet;
 
diff --git 
a/webconsole/src/main/java/org/apache/felix/webconsole/spi/package-info.java 
b/webconsole/src/main/java/org/apache/felix/webconsole/spi/package-info.java
index da3a407a37..966e63eb77 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/spi/package-info.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/spi/package-info.java
@@ -16,6 +16,6 @@
  * specific language governing permissions and limitations
  * under the License.
  */
[email protected]("1.1.0")
[email protected]("1.2.0")
 package org.apache.felix.webconsole.spi;
 
diff --git 
a/webconsole/src/test/java/org/apache/felix/webconsole/AbstractWebConsolePluginTest.java
 
b/webconsole/src/test/java/org/apache/felix/webconsole/AbstractWebConsolePluginTest.java
index 02270e51e5..d4f811a020 100644
--- 
a/webconsole/src/test/java/org/apache/felix/webconsole/AbstractWebConsolePluginTest.java
+++ 
b/webconsole/src/test/java/org/apache/felix/webconsole/AbstractWebConsolePluginTest.java
@@ -122,6 +122,7 @@ public class AbstractWebConsolePluginTest extends TestCase
 
     private static class PrivateTestPlugin extends TestPlugin
     {
+        @SuppressWarnings("unused")
         private URL getResource( String name )
         {
             return null;
@@ -130,6 +131,7 @@ public class AbstractWebConsolePluginTest extends TestCase
 
     private static class ProtectedTestPlugin extends TestPlugin
     {
+        @SuppressWarnings("unused")
         protected URL getResource( String name )
         {
             return null;
@@ -138,6 +140,7 @@ public class AbstractWebConsolePluginTest extends TestCase
 
     private static class PackageTestPlugin extends TestPlugin
     {
+        @SuppressWarnings("unused")
         URL getResource( String name )
         {
             return null;
@@ -146,6 +149,7 @@ public class AbstractWebConsolePluginTest extends TestCase
 
     private static class PublicTestPlugin extends TestPlugin
     {
+        @SuppressWarnings("unused")
         public URL getResource( String name )
         {
             return null;
diff --git 
a/webconsole/src/test/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerTest.java
 
b/webconsole/src/test/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerTest.java
index 70b8574d18..99ca52fdad 100644
--- 
a/webconsole/src/test/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerTest.java
+++ 
b/webconsole/src/test/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerTest.java
@@ -70,7 +70,7 @@ public class OsgiManagerTest {
                 OsgiManager.splitCommaSeparatedString(" abc , x.y.z,123"));
     }
 
-    @SuppressWarnings({ "unchecked", "rawtypes", "serial" })
+    @SuppressWarnings({ "unchecked", "rawtypes"})
     @Test
     public void testUpdateDependenciesCustomizerAdd() throws Exception {
         BundleContext bc = mockBundleContext();
@@ -98,7 +98,7 @@ public class OsgiManagerTest {
         assertEquals(1, updateCalled.size());
     }
 
-    @SuppressWarnings({ "unchecked", "rawtypes", "serial" })
+    @SuppressWarnings({ "unchecked", "rawtypes" })
     @Test
     public void testUpdateDependenciesCustomzerRemove() throws Exception {
         BundleContext bc = mockBundleContext();

Reply via email to