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

rombert pushed a commit to branch issue/SLING-13398
in repository 
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature-analyser.git

commit 8b2712c21291fffb8c6a0d6e9e8becffdf06dbdb
Author: Robert Munteanu <[email protected]>
AuthorDate: Fri Oct 23 15:34:59 2026 +0200

    SLING-13398 - Feature analyser does not properly handle multi-release jars
---
 pom.xml                                            |  6 ++++
 .../extensions/AnalyserMetaDataHandler.java        |  7 ++--
 .../feature/scanner/impl/BundleDescriptorImpl.java | 42 +++++++++++++++++++++-
 .../scanner/impl/BundleDescriptorImplTest.java     | 30 ++++++++++++++++
 4 files changed, 80 insertions(+), 5 deletions(-)

diff --git a/pom.xml b/pom.xml
index 94f6af4..9e78b3b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -154,6 +154,12 @@
         <!-- END Content-Package check -->
 
         <!-- Testing -->
+        <dependency>
+            <groupId>io.projectreactor</groupId>
+            <artifactId>reactor-core</artifactId>
+            <version>3.8.8</version>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
diff --git 
a/src/main/java/org/apache/sling/feature/analyser/extensions/AnalyserMetaDataHandler.java
 
b/src/main/java/org/apache/sling/feature/analyser/extensions/AnalyserMetaDataHandler.java
index 434c69c..48a867f 100644
--- 
a/src/main/java/org/apache/sling/feature/analyser/extensions/AnalyserMetaDataHandler.java
+++ 
b/src/main/java/org/apache/sling/feature/analyser/extensions/AnalyserMetaDataHandler.java
@@ -28,7 +28,6 @@ import java.util.Optional;
 import java.util.ServiceLoader;
 import java.util.Set;
 import java.util.StringJoiner;
-import java.util.jar.JarFile;
 
 import jakarta.json.Json;
 import jakarta.json.JsonObject;
@@ -44,9 +43,9 @@ import org.apache.sling.feature.Feature;
 import org.apache.sling.feature.builder.HandlerContext;
 import org.apache.sling.feature.builder.PostProcessHandler;
 import org.apache.sling.feature.impl.felix.utils.resource.ResourceUtils;
-import org.apache.sling.feature.io.IOUtils;
 import org.apache.sling.feature.scanner.BundleDescriptor;
 import org.apache.sling.feature.scanner.PackageInfo;
+import org.apache.sling.feature.scanner.impl.BundleDescriptorImpl;
 import org.apache.sling.feature.scanner.impl.SystemBundleDescriptor;
 import org.apache.sling.feature.scanner.spi.FrameworkScanner;
 import org.osgi.framework.Constants;
@@ -196,8 +195,8 @@ public class AnalyserMetaDataHandler implements 
PostProcessHandler {
 
     private Optional<JsonObject> getManifest(HandlerContext handlerContext, 
ArtifactId bundle) {
         URL url = handlerContext.getArtifactProvider().provide(bundle);
-        try (JarFile jarFile = IOUtils.getJarFileFromURL(url, false, null)) {
-            return Optional.ofNullable(jarFile.getManifest()).map(manifest -> {
+        try {
+            return 
Optional.ofNullable(BundleDescriptorImpl.getManifest(url)).map(manifest -> {
                 JsonObjectBuilder manifestBuilder = Json.createObjectBuilder();
                 manifest.getMainAttributes().entrySet().stream()
                         .forEachOrdered(
diff --git 
a/src/main/java/org/apache/sling/feature/scanner/impl/BundleDescriptorImpl.java 
b/src/main/java/org/apache/sling/feature/scanner/impl/BundleDescriptorImpl.java
index e2074d1..772d71f 100644
--- 
a/src/main/java/org/apache/sling/feature/scanner/impl/BundleDescriptorImpl.java
+++ 
b/src/main/java/org/apache/sling/feature/scanner/impl/BundleDescriptorImpl.java
@@ -21,6 +21,7 @@ package org.apache.sling.feature.scanner.impl;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.net.URISyntaxException;
 import java.net.URL;
 import java.nio.file.Files;
@@ -32,6 +33,7 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.StringTokenizer;
+import java.util.jar.JarEntry;
 import java.util.jar.JarFile;
 import java.util.jar.Manifest;
 import java.util.stream.Collectors;
@@ -74,7 +76,20 @@ public class BundleDescriptorImpl extends BundleDescriptor {
     /** The corresponding artifact from the feature. */
     private final Artifact artifact;
 
-    static Manifest getManifest(URL file) throws IOException {
+    /**
+     * Read the bundle manifest, applying the OSGi multi-release JAR 
supplemental
+     * manifest for the running Java platform, if present.
+     * @param file The bundle URL
+     * @return The effective manifest, or {@code null} if there is no manifest
+     * @throws IOException If the manifest cannot be read
+     */
+    public static Manifest getManifest(URL file) throws IOException {
+        String javaVersion = System.getProperty("java.specification.version");
+        int majorVersion = Integer.parseInt(javaVersion.startsWith("1.") ? 
javaVersion.substring(2) : javaVersion);
+        return getManifest(file, majorVersion);
+    }
+
+    static Manifest getManifest(URL file, int javaVersion) throws IOException {
         Manifest manifest = null;
         try {
             if ("file".equals(file.getProtocol()) && new 
File(file.toURI()).isDirectory()) {
@@ -88,6 +103,31 @@ public class BundleDescriptorImpl extends BundleDescriptor {
             } else {
                 try (JarFile jarFile = IOUtils.getJarFileFromURL(file, true, 
null)) {
                     manifest = jarFile.getManifest();
+                    if (manifest != null
+                            && "true"
+                                    .equalsIgnoreCase(
+                                            
manifest.getMainAttributes().getValue("Multi-Release"))) {
+                        // OSGi Core 3.2.8: use only the first supplemental 
manifest found,
+                        // and replace only Import-Package and 
Require-Capability.
+                        for (int version = javaVersion; version >= 9; 
version--) {
+                            JarEntry entry =
+                                    jarFile.getJarEntry("META-INF/versions/" + 
version + "/OSGI-INF/MANIFEST.MF");
+                            if (entry != null) {
+                                try (InputStream is = 
jarFile.getInputStream(entry)) {
+                                    Manifest supplemental = new Manifest(is);
+                                    for (String header :
+                                            new String[] 
{Constants.IMPORT_PACKAGE, Constants.REQUIRE_CAPABILITY}) {
+                                        String value =
+                                                
supplemental.getMainAttributes().getValue(header);
+                                        if (value != null) {
+                                            
manifest.getMainAttributes().putValue(header, value);
+                                        }
+                                    }
+                                }
+                                break;
+                            }
+                        }
+                    }
                 } catch (final IOException ioe) {
                     // rethrow with more info
                     throw new IOException(file + " : " + ioe.getMessage(), 
ioe);
diff --git 
a/src/test/java/org/apache/sling/feature/scanner/impl/BundleDescriptorImplTest.java
 
b/src/test/java/org/apache/sling/feature/scanner/impl/BundleDescriptorImplTest.java
index 46b7bb7..f5e4547 100644
--- 
a/src/test/java/org/apache/sling/feature/scanner/impl/BundleDescriptorImplTest.java
+++ 
b/src/test/java/org/apache/sling/feature/scanner/impl/BundleDescriptorImplTest.java
@@ -39,7 +39,11 @@ import org.apache.sling.feature.scanner.FeatureDescriptor;
 import org.apache.sling.feature.scanner.PackageInfo;
 import org.apache.sling.feature.scanner.Scanner;
 import org.junit.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.osgi.framework.Constants;
 import org.osgi.framework.Version;
+import reactor.core.Disposable;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
@@ -91,6 +95,32 @@ public class BundleDescriptorImplTest {
                         .getValue("Manifest-Version"));
     }
 
+    @ParameterizedTest(name = "Java {0}")
+    @CsvSource({"8, 1.8, true", "21, 21, false"})
+    public void testReactorMultiReleaseBundle(
+            int javaVersion, String requiredExecutionEnvironmentVersion, 
boolean importsSunMisc) throws Exception {
+        URL jar = 
Disposable.class.getProtectionDomain().getCodeSource().getLocation();
+        BundleDescriptorImpl descriptor = new BundleDescriptorImpl(
+                new 
Artifact(ArtifactId.fromMvnId("io.projectreactor:reactor-core:3.8.8")),
+                jar,
+                null,
+                BundleDescriptorImpl.getManifest(jar, javaVersion));
+
+        assertEquals(importsSunMisc, 
descriptor.getImportedPackages().stream().anyMatch(p -> "sun.misc"
+                .equals(p.getName())));
+        assertEquals(
+                "osgi.ee;filter:=\"(&(osgi.ee=JavaSE)(version=" + 
requiredExecutionEnvironmentVersion + "))\"",
+                
descriptor.getManifest().getMainAttributes().getValue(Constants.REQUIRE_CAPABILITY));
+        assertEquals(
+                "(&(osgi.ee=JavaSE)(version=" + 
requiredExecutionEnvironmentVersion + "))",
+                descriptor.getRequirements().stream()
+                        .filter(r -> "osgi.ee".equals(r.getNamespace()))
+                        .findFirst()
+                        .get()
+                        .getDirectives()
+                        .get("filter"));
+    }
+
     /**
      * for SLING-10182
      */

Reply via email to