SLING-12924 Migrate common log webconsole plugin to jakarta servlet (#11)

diff --git a/pom.xml b/pom.xml
index 41ab824..6590ffa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,7 +28,7 @@
     </parent>
 
     <artifactId>org.apache.sling.commons.log.webconsole</artifactId>
-    <version>1.0.3-SNAPSHOT</version>
+    <version>2.0.0-SNAPSHOT</version>
 
     <name>Apache Sling Commons Log WebConsole</name>
     <description>This bundle provides webconsole plugin for Sling Commons Log</description>
@@ -42,7 +42,7 @@
 
     <properties>
         <sling.java.version>17</sling.java.version>
-        <slf4j.version>1.7.21</slf4j.version>
+        <slf4j.version>2.0.17</slf4j.version>
         <pax-exam.version>4.14.0</pax-exam.version>
         <project.build.outputTimestamp>1734083968</project.build.outputTimestamp>
 
@@ -76,6 +76,7 @@
         <dependency>
             <groupId>org.osgi</groupId>
             <artifactId>org.osgi.framework</artifactId>
+            <version>1.10.0</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
@@ -97,8 +98,9 @@
 
         <!-- servlet API for the web console plugin -->
         <dependency>
-            <groupId>javax.servlet</groupId>
-            <artifactId>javax.servlet-api</artifactId>
+            <groupId>jakarta.servlet</groupId>
+            <artifactId>jakarta.servlet-api</artifactId>
+            <version>6.0.0</version>
             <scope>provided</scope>
         </dependency>
 
@@ -120,7 +122,7 @@
         <dependency>
             <groupId>org.apache.felix</groupId>
             <artifactId>org.apache.felix.framework</artifactId>
-            <version>7.0.1</version>
+            <version>7.0.5</version>
             <scope>test</scope>
         </dependency>
 
diff --git a/src/main/java/org/apache/sling/commons/log/webconsole/internal/Activator.java b/src/main/java/org/apache/sling/commons/log/webconsole/internal/Activator.java
index 15d9b41..e9249b9 100644
--- a/src/main/java/org/apache/sling/commons/log/webconsole/internal/Activator.java
+++ b/src/main/java/org/apache/sling/commons/log/webconsole/internal/Activator.java
@@ -18,32 +18,33 @@
  */
 package org.apache.sling.commons.log.webconsole.internal;
 
+import jakarta.servlet.Servlet;
 import org.apache.sling.commons.log.logback.webconsole.LogPanel;
 import org.osgi.annotation.bundle.Header;
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
 import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
 import org.osgi.util.tracker.ServiceTracker;
 
 @Header(name = Constants.BUNDLE_ACTIVATOR, value = "${@class}")
 public class Activator implements BundleActivator {
-    private ServiceTracker<LogPanel, LogWebConsolePlugin> panelTracker;
+    private ServiceTracker<LogPanel, ServiceRegistration<Servlet>> panelTracker;
 
     @Override
     public void start(BundleContext context) throws Exception {
-        panelTracker = new ServiceTracker<LogPanel, LogWebConsolePlugin>(context, LogPanel.class, null) {
+        panelTracker = new ServiceTracker<LogPanel, ServiceRegistration<Servlet>>(context, LogPanel.class, null) {
             @Override
-            public LogWebConsolePlugin addingService(ServiceReference<LogPanel> reference) {
+            public ServiceRegistration<Servlet> addingService(ServiceReference<LogPanel> reference) {
                 LogPanel panel = context.getService(reference);
                 LogWebConsolePlugin plugin = new LogWebConsolePlugin(panel);
-                plugin.register(context);
-                return plugin;
+                return plugin.register(context);
             }
 
             @Override
-            public void removedService(ServiceReference<LogPanel> reference, LogWebConsolePlugin plugin) {
-                plugin.unregister();
+            public void removedService(ServiceReference<LogPanel> reference, ServiceRegistration<Servlet> serviceReg) {
+                serviceReg.unregister();
                 context.ungetService(reference);
             }
         };
diff --git a/src/main/java/org/apache/sling/commons/log/webconsole/internal/LogWebConsolePlugin.java b/src/main/java/org/apache/sling/commons/log/webconsole/internal/LogWebConsolePlugin.java
index 1a88abd..910eba7 100644
--- a/src/main/java/org/apache/sling/commons/log/webconsole/internal/LogWebConsolePlugin.java
+++ b/src/main/java/org/apache/sling/commons/log/webconsole/internal/LogWebConsolePlugin.java
@@ -18,17 +18,23 @@
  */
 package org.apache.sling.commons.log.webconsole.internal;
 
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.util.Dictionary;
+import java.util.Map;
 
-import org.apache.felix.webconsole.SimpleWebConsolePlugin;
+import jakarta.servlet.Servlet;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.apache.felix.webconsole.servlet.AbstractServlet;
+import org.apache.felix.webconsole.servlet.ServletConstants;
 import org.apache.sling.commons.log.logback.webconsole.LogPanel;
 import org.apache.sling.commons.log.logback.webconsole.LoggerConfig;
 import org.apache.sling.commons.log.logback.webconsole.TailerOptions;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceRegistration;
 
 import static org.apache.sling.commons.log.logback.webconsole.LogPanel.APP_ROOT;
 import static org.apache.sling.commons.log.logback.webconsole.LogPanel.PARAM_APPENDER_NAME;
@@ -36,53 +42,78 @@
 import static org.apache.sling.commons.log.logback.webconsole.LogPanel.PARAM_TAIL_NUM_OF_LINES;
 import static org.apache.sling.commons.log.logback.webconsole.LogPanel.PATH_TAILER;
 
-public class LogWebConsolePlugin extends SimpleWebConsolePlugin {
+public class LogWebConsolePlugin extends AbstractServlet {
+    private static final long serialVersionUID = 1L;
+
     private static final String RES_LOC = LogPanel.APP_ROOT + "/res/ui";
 
     private static final String[] CSS_REFS = {
         RES_LOC + "/jquery.autocomplete.css", RES_LOC + "/prettify.css", RES_LOC + "/log.css",
     };
 
-    private final LogPanel panel;
+    private final transient LogPanel panel;
 
     public LogWebConsolePlugin(LogPanel panel) {
-        super(LogPanel.APP_ROOT, "Log Support", "Sling", CSS_REFS);
         this.panel = panel;
     }
 
+    public ServiceRegistration<Servlet> register(BundleContext context) {
+        Dictionary<String, Object> props = FrameworkUtil.asDictionary(Map.of(
+                ServletConstants.PLUGIN_LABEL,
+                LogPanel.APP_ROOT,
+                ServletConstants.PLUGIN_TITLE,
+                "Log Support",
+                ServletConstants.PLUGIN_CATEGORY,
+                "Sling",
+                ServletConstants.PLUGIN_CSS_REFERENCES,
+                CSS_REFS));
+        return context.registerService(Servlet.class, this, props);
+    }
+
+    /**
+     * Override so we can ensure the rendering of the tailer text output
+     * does not contain the html header and footer tags.
+     */
     @Override
-    protected void renderContent(HttpServletRequest req, HttpServletResponse resp)
-            throws ServletException, IOException {
-        final PrintWriter pw = resp.getWriter();
-        final String consoleAppRoot = getAppRoot(req);
-
-        if (req.getPathInfo() != null) {
-            if (req.getPathInfo().endsWith(PATH_TAILER)) {
-                String appenderName = req.getParameter(PARAM_APPENDER_NAME);
-                String regex = req.getParameter(PARAM_TAIL_GREP);
-                addNoSniffHeader(resp);
-                if (appenderName == null) {
-                    pw.printf("Provide appender name via [%s] request parameter%n", PARAM_APPENDER_NAME);
-                    return;
-                }
-                int numOfLines = 0;
-                try {
-                    numOfLines = Integer.valueOf(req.getParameter(PARAM_TAIL_NUM_OF_LINES));
-                } catch (NumberFormatException e) {
-                    // ignore
-                }
-                TailerOptions opts = new TailerOptions(numOfLines, regex);
-                panel.tail(pw, appenderName, opts);
-                return;
-            }
+    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+        if ("GET".equalsIgnoreCase(req.getMethod())
+                && req.getPathInfo() != null
+                && req.getPathInfo().endsWith(PATH_TAILER)) {
+            renderContent(req, resp);
+        } else {
+            super.service(req, resp);
         }
-
-        panel.render(pw, consoleAppRoot);
     }
 
     @Override
-    protected boolean isHtmlRequest(HttpServletRequest request) {
-        return !request.getRequestURI().endsWith(PATH_TAILER);
+    public void renderContent(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+        final PrintWriter pw = resp.getWriter();
+        final String consoleAppRoot = getAppRoot(req);
+
+        if (req.getPathInfo() != null && req.getPathInfo().endsWith(PATH_TAILER)) {
+            // NOTE: set the content type here to ensure that EnhancedPluginAdapter.CheckHttpServletResponse
+            //       that is being processed knows that the output is done and doesn't try rendering the html
+            //       header and footer tags
+            resp.setContentType("text/plain");
+            String appenderName = req.getParameter(PARAM_APPENDER_NAME);
+            String regex = req.getParameter(PARAM_TAIL_GREP);
+            addNoSniffHeader(resp);
+            if (appenderName == null) {
+                pw.printf("Provide appender name via [%s] request parameter%n", PARAM_APPENDER_NAME);
+                return;
+            }
+            int numOfLines = 0;
+            try {
+                numOfLines = Integer.valueOf(req.getParameter(PARAM_TAIL_NUM_OF_LINES));
+            } catch (NumberFormatException e) {
+                // ignore
+            }
+            TailerOptions opts = new TailerOptions(numOfLines, regex);
+            panel.tail(pw, appenderName, opts);
+            return;
+        }
+
+        panel.render(pw, consoleAppRoot);
     }
 
     @Override
diff --git a/src/test/java/org/apache/sling/commons/log/webconsole/ITLogWebConsolePlugin.java b/src/test/java/org/apache/sling/commons/log/webconsole/ITLogWebConsolePlugin.java
index 290766f..294b06a 100644
--- a/src/test/java/org/apache/sling/commons/log/webconsole/ITLogWebConsolePlugin.java
+++ b/src/test/java/org/apache/sling/commons/log/webconsole/ITLogWebConsolePlugin.java
@@ -19,8 +19,8 @@
 package org.apache.sling.commons.log.webconsole;
 
 import javax.inject.Inject;
-import javax.servlet.Servlet;
 
+import jakarta.servlet.Servlet;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.ops4j.pax.exam.junit.PaxExam;
diff --git a/src/test/java/org/apache/sling/commons/log/webconsole/ITWebConsoleRemote.java b/src/test/java/org/apache/sling/commons/log/webconsole/ITWebConsoleRemote.java
index 73fe1a0..4ac3410 100644
--- a/src/test/java/org/apache/sling/commons/log/webconsole/ITWebConsoleRemote.java
+++ b/src/test/java/org/apache/sling/commons/log/webconsole/ITWebConsoleRemote.java
@@ -22,6 +22,7 @@
 import java.io.IOException;
 
 import org.apache.commons.io.FilenameUtils;
+import org.apache.sling.commons.log.logback.webconsole.LogPanel;
 import org.apache.sling.commons.log.webconsole.remote.WebConsoleTestActivator;
 import org.htmlunit.DefaultCredentialsProvider;
 import org.htmlunit.Page;
@@ -152,6 +153,17 @@
         assertThat(text, not(containsString(WebConsoleTestActivator.BAR_LOG)));
     }
 
+    @Test
+    public void tailerGrepWithoutAppenderName() throws Exception {
+        TextPage page = webClient.getPage(prepareUrl("slinglog/tailer.txt"));
+        String text = page.getContent();
+
+        assertThat(
+                text,
+                containsString(String.format(
+                        "Provide appender name via [%s] request parameter", LogPanel.PARAM_APPENDER_NAME)));
+    }
+
     @AfterClass
     public static void tearDownClass() {
         if (testContainer != null) {
diff --git a/src/test/java/org/apache/sling/commons/log/webconsole/LogTestBase.java b/src/test/java/org/apache/sling/commons/log/webconsole/LogTestBase.java
index a3ef60e..2ddd1c2 100644
--- a/src/test/java/org/apache/sling/commons/log/webconsole/LogTestBase.java
+++ b/src/test/java/org/apache/sling/commons/log/webconsole/LogTestBase.java
@@ -55,7 +55,7 @@
     // the name of the system property which captures the jococo coverage agent
     // command
     // if specified then agent would be specified otherwise ignored
-    protected static final String COVERAGE_COMMAND = "coverage.command";
+    protected static final String COVERAGE_COMMAND = "jacoco.it.command";
 
     // the default bundle jar file name
     protected static final String BUNDLE_JAR_DEFAULT = "target/slinglogback-webconsole.jar";
diff --git a/src/test/java/org/apache/sling/commons/log/webconsole/remote/WebConsoleTestActivator.java b/src/test/java/org/apache/sling/commons/log/webconsole/remote/WebConsoleTestActivator.java
index c4bc407..75f4403 100644
--- a/src/test/java/org/apache/sling/commons/log/webconsole/remote/WebConsoleTestActivator.java
+++ b/src/test/java/org/apache/sling/commons/log/webconsole/remote/WebConsoleTestActivator.java
@@ -50,7 +50,7 @@
     public static final String FOO_LOG = "WebConsoleTestActivator-Foo";
     public static final String BAR_LOG = "WebConsoleTestActivator-BAR";
 
-    public static Class[] BUNDLE_CLASS_NAMES = {
+    public static Class<?>[] BUNDLE_CLASS_NAMES = {
         WebConsoleTestActivator.class,
         WebConsoleTestTurboFilter.class,
         WebConsoleTestConfigProvider.class,