You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by kw...@apache.org on 2022/11/15 11:20:26 UTC

[sling-org-apache-sling-tooling-support-source] 01/01: SLING-11676 Migrate to OSGi Whiteboard

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

kwin pushed a commit to branch feature/osgi-http-whiteboard
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-tooling-support-source.git

commit 84fe1ccd35a47a4e5a3db4c6d4becc0184ff2b83
Author: Konrad Windszus <kw...@apache.org>
AuthorDate: Tue Nov 15 12:20:20 2022 +0100

    SLING-11676 Migrate to OSGi Whiteboard
    
    Add tests
    Update dependencies
---
 pom.xml                                            |  57 ++++++--
 .../impl/FelixJettySourceReferenceFinder.java      |  37 ++---
 .../source/impl/SourceReferencesServlet.java       | 151 ++++++++++-----------
 .../support/source/impl/PomHandlerTest.java        |  15 +-
 .../source/impl/SourceReferencesServletTest.java   |  77 +++++++++++
 .../support/source/impl/expectedBundleSource.json  |   1 +
 .../tooling/support/source/impl/pom.properties     |   3 +
 7 files changed, 222 insertions(+), 119 deletions(-)

diff --git a/pom.xml b/pom.xml
index abc448f..708ef0c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,14 +23,14 @@
     <parent>
         <groupId>org.apache.sling</groupId>
         <artifactId>sling-bundle-parent</artifactId>
-        <version>48</version>
+        <version>49</version>
     </parent>
 
     <artifactId>org.apache.sling.tooling.support.source</artifactId>
     <version>1.0.5-SNAPSHOT</version>
 
     <name>Apache Sling Tooling Support Source</name>
-
+    <description>Outputs source information about bundles running in a Sling instance</description>
     <scm>
         <connection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-tooling-support-source.git</connection>
         <developerConnection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-tooling-support-source.git</developerConnection>
@@ -40,7 +40,12 @@
     <dependencies>
         <dependency>
             <groupId>org.osgi</groupId>
-            <artifactId>osgi.core</artifactId>
+            <artifactId>org.osgi.framework</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.resource</artifactId>
             <scope>provided</scope>
         </dependency>
         <dependency>
@@ -48,15 +53,21 @@
             <artifactId>org.osgi.service.component.annotations</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.service.http.whiteboard</artifactId>
+            <scope>provided</scope>
+        </dependency>
         <dependency>
             <groupId>org.apache.felix</groupId>
             <artifactId>org.apache.felix.utils</artifactId>
-            <version>1.9.0</version>
+            <version>1.11.8</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
+            <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>javax.servlet</groupId>
@@ -64,15 +75,41 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
-            <groupId>junit</groupId>
-            <artifactId>junit</artifactId>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter</artifactId>
+            <version>5.9.1</version>
             <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>commons-io</groupId>
-            <artifactId>commons-io</artifactId>
-            <version>2.4</version>
-            <scope>provided</scope>
+          <groupId>org.mockito</groupId>
+          <artifactId>mockito-core</artifactId>
+          <version>4.8.1</version>
+          <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-junit-jupiter</artifactId>
+            <version>4.8.1</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <scope>test</scope>
         </dependency>
     </dependencies>
+    
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.rat</groupId>
+                <artifactId>apache-rat-plugin</artifactId>
+                <configuration>
+                    <excludes combine.children="append">
+                        <exclude>src/test/resources/**/*.properties</exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
 </project>
diff --git a/src/main/java/org/apache/sling/tooling/support/source/impl/FelixJettySourceReferenceFinder.java b/src/main/java/org/apache/sling/tooling/support/source/impl/FelixJettySourceReferenceFinder.java
index e9be471..46c9865 100644
--- a/src/main/java/org/apache/sling/tooling/support/source/impl/FelixJettySourceReferenceFinder.java
+++ b/src/main/java/org/apache/sling/tooling/support/source/impl/FelixJettySourceReferenceFinder.java
@@ -27,7 +27,6 @@ import javax.xml.parsers.ParserConfigurationException;
 import javax.xml.parsers.SAXParser;
 import javax.xml.parsers.SAXParserFactory;
 
-import org.apache.commons.io.IOUtils;
 import org.osgi.framework.Bundle;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -37,12 +36,16 @@ import org.xml.sax.SAXException;
 public class FelixJettySourceReferenceFinder implements SourceReferenceFinder {
 
     private final Logger log = LoggerFactory.getLogger(getClass());
-
+    private final SAXParserFactory parserFactory;
+    
+    public FelixJettySourceReferenceFinder() {
+        parserFactory = SAXParserFactory.newInstance();
+    }
     @Override
     public List<SourceReference> findSourceReferences(Bundle bundle) throws SourceReferenceException {
         // the org.apache.felix.http.jetty bundle does not retain references to the source bundles
         // so infer them from the X-Jetty-Version header
-        if ( !bundle.getSymbolicName().equals("org.apache.felix.http.jetty")) {
+        if (!"org.apache.felix.http.jetty".equals(bundle.getSymbolicName())) {
             return Collections.emptyList();
         }
             
@@ -54,30 +57,14 @@ public class FelixJettySourceReferenceFinder implements SourceReferenceFinder {
         Enumeration<URL> entries = bundle.findEntries("META-INF/maven", "pom.xml", true);
         if (entries != null && entries.hasMoreElements()) {
             URL entry = entries.nextElement();
-            InputStream pom = null;
-            try {
-                pom = entry.openStream();
-                
-                try {
-                    SAXParserFactory parserFactory = SAXParserFactory.newInstance();
-                    SAXParser parser = parserFactory.newSAXParser();
-                    PomHandler handler = new PomHandler((String) jettyVersion);
-                    parser.parse(new InputSource(pom), handler);
-                    
-                    return handler.getReferences();
-                } catch (SAXException e) {
-                    throw new SourceReferenceException(e);
-                } catch (ParserConfigurationException e) {
-                    throw new SourceReferenceException(e);
-                } finally {
-                    IOUtils.closeQuietly(pom);
-                }
-            } catch (IOException e) {
+            try (InputStream pom = entry.openStream()) {
+                SAXParser parser = parserFactory.newSAXParser();
+                PomHandler handler = new PomHandler((String) jettyVersion);
+                parser.parse(new InputSource(pom), handler);
+                return handler.getReferences();
+            } catch (SAXException|ParserConfigurationException|IOException e) {
                 throw new SourceReferenceException(e);
-            } finally {
-                IOUtils.closeQuietly(pom);
             }
-            
         } else {
             log.warn("Could not find a pom.xml in bundle '{}'!", bundle);
             return Collections.emptyList();
diff --git a/src/main/java/org/apache/sling/tooling/support/source/impl/SourceReferencesServlet.java b/src/main/java/org/apache/sling/tooling/support/source/impl/SourceReferencesServlet.java
index bf9d570..92626ef 100644
--- a/src/main/java/org/apache/sling/tooling/support/source/impl/SourceReferencesServlet.java
+++ b/src/main/java/org/apache/sling/tooling/support/source/impl/SourceReferencesServlet.java
@@ -18,6 +18,7 @@ package org.apache.sling.tooling.support.source.impl;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.Writer;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -33,29 +34,27 @@ import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.apache.commons.io.IOUtils;
 import org.apache.felix.utils.json.JSONWriter;
 import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
 import org.osgi.framework.wiring.BundleRevision;
-import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
+import org.osgi.service.http.whiteboard.propertytypes.HttpWhiteboardServletPattern;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
  * The <tt>SourceReferencesServlet</tt> infers and outputs source information about bundles running a Sling instance
  */
-@Component(service = Servlet.class,
-    property = {
-            Constants.SERVICE_VENDOR + "=The Apache Software Foundation",
-            "alias=/system/sling/tooling/sourceReferences.json"
-    })
+@Component(service = Servlet.class)
+@HttpWhiteboardServletPattern("/system/sling/tooling/sourceReferences.json")
 public class SourceReferencesServlet extends HttpServlet {
 
     private static final long serialVersionUID = 1L;
 
-    private final Logger log = LoggerFactory.getLogger(getClass());
+    private static final Logger log = LoggerFactory.getLogger(SourceReferencesServlet.class);
 
     private static final String KEY_TYPE = "__type__";
     private static final String KEY_GROUP_ID = "groupId";
@@ -67,10 +66,11 @@ public class SourceReferencesServlet extends HttpServlet {
     private static final String FELIX_FW_GROUP_ID = "org.apache.felix";
     private static final String FELIX_FW_ARTIFACT_ID = "org.apache.felix.framework";
 
-    private ComponentContext ctx;
-    private List<SourceReferenceFinder> finders;
+    private final BundleContext ctx;
+    private final List<SourceReferenceFinder> finders;
 
-    protected void activate(ComponentContext ctx) {
+    @Activate
+    public SourceReferencesServlet(BundleContext ctx) {
         this.ctx = ctx;
 
         finders = new ArrayList<>();
@@ -81,87 +81,87 @@ public class SourceReferencesServlet extends HttpServlet {
     protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
         try {
             response.setContentType("application/json");
+            writeBundleSourceJson(response.getWriter(), ctx.getBundles());
+        } catch (IOException e) {
+            throw new ServletException(e);
+        }
+    }
 
-            final JSONWriter w = new JSONWriter(response.getWriter());
-            w.array();
+    void writeBundleSourceJson(Writer writer, Bundle... bundles) throws IOException {
+        final JSONWriter w = new JSONWriter(writer);
+        w.array();
 
-            for ( Bundle bundle : ctx.getBundleContext().getBundles() ) {
+        for (Bundle bundle : bundles) {
 
-                // skip bundle if it is a fragment (http://stackoverflow.com/questions/11655295/using-the-osgi-api-how-do-i-find-out-if-a-given-bundle-is-a-fragment)
-                if ((bundle.adapt(BundleRevision.class).getTypes() & BundleRevision.TYPE_FRAGMENT) != 0) {
-                    log.debug("Skip bundle '{}' because it is a fragment", bundle);
-                    // source references should only be listed with the host bundle
-                    continue;
-                }
-                Object bundleVersion = bundle.getHeaders().get(Constants.BUNDLE_VERSION);
+            // skip bundle if it is a fragment (http://stackoverflow.com/questions/11655295/using-the-osgi-api-how-do-i-find-out-if-a-given-bundle-is-a-fragment)
+            if ((bundle.adapt(BundleRevision.class).getTypes() & BundleRevision.TYPE_FRAGMENT) != 0) {
+                log.debug("Skip bundle '{}' because it is a fragment", bundle);
+                // source references should only be listed with the host bundle
+                continue;
+            }
+            Object bundleVersion = bundle.getVersion();
 
-                w.object();
-                w.key(Constants.BUNDLE_SYMBOLICNAME);
-                w.value(bundle.getSymbolicName());
-                w.key(Constants.BUNDLE_VERSION);
-                w.value(bundleVersion);
+            w.object();
+            w.key(Constants.BUNDLE_SYMBOLICNAME);
+            w.value(bundle.getSymbolicName());
+            w.key(Constants.BUNDLE_VERSION);
+            w.value(bundleVersion);
 
-                w.key("sourceReferences");
-                w.array();
+            w.key("sourceReferences");
+            w.array();
 
-                // the system bundle is embedded by the launchpad jar so we need special handling
-                // since the pom.properties file is not located in the bundle
-                if ( bundle.getBundleId() == 0 && bundle.getSymbolicName().equals(FELIX_FW_ARTIFACT_ID) ) {
-                    writeMavenGav(w, FELIX_FW_GROUP_ID, FELIX_FW_ARTIFACT_ID, (String) bundleVersion);
-                }
+            // the system bundle is embedded by the launchpad jar so we need special handling
+            // since the pom.properties file is not located in the bundle
+            if ( bundle.getBundleId() == Constants.SYSTEM_BUNDLE_ID && FELIX_FW_ARTIFACT_ID.equals(bundle.getSymbolicName()) ) {
+                writeMavenGav(w, FELIX_FW_GROUP_ID, FELIX_FW_ARTIFACT_ID, (String) bundleVersion);
+            }
 
-                // look for pom.properties in the bundle ( the original bundle, fragments )
-                collectMavenSourceReferences(w, bundle);
+            // look for pom.properties in the bundle ( the original bundle, fragments )
+            collectMavenSourceReferences(w, bundle);
 
-                // look for pom.properties in jars embedded in the bundle
-                for ( String jar : getEmbeddedJars(bundle)) {
-                    URL entry = bundle.getEntry(jar);
-                    // incorrect or inaccessible entry
-                    if ( entry == null ) {
-                        continue;
-                    }
-                    collectMavenSourceRerefences(w, entry);
+            // look for pom.properties in jars embedded in the bundle
+            for ( String jar : getEmbeddedJars(bundle)) {
+                URL entry = bundle.getEntry(jar);
+                // incorrect or inaccessible entry
+                if ( entry == null ) {
+                    continue;
                 }
+                collectMavenSourceReferences(w, entry);
+            }
 
-                // query custom finders for source references
-                for ( SourceReferenceFinder finder : finders ) {
-                    try {
-                        for ( SourceReference reference : finder.findSourceReferences(bundle)) {
-                            log.debug("{} found reference {}:{}:{} in {}", new Object[] { finder, reference.getGroupId(), reference.getArtifactId(), reference.getVersion(), bundle});
-                            writeMavenGav(w, reference.getGroupId(), reference.getArtifactId(), reference.getVersion());
-                        }
-                    } catch (SourceReferenceException e) {
-                        log.warn(finder + " execution did not complete normally for " + bundle, e);
+            // query custom finders for source references
+            for ( SourceReferenceFinder finder : finders ) {
+                try {
+                    for ( SourceReference reference : finder.findSourceReferences(bundle)) {
+                        log.debug("{} found reference {}:{}:{} in {}", new Object[] { finder, reference.getGroupId(), reference.getArtifactId(), reference.getVersion(), bundle});
+                        writeMavenGav(w, reference.getGroupId(), reference.getArtifactId(), reference.getVersion());
                     }
+                } catch (SourceReferenceException e) {
+                    log.warn(finder + " execution did not complete normally for " + bundle, e);
                 }
-
-                w.endArray();
-                w.endObject();
             }
 
             w.endArray();
-        } catch (IOException e) {
-            throw new ServletException(e);
+            w.endObject();
         }
+
+        w.endArray();
     }
 
-    private void collectMavenSourceReferences(JSONWriter w, Bundle bundle) throws IOException {
+    private static void collectMavenSourceReferences(JSONWriter w, Bundle bundle) throws IOException {
 
         Enumeration<?> entries = bundle.findEntries("/META-INF/maven", "pom.properties", true);
 
         while ( entries != null && entries.hasMoreElements()) {
             URL entry = (URL) entries.nextElement();
 
-            InputStream in = entry.openStream();
-            try {
+            try (InputStream in = entry.openStream()) {
                 writeMavenGav(w, in);
-            } finally {
-                IOUtils.closeQuietly(in);
             }
         }
     }
 
-    private void writeMavenGav(JSONWriter w, String groupId, String artifactId, String version) throws IOException {
+    private static void writeMavenGav(JSONWriter w, String groupId, String artifactId, String version) throws IOException {
 
         w.object();
         w.key(KEY_TYPE).value(VALUE_TYPE_MAVEN);
@@ -171,7 +171,7 @@ public class SourceReferencesServlet extends HttpServlet {
         w.endObject();
     }
 
-    private void writeMavenGav(JSONWriter w, InputStream in) throws IOException {
+    private static void writeMavenGav(JSONWriter w, InputStream in) throws IOException {
 
         Properties p = new Properties();
         p.load(in);
@@ -183,18 +183,18 @@ public class SourceReferencesServlet extends HttpServlet {
         w.endObject();
     }
 
-    private List<String> getEmbeddedJars(Bundle bundle) {
+    private static List<String> getEmbeddedJars(Bundle bundle) {
 
-        String classPath = (String) bundle.getHeaders().get(Constants.BUNDLE_CLASSPATH);
-        if ( classPath == null ) {
+        String classPath = bundle.getHeaders().get(Constants.BUNDLE_CLASSPATH);
+        if (classPath == null) {
             return Collections.emptyList();
         }
 
         List<String> embeddedJars = new ArrayList<>();
 
         String[] classPathEntryNames = classPath.split("\\,");
-        for ( String classPathEntry : classPathEntryNames ) {
-            if ( classPathEntry.endsWith(".jar")) {
+        for (String classPathEntry : classPathEntryNames) {
+            if (classPathEntry.endsWith(".jar")) {
                 embeddedJars.add(classPathEntry);
             }
         }
@@ -202,20 +202,17 @@ public class SourceReferencesServlet extends HttpServlet {
         return embeddedJars;
     }
 
-    private void collectMavenSourceRerefences(JSONWriter w, URL entry) throws IOException {
+    private static void collectMavenSourceReferences(JSONWriter w, URL entry) throws IOException {
 
-        InputStream wrappedIn = entry.openStream();
-        try {
-            JarInputStream jarIs = new JarInputStream(wrappedIn);
+        try (InputStream wrappedIn = entry.openStream();
+            JarInputStream jarIs = new JarInputStream(wrappedIn)) {
             JarEntry jarEntry;
-            while ( ( jarEntry = jarIs.getNextJarEntry()) != null ) {
+            while ((jarEntry = jarIs.getNextJarEntry()) != null) {
                 String entryName = jarEntry.getName();
-                if ( entryName.startsWith("META-INF/maven/") && entryName.endsWith("/pom.properties")) {
+                if (entryName.startsWith("META-INF/maven/") && entryName.endsWith("/pom.properties")) {
                     writeMavenGav(w, jarIs);
                 }
             }
-        } finally {
-            IOUtils.closeQuietly(wrappedIn);
         }
     }
 
diff --git a/src/test/java/org/apache/sling/tooling/support/source/impl/PomHandlerTest.java b/src/test/java/org/apache/sling/tooling/support/source/impl/PomHandlerTest.java
index 15b73e0..11b553b 100644
--- a/src/test/java/org/apache/sling/tooling/support/source/impl/PomHandlerTest.java
+++ b/src/test/java/org/apache/sling/tooling/support/source/impl/PomHandlerTest.java
@@ -16,26 +16,27 @@
  */
 package org.apache.sling.tooling.support.source.impl;
 
-import static org.junit.Assert.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
 
 import java.io.IOException;
+import java.io.InputStream;
 import java.util.List;
 
 import javax.xml.parsers.ParserConfigurationException;
 import javax.xml.parsers.SAXParserFactory;
 
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
 import org.xml.sax.SAXException;
 
-public class PomHandlerTest {
+class PomHandlerTest {
 
     @Test
-    public void parseJettyPom() throws SAXException, IOException, ParserConfigurationException {
+     void parseJettyPom() throws SAXException, IOException, ParserConfigurationException {
         
         PomHandler handler = new PomHandler("9.2.4");
-        
-        SAXParserFactory.newInstance().newSAXParser().parse(getClass().getResourceAsStream("felix-pom.xml"), handler);
-        
+        try (InputStream input = getClass().getResourceAsStream("felix-pom.xml")) {
+            SAXParserFactory.newInstance().newSAXParser().parse(input, handler);
+        }
         List<SourceReference> references = handler.getReferences();
         
         assertEquals(10, references.size());
diff --git a/src/test/java/org/apache/sling/tooling/support/source/impl/SourceReferencesServletTest.java b/src/test/java/org/apache/sling/tooling/support/source/impl/SourceReferencesServletTest.java
new file mode 100644
index 0000000..40da3b4
--- /dev/null
+++ b/src/test/java/org/apache/sling/tooling/support/source/impl/SourceReferencesServletTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.tooling.support.source.impl;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.StringWriter;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.stream.Collectors;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.wiring.BundleRevision;
+
+@ExtendWith(MockitoExtension.class)
+class SourceReferencesServletTest {
+
+    @SuppressWarnings("unchecked")
+    @Test
+    void testWriteBundleSourceJson() throws IOException {
+        Bundle bundle1 = Mockito.mock(Bundle.class);
+        BundleRevision bundleRevision = Mockito.mock(BundleRevision.class);
+        Mockito.when(bundle1.adapt(BundleRevision.class)).thenReturn(bundleRevision);
+        Dictionary<String, String> dictionary = Mockito.mock(Dictionary.class);
+        Mockito.when(bundle1.getHeaders()).thenReturn(dictionary);
+        BundleContext ctx = Mockito.mock(BundleContext.class);
+        URLConnection mockUrlConnection = Mockito.mock(URLConnection.class);
+        URLStreamHandler stubUrlHandler = new URLStreamHandler() {
+            @Override
+             protected URLConnection openConnection(URL u) throws IOException {
+                return mockUrlConnection;
+             }
+        };
+        URL url = new URL("foo", "bar", 99, "/foobar", stubUrlHandler);
+        Mockito.when(mockUrlConnection.getInputStream()).thenReturn(this.getClass().getResourceAsStream("pom.properties"));
+        Mockito.when(bundle1.findEntries("/META-INF/maven", "pom.properties", true)).thenReturn(Collections.enumeration(Collections.singleton(url)));
+
+        SourceReferencesServlet servlet = new SourceReferencesServlet(ctx);
+        StringWriter writer = new StringWriter();
+        servlet.writeBundleSourceJson(writer, bundle1);
+        String expectedOutput;
+        try (InputStream input = this.getClass().getResourceAsStream("expectedBundleSource.json");
+             BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {
+            // normalize line separators to system default
+            expectedOutput = reader.lines().collect(Collectors.joining(System.lineSeparator()));
+        }
+        assertEquals(expectedOutput, writer.toString());
+    }
+
+}
diff --git a/src/test/resources/org/apache/sling/tooling/support/source/impl/expectedBundleSource.json b/src/test/resources/org/apache/sling/tooling/support/source/impl/expectedBundleSource.json
new file mode 100644
index 0000000..ca01f19
--- /dev/null
+++ b/src/test/resources/org/apache/sling/tooling/support/source/impl/expectedBundleSource.json
@@ -0,0 +1 @@
+[{"Bundle-SymbolicName":null,"Bundle-Version":null,"sourceReferences":[{"__type__":"maven","groupId":"com.example.myGroupId","artifactId":"myArtifactId","version":"1.0.0"}]}]
\ No newline at end of file
diff --git a/src/test/resources/org/apache/sling/tooling/support/source/impl/pom.properties b/src/test/resources/org/apache/sling/tooling/support/source/impl/pom.properties
new file mode 100644
index 0000000..8377447
--- /dev/null
+++ b/src/test/resources/org/apache/sling/tooling/support/source/impl/pom.properties
@@ -0,0 +1,3 @@
+groupId=com.example.myGroupId
+artifactId=myArtifactId
+version=1.0.0
\ No newline at end of file