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

enapps-enorman pushed a commit to branch master
in repository 
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-installer-factory-configuration.git


The following commit(s) were added to refs/heads/master by this push:
     new 0939378  SLING-13165 migrate to jakarta Servlet (#23)
0939378 is described below

commit 093937814409e2a669bf113dc999aeed8414a9ed
Author: Eric Norman <[email protected]>
AuthorDate: Mon Apr 20 14:42:54 2026 -0700

    SLING-13165 migrate to jakarta Servlet (#23)
---
 bnd.bnd                                            |   4 +-
 pom.xml                                            |  45 +++-
 .../ConfigurationSerializerWebConsolePlugin.java   |  95 ++++----
 ...onfigurationSerializerWebConsolePluginTest.java | 239 +++++++++++++++++++++
 .../configuration/impl/ReflectionTools.java        |  71 ++++++
 5 files changed, 400 insertions(+), 54 deletions(-)

diff --git a/bnd.bnd b/bnd.bnd
index e94d050..2cc25f3 100644
--- a/bnd.bnd
+++ b/bnd.bnd
@@ -1,7 +1,7 @@
 Import-Package: !org.osgi.service.metatype, \
   !org.apache.felix.webconsole.spi,\
-  javax.servlet;resolution:=optional,\
+  jakarta.servlet;resolution:=optional,\
   *
 DynamicImport-Package: org.osgi.service.metatype, \
   org.apache.felix.webconsole.spi, \
-  javax.servlet
+  jakarta.servlet
diff --git a/pom.xml b/pom.xml
index 1c148e5..0c20cc5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -27,7 +27,7 @@
     </parent>
 
     <artifactId>org.apache.sling.installer.factory.configuration</artifactId>
-    <version>1.4.11-SNAPSHOT</version>
+    <version>2.0.0-SNAPSHOT</version>
 
     <name>Apache Sling Installer Configuration Factory</name>
     <description>Provides support for configurations to the Apache Sling OSGi 
installer</description>
@@ -36,7 +36,7 @@
         
<connection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-installer-factory-configuration.git</connection>
         
<developerConnection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-installer-factory-configuration.git</developerConnection>
         <tag>HEAD</tag>
-        
<url>https://github.com/apache/sling-org-apache-sling-installer-factory-configuration.git</url>
+        
<url>https://github.com/apache/sling-org-apache-sling-installer-factory-configuration/tree/${project.scm.tag}</url>
     </scm>
 
     <properties>
@@ -45,6 +45,12 @@
     </properties>
 
     <dependencies>
+        <!-- JetBrains annotations for null-analysis (SLING-7798) -->
+        <dependency>
+            <groupId>org.jetbrains</groupId>
+            <artifactId>annotations</artifactId>
+            <scope>provided</scope>
+        </dependency>
         <!-- OSGi annotations (OSGi R7) -->
         <!-- 
https://osgi.org/javadoc/osgi.annotation/7.0.0/org/osgi/annotation/versioning/package-frame.html
 -->
         <dependency>
@@ -99,7 +105,7 @@
         <dependency>
             <groupId>org.apache.felix</groupId>
             <artifactId>org.apache.felix.webconsole</artifactId>
-            <version>4.7.0</version>
+            <version>5.0.10</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
@@ -123,9 +129,10 @@
             <artifactId>org.osgi.util.function</artifactId>
             <scope>provided</scope>
         </dependency>
+        <!-- for Web Console plugin -->
         <dependency>
-            <groupId>javax.servlet</groupId>
-            <artifactId>javax.servlet-api</artifactId>
+            <groupId>jakarta.servlet</groupId>
+            <artifactId>jakarta.servlet-api</artifactId>
             <scope>provided</scope>
         </dependency>
         <dependency>
@@ -138,11 +145,39 @@
             <artifactId>junit-jupiter-engine</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-params</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.testing.sling-mock.junit5</artifactId>
+            <version>4.0.4</version>
+            <scope>test</scope>
+        </dependency>
+        <!-- JSON Configurations -->
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.cm.json</artifactId>
+            <version>1.0.6</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.util.promise</artifactId>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>org.mockito</groupId>
             <artifactId>mockito-core</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git 
a/src/main/java/org/apache/sling/installer/factories/configuration/impl/ConfigurationSerializerWebConsolePlugin.java
 
b/src/main/java/org/apache/sling/installer/factories/configuration/impl/ConfigurationSerializerWebConsolePlugin.java
index e2f56a2..68bfedb 100644
--- 
a/src/main/java/org/apache/sling/installer/factories/configuration/impl/ConfigurationSerializerWebConsolePlugin.java
+++ 
b/src/main/java/org/apache/sling/installer/factories/configuration/impl/ConfigurationSerializerWebConsolePlugin.java
@@ -18,10 +18,6 @@
  */
 package org.apache.sling.installer.factories.configuration.impl;
 
-import javax.servlet.GenericServlet;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -37,9 +33,14 @@ import java.util.Objects;
 import java.util.Set;
 import java.util.stream.Collectors;
 
+import jakarta.servlet.GenericServlet;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
 import org.apache.sling.installer.api.info.InfoProvider;
 import 
org.apache.sling.installer.api.serializer.ConfigurationSerializerFactory;
 import 
org.apache.sling.installer.api.serializer.ConfigurationSerializerFactory.Format;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 import org.osgi.framework.Constants;
 import org.osgi.service.cm.Configuration;
 import org.osgi.service.cm.ConfigurationAdmin;
@@ -51,7 +52,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 @Component(
-        service = javax.servlet.Servlet.class,
+        service = jakarta.servlet.Servlet.class,
         property = {
             Constants.SERVICE_VENDOR + "=The Apache Software Foundation",
             Constants.SERVICE_DESCRIPTION + "=Apache Sling OSGi Installer 
Configuration Serializer Web Console Plugin",
@@ -69,16 +70,16 @@ public class ConfigurationSerializerWebConsolePlugin 
extends GenericServlet {
     private static final String PARAMETER_HIDE_REDUNDANT_PROPERTIES = 
"hideRedundantProperties";
 
     /** The logger */
-    private final Logger LOGGER = 
LoggerFactory.getLogger(ConfigurationSerializerWebConsolePlugin.class);
+    private final transient Logger logger = 
LoggerFactory.getLogger(ConfigurationSerializerWebConsolePlugin.class);
 
     @Reference
-    ConfigurationAdmin configurationAdmin;
+    transient ConfigurationAdmin configurationAdmin;
 
     @Reference
-    private InfoProvider infoProvider;
+    private transient InfoProvider infoProvider;
 
     @Reference
-    private ServiceComponentRuntime scr;
+    private transient ServiceComponentRuntime scr;
 
     @Override
     public void service(final ServletRequest request, final ServletResponse 
response) throws IOException {
@@ -95,7 +96,7 @@ public class ConfigurationSerializerWebConsolePlugin extends 
GenericServlet {
             try {
                 serializationFormat = 
ConfigurationSerializerFactory.Format.valueOf(format);
             } catch (IllegalArgumentException e) {
-                LOGGER.warn("Illegal parameter 'format' given, falling back to 
default '{}'", serializationFormat, e);
+                logger.warn("Illegal parameter 'format' given, falling back to 
default '{}'", serializationFormat, e);
             }
         }
         final boolean hideRedundantProperties;
@@ -108,10 +109,10 @@ public class ConfigurationSerializerWebConsolePlugin 
extends GenericServlet {
     }
 
     private void dumpConfiguration(
-            Configuration configuration,
-            ConfigurationSerializerFactory.Format serializationFormat,
+            @Nullable Configuration configuration,
+            @NotNull ConfigurationSerializerFactory.Format serializationFormat,
             boolean hideRedundantProperties,
-            PrintWriter pw) {
+            @NotNull PrintWriter pw) {
         // map with key = configuration pid and value = 
Set<ComponentDescriptionDTO>
         Map<String, Set<ComponentDescriptionDTO>> allComponentDescriptions = 
new HashMap<>();
         String pid = configuration != null ? configuration.getPid() : "";
@@ -183,7 +184,7 @@ public class ConfigurationSerializerWebConsolePlugin 
extends GenericServlet {
             Set<ComponentDescriptionDTO> componentDescriptions =
                     
allComponentDescriptions.get(pidReferencedFromComponentDescription);
             if (componentDescriptions != null) {
-                if (sb.length() > 0) {
+                if (!sb.isEmpty()) {
                     sb.append(" and ");
                 }
                 sb.append("from component description(s) of ");
@@ -200,7 +201,7 @@ public class ConfigurationSerializerWebConsolePlugin 
extends GenericServlet {
         } else {
             pidReferencedFromComponentDescription = "";
         }
-        if (sb.length() == 0) {
+        if (sb.isEmpty()) {
             sb.append("no fallback sources found");
         }
         pw.append(sb.toString()).append(")</label>").println();
@@ -259,7 +260,7 @@ public class ConfigurationSerializerWebConsolePlugin 
extends GenericServlet {
                     pw.print("<p class='ui-state-error-text'>");
                     pw.print("Error serializing pid '" + escapeXml(pid) + "': 
" + e.getMessage());
                     pw.println("</p>");
-                    LOGGER.warn("Error serializing pid '{}'", pid, e);
+                    logger.warn("Error serializing pid '{}'", pid, e);
                 }
             }
             closeTd(pw);
@@ -269,29 +270,33 @@ public class ConfigurationSerializerWebConsolePlugin 
extends GenericServlet {
         pw.print("</form>");
     }
 
-    private void tdContent(final PrintWriter pw) {
+    private void tdContent(@NotNull final PrintWriter pw) {
         pw.print("<td class='content' colspan='2'>");
     }
 
-    private void closeTd(final PrintWriter pw) {
+    private void closeTd(@NotNull final PrintWriter pw) {
         pw.print("</td>");
     }
 
-    private void closeTr(final PrintWriter pw) {
+    private void closeTr(@NotNull final PrintWriter pw) {
         pw.println("</tr>");
     }
 
-    private void tdLabel(final PrintWriter pw, final String label) {
+    private void tdLabel(@NotNull final PrintWriter pw, @NotNull final String 
label) {
         pw.print("<td class='content'>");
         pw.print(label);
         pw.println("</td>");
     }
 
-    private void tr(final PrintWriter pw) {
+    private void tr(@NotNull final PrintWriter pw) {
         pw.println("<tr class='content'>");
     }
 
-    private void option(final PrintWriter pw, String value, String label, 
String selectedValue) {
+    private void option(
+            @NotNull final PrintWriter pw,
+            @NotNull String value,
+            @NotNull String label,
+            @NotNull String selectedValue) {
         pw.print("<option value='");
         pw.print(value);
         pw.print("'");
@@ -303,20 +308,19 @@ public class ConfigurationSerializerWebConsolePlugin 
extends GenericServlet {
         pw.println("</option>");
     }
 
-    private void titleHtml(final PrintWriter pw, final String title, final 
String description) {
+    private void titleHtml(
+            @NotNull final PrintWriter pw, @NotNull final String title, 
@NotNull final String description) {
         tr(pw);
         pw.print("<th colspan='3' class='content container'>");
         pw.print(escapeXml(title));
         pw.println("</th>");
         closeTr(pw);
 
-        if (description != null) {
-            tr(pw);
-            pw.print("<td colspan='3' class='content'>");
-            pw.print(description);
-            pw.println("</th>");
-            closeTr(pw);
-        }
+        tr(pw);
+        pw.print("<td colspan='3' class='content'>");
+        pw.print(description);
+        pw.println("</th>");
+        closeTr(pw);
     }
 
     /**
@@ -325,7 +329,7 @@ public class ConfigurationSerializerWebConsolePlugin 
extends GenericServlet {
      * @param input The input text
      * @return The escaped text
      */
-    protected String escapeXml(final String input) {
+    protected String escapeXml(@Nullable final String input) {
         if (input == null) {
             return null;
         }
@@ -359,18 +363,17 @@ public class ConfigurationSerializerWebConsolePlugin 
extends GenericServlet {
      * @param mergedProperties the merged/inherited properties from some other 
OSGi installer resource
      */
     private void removeComponentDefaultProperties(
-            final Map<String, Set<ComponentDescriptionDTO>> 
allComponentDescriptions,
-            final String pidReferencedFromComponentDescription,
-            final Dictionary<String, Object> properties,
-            final Dictionary<String, Object> mergedProperties) {
+            @NotNull final Map<String, Set<ComponentDescriptionDTO>> 
allComponentDescriptions,
+            @NotNull final String pidReferencedFromComponentDescription,
+            @NotNull final Dictionary<String, Object> properties,
+            @NotNull final Dictionary<String, Object> mergedProperties) {
         Set<ComponentDescriptionDTO> componentDescriptions =
                 
allComponentDescriptions.get(pidReferencedFromComponentDescription);
-
-        final Enumeration<String> e = properties.keys();
-        while (e.hasMoreElements()) {
-            final String key = e.nextElement();
-            final Object newValue = properties.get(key);
-            if (componentDescriptions != null) {
+        if (componentDescriptions != null) {
+            final Enumeration<String> e = properties.keys();
+            while (e.hasMoreElements()) {
+                final String key = e.nextElement();
+                final Object newValue = properties.get(key);
                 // check all bound component descriptions
                 Set<Object> defaultValues = componentDescriptions.stream()
                         .map(dto -> dto.properties.get(key))
@@ -388,6 +391,7 @@ public class ConfigurationSerializerWebConsolePlugin 
extends GenericServlet {
         }
     }
 
+    @NotNull
     String getRelativeResourcePrefix() {
         return RES_LOC;
     }
@@ -396,14 +400,11 @@ public class ConfigurationSerializerWebConsolePlugin 
extends GenericServlet {
      * Method to retrieve static resources from this bundle.
      */
     @SuppressWarnings("unused")
-    private URL getResource(final String path) {
+    private @Nullable URL getResource(@NotNull final String path) {
         if (path.startsWith("/" + getRelativeResourcePrefix())) {
             // strip label
-            int index = path.indexOf('/', 1);
-            if (index <= 0) {
-                throw new IllegalStateException("The relativeResourcePrefix 
must contain at least one '/'");
-            }
-            return this.getClass().getResource(path.substring(index));
+            String resPath = path.substring(LABEL.length() + 1);
+            return this.getClass().getResource(resPath);
         }
         return null;
     }
diff --git 
a/src/test/java/org/apache/sling/installer/factories/configuration/impl/ConfigurationSerializerWebConsolePluginTest.java
 
b/src/test/java/org/apache/sling/installer/factories/configuration/impl/ConfigurationSerializerWebConsolePluginTest.java
new file mode 100644
index 0000000..905d455
--- /dev/null
+++ 
b/src/test/java/org/apache/sling/installer/factories/configuration/impl/ConfigurationSerializerWebConsolePluginTest.java
@@ -0,0 +1,239 @@
+/*
+ * 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.sling.installer.factories.configuration.impl;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
+
+import org.apache.sling.installer.api.info.InfoProvider;
+import org.apache.sling.installer.api.info.InstallationState;
+import org.apache.sling.installer.api.info.Resource;
+import org.apache.sling.installer.api.info.ResourceGroup;
+import 
org.apache.sling.installer.api.serializer.ConfigurationSerializerFactory;
+import org.apache.sling.testing.mock.sling.junit5.SlingContext;
+import org.apache.sling.testing.mock.sling.junit5.SlingContextExtension;
+import 
org.apache.sling.testing.mock.sling.servlet.MockSlingJakartaHttpServletRequest;
+import 
org.apache.sling.testing.mock.sling.servlet.MockSlingJakartaHttpServletResponse;
+import org.jetbrains.annotations.NotNull;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.osgi.framework.dto.BundleDTO;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.component.runtime.ServiceComponentRuntime;
+import org.osgi.service.component.runtime.dto.ComponentDescriptionDTO;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+
+/**
+ *
+ */
+@ExtendWith(SlingContextExtension.class)
+class ConfigurationSerializerWebConsolePluginTest {
+
+    public final SlingContext context = new SlingContext();
+
+    private ConfigurationSerializerWebConsolePlugin plugin;
+
+    private InfoProvider mockInfoProvider;
+    private ServiceComponentRuntime mockScr;
+
+    @BeforeEach
+    void beforeEach() {
+        mockInfoProvider = context.registerService(InfoProvider.class, 
Mockito.mock(InfoProvider.class));
+        mockScr = context.registerService(ServiceComponentRuntime.class, 
Mockito.mock(ServiceComponentRuntime.class));
+
+        plugin = 
context.registerInjectActivateService(ConfigurationSerializerWebConsolePlugin.class);
+    }
+
+    protected static Stream<Arguments> testServiceArgs() {
+        return Stream.of(
+                Arguments.of(Map.of()),
+                Arguments.of(Map.of("pid", "")),
+                Arguments.of(Map.of("pid", "test1")),
+                Arguments.of(Map.of("pid", "test1", "format", "")),
+                Arguments.of(Map.of("pid", "test1", "format", "JSON")),
+                Arguments.of(Map.of("pid", "test2", "format", "JSON")),
+                Arguments.of(Map.of("pid", "test1", "format", "INVALID")));
+    }
+
+    /**
+     * Test method for {@link 
org.apache.sling.installer.factories.configuration.impl.ConfigurationSerializerWebConsolePlugin#service(jakarta.servlet.ServletRequest,
 jakarta.servlet.ServletResponse)}.
+     */
+    @ParameterizedTest
+    @MethodSource("testServiceArgs")
+    void testService(Map<String, Object> reqParams) throws IOException {
+        // mock InfoProvider
+        mockInstallationState();
+
+        // mock existing test1 component configuration
+        mockExistingConfiguration("test1", Map.of("key1", "value1"));
+
+        // mock DTO for test1 component
+        mockComponentDescriptionDTO("test1", Map.of());
+
+        final @NotNull MockSlingJakartaHttpServletRequest req = 
context.jakartaRequest();
+        req.setParameterMap(reqParams);
+        final @NotNull MockSlingJakartaHttpServletResponse resp = 
context.jakartaResponse();
+        plugin.service(req, resp);
+        final String outputAsString = resp.getOutputAsString();
+        assertNotNull(outputAsString);
+    }
+
+    @Test
+    void testServiceWithSerializerException() throws Exception {
+        // mock existing test1 component configuration
+        mockExistingConfiguration("test1", Map.of("key1", "value1"));
+
+        final @NotNull MockSlingJakartaHttpServletRequest req = 
context.jakartaRequest();
+        req.setParameterMap(Map.of("pid", "test1"));
+        final @NotNull MockSlingJakartaHttpServletResponse resp = 
context.jakartaResponse();
+        try (MockedStatic<ConfigurationSerializerFactory> factoryMock =
+                Mockito.mockStatic(ConfigurationSerializerFactory.class); ) {
+            factoryMock
+                    .when(() -> 
ConfigurationSerializerFactory.create(any(ConfigurationSerializerFactory.Format.class)))
+                    .thenThrow(UnsupportedOperationException.class);
+
+            plugin.service(req, resp);
+            final String outputAsString = resp.getOutputAsString();
+            assertNotNull(outputAsString);
+            assertTrue(outputAsString.contains("Error serializing pid"));
+        }
+    }
+
+    @ParameterizedTest
+    @ValueSource(booleans = {true, false})
+    void testServiceWithMergeScheme(boolean createDTO) throws IOException {
+        Activator.MERGE_SCHEMES = List.of("launchpad");
+
+        // mock InfoProvider
+        final InstallationState mockInstallationState = 
mockInstallationState();
+        ResourceGroup mockResourceGroup = Mockito.mock(ResourceGroup.class);
+        Resource mockInstalledResource = Mockito.mock(Resource.class);
+        
Mockito.doReturn("config:factory1~test1").when(mockInstalledResource).getEntityId();
+        Mockito.doReturn("launchpad").when(mockInstalledResource).getScheme();
+        Mockito.doReturn(new Hashtable<>(Map.of("key1", "value1", "key2", 
"value2")))
+                .when(mockInstalledResource)
+                .getDictionary();
+        
Mockito.doReturn(List.of(mockInstalledResource)).when(mockResourceGroup).getResources();
+        
Mockito.doReturn(List.of(mockResourceGroup)).when(mockInstallationState).getInstalledResources();
+
+        // mock existing test1 component configuration
+        mockExistingFactoryConfiguration(
+                "factory1",
+                "test1",
+                Map.of("key1", "value1", "key2", "value2changed", "key3", 
"value3", "key4", "value4"));
+
+        if (createDTO) {
+            // mock DTO for test1 component
+            mockComponentDescriptionDTO(
+                    "factory1", Map.of("key1", "value1", "key3", "value3", 
"key4", "value4changed"));
+        }
+
+        final @NotNull MockSlingJakartaHttpServletRequest req = 
context.jakartaRequest();
+        req.setParameterMap(Map.of("pid", "factory1~test1", 
"hideRedundantProperties", "true"));
+        final @NotNull MockSlingJakartaHttpServletResponse resp = 
context.jakartaResponse();
+        plugin.service(req, resp);
+        final String outputAsString = resp.getOutputAsString();
+        assertNotNull(outputAsString);
+    }
+
+    private void mockComponentDescriptionDTO(String configPid, Map<String, 
Object> properties) {
+        ComponentDescriptionDTO mockDTO = 
Mockito.mock(ComponentDescriptionDTO.class);
+        mockDTO.bundle = Mockito.mock(BundleDTO.class);
+        mockDTO.bundle.id = 1;
+        mockDTO.name = "name1";
+        mockDTO.configurationPid = new String[] {configPid};
+        mockDTO.properties = properties;
+        
Mockito.doReturn(List.of(mockDTO)).when(mockScr).getComponentDescriptionDTOs();
+    }
+
+    private void mockExistingConfiguration(String pid, Map<String, Object> 
props) throws IOException {
+        ConfigurationAdmin configAdmin = 
context.getService(ConfigurationAdmin.class);
+        final Configuration test1Configuration = 
configAdmin.getConfiguration(pid);
+        test1Configuration.update(new Hashtable<>(props));
+    }
+
+    private void mockExistingFactoryConfiguration(String factoryPid, String 
name, Map<String, Object> props)
+            throws IOException {
+        ConfigurationAdmin configAdmin = 
context.getService(ConfigurationAdmin.class);
+        final Configuration test1Configuration = 
configAdmin.getFactoryConfiguration(factoryPid, name);
+        test1Configuration.update(new Hashtable<>(props));
+    }
+
+    private InstallationState mockInstallationState() {
+        InstallationState mockInstallationState = 
Mockito.mock(InstallationState.class);
+        
Mockito.doReturn(mockInstallationState).when(mockInfoProvider).getInstallationState();
+        return mockInstallationState;
+    }
+
+    /**
+     * Test method for {@link 
org.apache.sling.installer.factories.configuration.impl.ConfigurationSerializerWebConsolePlugin#escapeXml(java.lang.String)}.
+     */
+    @Test
+    void testEscapeXml() {
+        assertNull(plugin.escapeXml(null));
+        assertEquals("", plugin.escapeXml(""));
+        assertEquals("&lt;hello/&gt;", plugin.escapeXml("<hello/>"));
+        assertEquals("&quot;dog&apos;s &amp; cat&quot;", 
plugin.escapeXml("\"dog's & cat\""));
+    }
+
+    /**
+     * Test method for {@link 
org.apache.sling.installer.factories.configuration.impl.ConfigurationSerializerWebConsolePlugin#getRelativeResourcePrefix()}.
+     */
+    @Test
+    void testGetRelativeResourcePrefix() {
+        assertNotNull(plugin.getRelativeResourcePrefix());
+    }
+
+    /**
+     * Test method for {@link
+     * 
org.apache.sling.installer.factories.configuration.impl.ConfigurationSerializerWebConsolePlugin#getResource(java.lang.String)}.
+     */
+    @Test
+    void testGetResource() throws SecurityException, IllegalArgumentException {
+        final URL value1 = ReflectionTools.invokeMethodWithReflection(
+                plugin, "getResource", new Class[] {String.class}, URL.class, 
new Object[] {"/invalid"});
+        assertNull(value1);
+
+        final URL value2 = ReflectionTools.invokeMethodWithReflection(
+                plugin, "getResource", new Class[] {String.class}, URL.class, 
new Object[] {
+                    "/osgi-installer-config-printer/res/ui/clipboard.js"
+                });
+        assertNotNull(value2);
+
+        final URL value3 = ReflectionTools.invokeMethodWithReflection(
+                plugin, "getResource", new Class[] {String.class}, URL.class, 
new Object[] {
+                    "/osgi-installer-config-printer/res/ui/invalid.css"
+                });
+        assertNull(value3);
+    }
+}
diff --git 
a/src/test/java/org/apache/sling/installer/factories/configuration/impl/ReflectionTools.java
 
b/src/test/java/org/apache/sling/installer/factories/configuration/impl/ReflectionTools.java
new file mode 100644
index 0000000..4af9453
--- /dev/null
+++ 
b/src/test/java/org/apache/sling/installer/factories/configuration/impl/ReflectionTools.java
@@ -0,0 +1,71 @@
+/*
+ * 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.sling.installer.factories.configuration.impl;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+import static org.junit.jupiter.api.Assertions.fail;
+
+/**
+ * Reflection utilities to facilitate testing
+ */
+public class ReflectionTools {
+
+    private ReflectionTools() {
+        // hide the public constructor
+    }
+
+    public static <T> T invokeMethodWithReflection(
+            Object obj, String methodName, Class<?>[] parameterTypes, Class<T> 
expectedType, Object[] methodArgs) {
+        Object result = null;
+        try {
+            Class<?> clazz = obj.getClass();
+            Method method = null;
+            do {
+                try { // NOSONAR
+                    method = clazz.getDeclaredMethod(methodName, 
parameterTypes);
+                } catch (NoSuchMethodException nsfe) {
+                    clazz = clazz.getSuperclass();
+                }
+            } while (method == null && clazz != null);
+            if (method != null) {
+                if (Modifier.isStatic(method.getModifiers())) {
+                    if (!method.canAccess(null)) {
+                        method.setAccessible(true);
+                    }
+                    result = method.invoke(null, methodArgs);
+                } else {
+                    if (!method.canAccess(obj)) {
+                        method.setAccessible(true);
+                    }
+                    result = method.invoke(obj, methodArgs);
+                }
+            } else {
+                fail("Failed to find method via reflection: " + methodName);
+            }
+        } catch (IllegalArgumentException | IllegalAccessException | 
SecurityException e) {
+            fail("Failed to get method via reflection. Reason: " + 
e.getMessage());
+        } catch (InvocationTargetException e) {
+            fail("Failed to invoke method via reflection. Reason: " + 
e.getMessage());
+        }
+        return expectedType == null ? null : expectedType.cast(result);
+    }
+}

Reply via email to