This is an automated email from the ASF dual-hosted git repository.
enorman pushed a commit to branch master
in repository
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-bundleresource-impl.git
The following commit(s) were added to refs/heads/master by this push:
new 3887fd2 SLING-12979 migrate to Sling API 3.x and Jakarta Servlet (#7)
3887fd2 is described below
commit 3887fd2d58827666e4d713d5ee8e2356e6de5bbc
Author: Eric Norman <[email protected]>
AuthorDate: Tue Nov 4 14:10:03 2025 -0800
SLING-12979 migrate to Sling API 3.x and Jakarta Servlet (#7)
---
pom.xml | 20 +-
.../impl/BundleResourceWebConsolePlugin.java | 67 ++++---
.../impl/BundleResourceWebConsolePluginTest.java | 212 +++++++++++++++++++++
3 files changed, 261 insertions(+), 38 deletions(-)
diff --git a/pom.xml b/pom.xml
index 844ae84..a161919 100644
--- a/pom.xml
+++ b/pom.xml
@@ -27,7 +27,7 @@
</parent>
<artifactId>org.apache.sling.bundleresource.impl</artifactId>
- <version>2.4.1-SNAPSHOT</version>
+ <version>3.0.0-SNAPSHOT</version>
<name>Apache Sling Bundle Resource Provider</name>
<description>Provides a ResourceProvider implementation supporting bundle
@@ -41,18 +41,21 @@
</scm>
<properties>
- <sling.java.version>11</sling.java.version>
+ <sling.java.version>17</sling.java.version>
+ <slf4j.version>2.0.17</slf4j.version>
<project.build.outputTimestamp>2025-03-05T17:41:02Z</project.build.outputTimestamp>
</properties>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
+ <version>${slf4j.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
- <groupId>javax.servlet</groupId>
- <artifactId>javax.servlet-api</artifactId>
+ <groupId>jakarta.servlet</groupId>
+ <artifactId>jakarta.servlet-api</artifactId>
+ <version>6.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
@@ -64,7 +67,7 @@
<dependency>
<groupId>org.apache.sling</groupId>
<artifactId>org.apache.sling.api</artifactId>
- <version>2.25.4</version>
+ <version>3.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
@@ -99,31 +102,30 @@
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
+ <version>${slf4j.version}</version>
<scope>provided</scope>
</dependency>
<!-- Testing -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
- <version>5.10.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
- <version>5.10.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
- <version>5.5.0</version>
+ <version>5.20.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.johnzon</groupId>
<artifactId>johnzon-core</artifactId>
- <version>2.0.1</version>
+ <version>2.0.2</version>
<scope>test</scope>
</dependency>
</dependencies>
diff --git
a/src/main/java/org/apache/sling/bundleresource/impl/BundleResourceWebConsolePlugin.java
b/src/main/java/org/apache/sling/bundleresource/impl/BundleResourceWebConsolePlugin.java
index ecd8073..5a4ed20 100644
---
a/src/main/java/org/apache/sling/bundleresource/impl/BundleResourceWebConsolePlugin.java
+++
b/src/main/java/org/apache/sling/bundleresource/impl/BundleResourceWebConsolePlugin.java
@@ -18,19 +18,19 @@
*/
package org.apache.sling.bundleresource.impl;
-import javax.servlet.Servlet;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
import java.io.IOException;
import java.io.PrintWriter;
-import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.List;
-
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.atomic.AtomicReference;
+
+import jakarta.servlet.Servlet;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
import org.apache.sling.spi.resource.provider.ResourceProvider;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
@@ -45,31 +45,34 @@ class BundleResourceWebConsolePlugin extends HttpServlet {
private static final String LABEL = "bundleresources";
- private volatile ServiceRegistration<Servlet> serviceRegistration;
+ // runtime-only holders — transient so servlet serialization won't try to
persist them
+ private final transient AtomicReference<ServiceRegistration<Servlet>>
serviceRegistration = new AtomicReference<>();
@SuppressWarnings("rawtypes")
- private volatile ServiceTracker<ResourceProvider, ResourceProvider>
providerTracker;
+ private final transient AtomicReference<ServiceTracker<ResourceProvider,
ResourceProvider>> providerTracker =
+ new AtomicReference<>();
- private final List<BundleResourceProvider> provider = new ArrayList<>();
+ // thread-safe list so ServiceTracker callbacks can add/remove while doGet
iterates
+ private final transient List<BundleResourceProvider> provider = new
CopyOnWriteArrayList<>();
// --------- setup and shutdown
- private static BundleResourceWebConsolePlugin INSTANCE;
+ private static BundleResourceWebConsolePlugin instance;
static void initPlugin(BundleContext context) {
- if (INSTANCE == null) {
+ if (instance == null) {
BundleResourceWebConsolePlugin tmp = new
BundleResourceWebConsolePlugin();
tmp.activate(context);
- INSTANCE = tmp;
+ instance = tmp;
}
}
static void destroyPlugin() {
- if (INSTANCE != null) {
+ if (instance != null) {
try {
- INSTANCE.deactivate();
+ instance.deactivate();
} finally {
- INSTANCE = null;
+ instance = null;
}
}
}
@@ -145,7 +148,7 @@ class BundleResourceWebConsolePlugin extends HttpServlet {
@SuppressWarnings("rawtypes")
public void activate(BundleContext context) {
- providerTracker =
+ ServiceTracker<ResourceProvider, ResourceProvider> tracker =
new ServiceTracker<ResourceProvider, ResourceProvider>(
context, ResourceProvider.class.getName(), null) {
@@ -154,8 +157,8 @@ class BundleResourceWebConsolePlugin extends HttpServlet {
ResourceProvider service = null;
if
(reference.getProperty(BundleResourceProvider.PROP_BUNDLE) != null) {
service = super.addingService(reference);
- if (service instanceof BundleResourceProvider) {
- provider.add((BundleResourceProvider) service);
+ if (service instanceof BundleResourceProvider
brpService) {
+ provider.add(brpService);
}
}
return service;
@@ -170,27 +173,33 @@ class BundleResourceWebConsolePlugin extends HttpServlet {
super.removedService(reference, service);
}
};
- providerTracker.open();
+ providerTracker.set(tracker);
+ tracker.open();
- Dictionary<String, Object> props = new Hashtable<>();
+ Dictionary<String, Object> props = new Hashtable<>(); // NOSONAR
props.put(Constants.SERVICE_DESCRIPTION, "Web Console Plugin for
Bundle Resource Providers");
props.put(Constants.SERVICE_VENDOR, "The Apache Software Foundation");
props.put("felix.webconsole.label", LABEL);
props.put("felix.webconsole.title", "Bundle Resource Provider");
props.put("felix.webconsole.category", "Sling");
- serviceRegistration = context.registerService(Servlet.class, this,
props);
+ serviceRegistration.set(context.registerService(Servlet.class, this,
props));
}
public void deactivate() {
- if (serviceRegistration != null) {
- serviceRegistration.unregister();
- serviceRegistration = null;
+ ServiceRegistration<Servlet> sr = serviceRegistration.getAndSet(null);
+ if (sr != null) {
+ try {
+ sr.unregister();
+ } catch (IllegalStateException ise) {
+ // ignore if already unregistered
+ }
}
- if (providerTracker != null) {
- providerTracker.close();
- providerTracker = null;
+ @SuppressWarnings("rawtypes")
+ ServiceTracker<ResourceProvider, ResourceProvider> t =
providerTracker.getAndSet(null);
+ if (t != null) {
+ t.close();
}
}
diff --git
a/src/test/java/org/apache/sling/bundleresource/impl/BundleResourceWebConsolePluginTest.java
b/src/test/java/org/apache/sling/bundleresource/impl/BundleResourceWebConsolePluginTest.java
new file mode 100644
index 0000000..8c9dd23
--- /dev/null
+++
b/src/test/java/org/apache/sling/bundleresource/impl/BundleResourceWebConsolePluginTest.java
@@ -0,0 +1,212 @@
+/*
+ * 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.bundleresource.impl;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.reflect.Field;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+
+import jakarta.servlet.Servlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.apache.sling.spi.resource.provider.ResourceProvider;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+import org.osgi.framework.Bundle;
+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;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Basic unit tests for BundleResourceWebConsolePlugin to exercise activation,
simple doGet rendering,
+ * and deactivation/unregister behavior.
+ *
+ * - Calls initPlugin(BundleContext) with a mocked BundleContext that returns
a mocked ServiceRegistration.
+ * - Reflectively obtains the created plugin instance and invokes doGet to
capture the generated HTML.
+ * - Calls destroyPlugin() and verifies the ServiceRegistration.unregister()
was invoked.
+ */
+class BundleResourceWebConsolePluginTest {
+
+ @Test
+ void testActivateDoGetDeactivate() throws Exception {
+ testPlugin((ctx, reg, plugin) -> {
+ // calling init again should do no additional work
+ BundleResourceWebConsolePlugin.initPlugin(ctx);
+ BundleResourceWebConsolePlugin plugin2 = getInstanceField();
+ assertSame(plugin2, plugin);
+
+ mockBundleResourceProvider(ctx, plugin);
+
+ // Prepare mocks for servlet invocation and capture output
+ HttpServletRequest req = mock(HttpServletRequest.class);
+ HttpServletResponse resp = mock(HttpServletResponse.class);
+
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ when(resp.getWriter()).thenReturn(pw);
+
+ // Invoke doGet which should render an HTML table (even with no
providers)
+ plugin.doGet(req, resp);
+ pw.flush();
+ String output = sw.toString();
+
+ // Assert: basic expected content is present
+ assertTrue(output.contains("Bundle Resource Provider"), "Output
should contain the webconsole title");
+ assertTrue(output.contains("<table"), "Output should contain a
table element");
+
+ // Clean up: destroy plugin (should unregister the service)
+ BundleResourceWebConsolePlugin.destroyPlugin();
+ // calling a second time should do no additional work
+ BundleResourceWebConsolePlugin.destroyPlugin();
+
+ // Verify unregister was called on the registration
+ verify(reg, times(1)).unregister();
+ });
+ }
+
+ @Test
+ void testDeactivateTwice() throws Exception {
+ testPlugin((ctx, reg, plugin) -> {
+ plugin.deactivate();
+ // Verify unregister was called on the registration
+ verify(reg, times(1)).unregister();
+
+ // call deactivate again should do no addiional work
+ Mockito.reset((Object) reg);
+ plugin.deactivate();
+ // Verify unregister was called on the registration
+ verify(reg, times(0)).unregister();
+ });
+ }
+
+ @Test
+ void testDeactivateWithServiceRegAlreadyUnregistered() throws Exception {
+ testPlugin((ctx, reg, plugin) -> {
+
Mockito.doThrow(IllegalStateException.class).when(reg).unregister();
+ plugin.deactivate();
+ // Verify unregister was called on the registration
+ verify(reg, times(1)).unregister();
+ });
+ }
+
+ /**
+ * Simulate a BundleResourceProvider service being registered and added to
the service tracker
+ */
+ private void mockBundleResourceProvider(BundleContext ctx,
BundleResourceWebConsolePlugin plugin)
+ throws NoSuchFieldException, SecurityException,
IllegalArgumentException, IllegalAccessException {
+ @SuppressWarnings("rawtypes")
+ final ServiceTracker<ResourceProvider, ResourceProvider>
providerTracker = getProviderTrackerField(plugin);
+ assertNotNull(providerTracker);
+ final BundleResourceProvider mockResourceProvider1 =
Mockito.mock(BundleResourceProvider.class);
+ BundleResourceCache cache = Mockito.mock(BundleResourceCache.class);
+ Bundle mockBundle = Mockito.mock(Bundle.class);
+ Dictionary<String, Object> headers = new
Hashtable<>(Map.of(Constants.BUNDLE_NAME, "test.bundle1"));
+ Mockito.doReturn(headers).when(mockBundle).getHeaders();
+ Mockito.doReturn(mockBundle).when(cache).getBundle();
+
Mockito.doReturn(cache).when(mockResourceProvider1).getBundleResourceCache();
+
+ PathMapping pathMapping = Mockito.mock(PathMapping.class);
+
Mockito.doReturn(pathMapping).when(mockResourceProvider1).getMappedPath();
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ ServiceReference<ResourceProvider> ref1 = mock(ServiceReference.class);
+
Mockito.doReturn(1L).when(ref1).getProperty(BundleResourceProvider.PROP_BUNDLE);
+ Mockito.doReturn(mockResourceProvider1).when(ctx).getService(ref1);
+ providerTracker.addingService(ref1);
+ }
+
+ /**
+ * Reflectively clear the private static instance so we can start from
scratch
+ */
+ private void clearInstanceField()
+ throws NoSuchFieldException, SecurityException,
IllegalArgumentException, IllegalAccessException {
+ Field instanceField =
BundleResourceWebConsolePlugin.class.getDeclaredField("instance");
+ instanceField.setAccessible(true);
+ instanceField.set(null, null);
+ }
+
+ /**
+ * Reflectively fetch the private static instance so we can call doGet on
it
+ */
+ private BundleResourceWebConsolePlugin getInstanceField()
+ throws NoSuchFieldException, SecurityException,
IllegalArgumentException, IllegalAccessException {
+ Field instanceField =
BundleResourceWebConsolePlugin.class.getDeclaredField("instance");
+ instanceField.setAccessible(true);
+ return (BundleResourceWebConsolePlugin) instanceField.get(null);
+ }
+
+ /**
+ * Reflectively fetch the private static instance so we can call doGet on
it
+ */
+ @SuppressWarnings("rawtypes")
+ private ServiceTracker<ResourceProvider, ResourceProvider>
getProviderTrackerField(
+ BundleResourceWebConsolePlugin plugin)
+ throws NoSuchFieldException, SecurityException,
IllegalArgumentException, IllegalAccessException {
+ Field instanceField =
BundleResourceWebConsolePlugin.class.getDeclaredField("providerTracker");
+ instanceField.setAccessible(true);
+ @SuppressWarnings("unchecked")
+ AtomicReference<ServiceTracker<ResourceProvider, ResourceProvider>>
ref =
+ (AtomicReference<ServiceTracker<ResourceProvider,
ResourceProvider>>) instanceField.get(plugin);
+ return ref.get();
+ }
+
+ @SuppressWarnings("unchecked")
+ protected void testPlugin(PluginWorker worker) throws Exception {
+ BundleContext ctx = mock(BundleContext.class);
+ ServiceRegistration<Servlet> reg = mock(ServiceRegistration.class);
+
+ // registerService should return our mock registration
+ when(ctx.registerService(eq(Servlet.class), any(Servlet.class),
any(Dictionary.class)))
+ .thenReturn(reg);
+
+ // make sure the is nothing left in this field
+ clearInstanceField();
+
+ // Act: initialize plugin (this creates and activates the plugin
instance)
+ BundleResourceWebConsolePlugin.initPlugin(ctx);
+
+ // Reflectively fetch the private static instance so we can call doGet
on it
+ BundleResourceWebConsolePlugin plugin = getInstanceField();
+ // ensure we have a plugin instance
+ assertNotNull(plugin, "Plugin instance should have been created");
+
+ worker.doWork(ctx, reg, plugin);
+ }
+
+ public static interface PluginWorker {
+ public void doWork(BundleContext ctx, ServiceRegistration<Servlet>
reg, BundleResourceWebConsolePlugin plugin)
+ throws Exception;
+ }
+}