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 */
