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

cziegeler pushed a commit to branch master
in repository 
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature-analyser.git


The following commit(s) were added to refs/heads/master by this push:
     new 301fcfa  SLING-13427 : Export / import analysers should take 
bootdelegation into account
301fcfa is described below

commit 301fcfae7ff46bc0b39c26b91de4bd364e42bb46
Author: Carsten Ziegeler <[email protected]>
AuthorDate: Fri Nov 13 08:50:38 2026 +0100

    SLING-13427 : Export / import analysers should take bootdelegation into 
account
---
 .../task/impl/CheckBundleExportsImports.java       | 23 +++++++
 .../task/impl/CheckBundleExportsImportsTest.java   | 72 ++++++++++++++++++++++
 2 files changed, 95 insertions(+)

diff --git 
a/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckBundleExportsImports.java
 
b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckBundleExportsImports.java
index efe13ff..fe6c7fa 100644
--- 
a/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckBundleExportsImports.java
+++ 
b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckBundleExportsImports.java
@@ -30,6 +30,7 @@ import org.apache.sling.feature.analyser.task.AnalyserTask;
 import org.apache.sling.feature.analyser.task.AnalyserTaskContext;
 import org.apache.sling.feature.scanner.BundleDescriptor;
 import org.apache.sling.feature.scanner.PackageInfo;
+import org.osgi.framework.Constants;
 
 public class CheckBundleExportsImports implements AnalyserTask {
 
@@ -62,6 +63,10 @@ public class CheckBundleExportsImports implements 
AnalyserTask {
     public void execute(final AnalyserTaskContext ctx) throws IOException {
         // basic checks
         final Map<BundleDescriptor, Report> reports = new HashMap<>();
+        final String[] bootDelegation = ctx.getFeature()
+                .getFrameworkProperties()
+                .getOrDefault(Constants.FRAMEWORK_BOOTDELEGATION, "")
+                .split(",");
 
         final SortedMap<Integer, List<BundleDescriptor>> bundlesMap = new 
TreeMap<>();
         for (final BundleDescriptor bi : 
ctx.getFeatureDescriptor().getBundleDescriptors()) {
@@ -88,6 +93,9 @@ public class CheckBundleExportsImports implements 
AnalyserTask {
                 for (final PackageInfo pck : info.getImportedPackages()) {
                     final List<BundleDescriptor> candidates = 
getCandidates(exportingBundles, pck);
                     if (candidates.isEmpty()) {
+                        if (isBootDelegated(bootDelegation, pck.getName())) {
+                            continue;
+                        }
                         if (pck.isOptional()) {
                             getReport(reports, 
info).missingExportsForOptional.add(pck);
                         } else {
@@ -101,6 +109,9 @@ public class CheckBundleExportsImports implements 
AnalyserTask {
                             }
                         }
                         if (matchingCandidates.isEmpty()) {
+                            if (isBootDelegated(bootDelegation, 
pck.getName())) {
+                                continue;
+                            }
                             if (pck.isOptional()) {
                                 getReport(reports, info)
                                         .missingExportsForOptional
@@ -145,6 +156,18 @@ public class CheckBundleExportsImports implements 
AnalyserTask {
         }
     }
 
+    private boolean isBootDelegated(final String[] bootDelegation, final 
String packageName) {
+        for (final String entry : bootDelegation) {
+            final String pattern = entry.trim();
+            if ("*".equals(pattern)
+                    || packageName.equals(pattern)
+                    || (pattern.endsWith(".*") && 
packageName.startsWith(pattern.substring(0, pattern.length() - 1)))) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private String getPackageInfo(final List<PackageInfo> pcks, final boolean 
includeVersion) {
         if (pcks.size() == 1) {
             final StringBuilder sb = new StringBuilder("package ");
diff --git 
a/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckBundleExportsImportsTest.java
 
b/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckBundleExportsImportsTest.java
index 7a068d5..72f7a23 100644
--- 
a/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckBundleExportsImportsTest.java
+++ 
b/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckBundleExportsImportsTest.java
@@ -21,6 +21,7 @@ package org.apache.sling.feature.analyser.task.impl;
 import java.io.File;
 import java.io.IOException;
 import java.util.Collections;
+import java.util.jar.Manifest;
 
 import org.apache.sling.feature.Artifact;
 import org.apache.sling.feature.ArtifactId;
@@ -36,6 +37,7 @@ import 
org.apache.sling.feature.scanner.impl.FeatureDescriptorImpl;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.mockito.Mockito;
+import org.osgi.framework.Constants;
 
 import static org.junit.Assert.assertEquals;
 
@@ -157,6 +159,76 @@ public class CheckBundleExportsImportsTest {
         Mockito.verify(ctx, 
Mockito.never()).reportWarning(Mockito.anyString());
     }
 
+    @Test
+    public void testBootDelegationMatches() throws IOException {
+        for (String bootDelegation : new String[] {
+            "org.foo.bar", "org.foo.*", "org.*", "*", " org.other , 
org.foo.bar ", "org.other, org.foo.*"
+        }) {
+            AnalyserTaskContext ctx = bootDelegationContext(bootDelegation, 
null);
+
+            new CheckBundleExportsImports().execute(ctx);
+
+            Mockito.verify(ctx, 
Mockito.never()).reportArtifactError(Mockito.any(), Mockito.anyString());
+            Mockito.verify(ctx, 
Mockito.never()).reportError(Mockito.anyString());
+        }
+    }
+
+    @Test
+    public void testBootDelegationDoesNotMatch() throws IOException {
+        for (String bootDelegation : new String[] {
+            null, "", " ", "org.other", "org.foo", "org.foo.bar.*", 
"org.fo.*", "org.foo.b*", "org.foo.bar.other"
+        }) {
+            AnalyserTaskContext ctx = bootDelegationContext(bootDelegation, 
null);
+
+            new CheckBundleExportsImports().execute(ctx);
+
+            Mockito.verify(ctx).reportArtifactError(Mockito.any(), 
Mockito.contains("org.foo.bar"));
+            Mockito.verify(ctx).reportError(Mockito.contains("marked as 
'complete'"));
+        }
+    }
+
+    @Test
+    public void testBootDelegationWithExistingExport() throws IOException {
+        for (String exportVersion : new String[] {"1.0", "2.0"}) {
+            for (String bootDelegation : new String[] {"org.foo.*", 
"org.other"}) {
+                AnalyserTaskContext ctx = 
bootDelegationContext(bootDelegation, exportVersion);
+
+                new CheckBundleExportsImports().execute(ctx);
+
+                if ("1.0".equals(exportVersion) && 
"org.other".equals(bootDelegation)) {
+                    Mockito.verify(ctx).reportArtifactError(Mockito.any(), 
Mockito.contains("required version range"));
+                    Mockito.verify(ctx).reportError(Mockito.contains("marked 
as 'complete'"));
+                } else {
+                    Mockito.verify(ctx, 
Mockito.never()).reportArtifactError(Mockito.any(), Mockito.anyString());
+                    Mockito.verify(ctx, 
Mockito.never()).reportError(Mockito.anyString());
+                }
+            }
+        }
+    }
+
+    private AnalyserTaskContext bootDelegationContext(String bootDelegation, 
String exportVersion) throws IOException {
+        Feature f = new Feature(ArtifactId.fromMvnId("f:f:1"));
+        f.setComplete(true);
+        if (bootDelegation != null) {
+            f.getFrameworkProperties().put(Constants.FRAMEWORK_BOOTDELEGATION, 
bootDelegation);
+        }
+        FeatureDescriptor fd = new FeatureDescriptorImpl(f);
+        Manifest manifest = new Manifest();
+        
manifest.getMainAttributes().putValue(Constants.BUNDLE_MANIFESTVERSION, "2");
+        manifest.getMainAttributes().putValue(Constants.BUNDLE_SYMBOLICNAME, 
"test-bundle");
+        manifest.getMainAttributes().putValue(Constants.BUNDLE_VERSION, 
"1.0.0");
+        manifest.getMainAttributes().putValue(Constants.IMPORT_PACKAGE, 
"org.foo.bar;version=\"[2.0,3.0)\"");
+        if (exportVersion != null) {
+            manifest.getMainAttributes().putValue(Constants.EXPORT_PACKAGE, 
"org.foo.bar;version=" + exportVersion);
+        }
+        fd.getBundleDescriptors()
+                .add(new BundleDescriptorImpl(new 
Artifact(ArtifactId.fromMvnId("g:b:1")), null, manifest));
+        AnalyserTaskContext ctx = Mockito.mock(AnalyserTaskContext.class);
+        Mockito.when(ctx.getFeature()).thenReturn(f);
+        Mockito.when(ctx.getFeatureDescriptor()).thenReturn(fd);
+        return ctx;
+    }
+
     private void fdAddBundle(FeatureDescriptor fd, String id, String file) 
throws IOException {
         BundleDescriptor bd1 = new BundleDescriptorImpl(
                 new Artifact(ArtifactId.fromMvnId(id)),

Reply via email to