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-extension-apiregions.git


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

commit 33461d9ec059cd82d2b9454a7872b981bf89cc8d
Author: Carsten Ziegeler <[email protected]>
AuthorDate: Fri Nov 13 09:01:13 2026 +0100

    SLING-13427 : Export / import analysers should take bootdelegation into 
account
---
 README.md                                          |  2 +
 .../CheckApiRegionsBundleExportsImports.java       | 21 +++++++
 .../CheckApiRegionsBundleExportsImportsTest.java   | 64 ++++++++++++++++++++++
 3 files changed, 87 insertions(+)

diff --git a/README.md b/README.md
index 77d9783..8878c36 100644
--- a/README.md
+++ b/README.md
@@ -28,6 +28,8 @@ in a given feature. If the same package is listed in multiple 
regions this will
 
 * `api-regions-exportsimports`: Checks bundle import/export package statements 
for consistency and completeness. If API Regions are used this analyser 
includes this
 information as part of the check, to ensure that bundles don't import packages 
of which they have no visibility because of API Regions restrictions.
+If no matching export is found, imports are checked against the 
`org.osgi.framework.bootdelegation` framework property,
+supporting exact package names, `package.*` for subpackages, and `*` for all 
packages.
 
 * `api-regions-check-order`: This analyser checks that regions are defined in 
the specified
 order and that the same region is only declared once. Later regions inherit 
the packages
diff --git 
a/src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsBundleExportsImports.java
 
b/src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsBundleExportsImports.java
index cbca8d0..1027298 100644
--- 
a/src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsBundleExportsImports.java
+++ 
b/src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsBundleExportsImports.java
@@ -38,6 +38,7 @@ import 
org.apache.sling.feature.extension.apiregions.api.ApiRegion;
 import org.apache.sling.feature.extension.apiregions.api.ApiRegions;
 import org.apache.sling.feature.scanner.BundleDescriptor;
 import org.apache.sling.feature.scanner.PackageInfo;
+import org.osgi.framework.Constants;
 
 public class CheckApiRegionsBundleExportsImports implements AnalyserTask {
 
@@ -78,6 +79,13 @@ public class CheckApiRegionsBundleExportsImports implements 
AnalyserTask {
         boolean ignoreAPIRegions = ctx.getConfiguration()
                 .getOrDefault(IGNORE_API_REGIONS_CONFIG_KEY, "false")
                 .equalsIgnoreCase("true");
+        final List<String> bootDelegation = Stream.of(ctx.getFeature()
+                        .getFrameworkProperties()
+                        .getOrDefault(Constants.FRAMEWORK_BOOTDELEGATION, "")
+                        .split(","))
+                .map(String::trim)
+                .filter(name -> !name.isEmpty())
+                .collect(Collectors.toList());
 
         final Map<BundleDescriptor, Report> reports = new HashMap<>();
 
@@ -119,6 +127,9 @@ public class CheckApiRegionsBundleExportsImports implements 
AnalyserTask {
                     final Map<BundleDescriptor, Set<String>> candidates =
                             getCandidates(exportingBundles, pck, info, 
apiRegions, ignoreAPIRegions);
                     if (candidates.isEmpty()) {
+                        if (isBootDelegated(pck.getName(), bootDelegation)) {
+                            continue;
+                        }
                         if (pck.isOptional()) {
                             getReport(reports, 
info).missingExportsForOptional.add(pck);
                         } else {
@@ -177,6 +188,9 @@ public class CheckApiRegionsBundleExportsImports implements 
AnalyserTask {
                         }
 
                         if (matchingCandidates.isEmpty()) {
+                            if (isBootDelegated(pck.getName(), 
bootDelegation)) {
+                                continue;
+                            }
                             if (pck.isOptional()) {
                                 getReport(reports, info)
                                         .missingExportsForOptional
@@ -236,6 +250,13 @@ public class CheckApiRegionsBundleExportsImports 
implements AnalyserTask {
         }
     }
 
+    private boolean isBootDelegated(final String packageName, final 
List<String> bootDelegation) {
+        return bootDelegation.stream()
+                .anyMatch(name -> name.equals("*")
+                        || name.equals(packageName)
+                        || (name.endsWith(".*") && 
packageName.startsWith(name.substring(0, name.length() - 1))));
+    }
+
     private Set<String> getBundleRegions(BundleDescriptor info, ApiRegions 
regions, boolean ignoreAPIRegions) {
         Set<String> result = ignoreAPIRegions
                 ? Collections.emptySet()
diff --git 
a/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsBundleExportsImportsTest.java
 
b/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsBundleExportsImportsTest.java
index 663d4cc..d68f449 100644
--- 
a/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsBundleExportsImportsTest.java
+++ 
b/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsBundleExportsImportsTest.java
@@ -34,11 +34,13 @@ import 
org.apache.sling.feature.analyser.task.AnalyserTaskContext;
 import org.apache.sling.feature.extension.apiregions.api.ApiRegions;
 import org.apache.sling.feature.scanner.BundleDescriptor;
 import org.apache.sling.feature.scanner.FeatureDescriptor;
+import org.apache.sling.feature.scanner.PackageInfo;
 import org.apache.sling.feature.scanner.impl.BundleDescriptorImpl;
 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;
 
@@ -132,6 +134,68 @@ public class CheckApiRegionsBundleExportsImportsTest {
         Mockito.verify(ctx, 
Mockito.never()).reportArtifactWarning(Mockito.any(), Mockito.anyString());
     }
 
+    @Test
+    public void testMissingExportWithBootDelegation() throws Exception {
+        for (String bootDelegation :
+                new String[] {"org.foo.e", "org.foo.*", "org.*", "*", " 
org.other, , org.foo.e "}) {
+            assertBootDelegation(bootDelegation, false);
+        }
+    }
+
+    @Test
+    public void testMissingExportWithNonMatchingBootDelegation() throws 
Exception {
+        for (String bootDelegation :
+                new String[] {"", " , ", "org.other", "org.foo.e.*", 
"org.fo.*", "org.foo", "org.foo.evil", "org.foo.e*"
+                }) {
+            assertBootDelegation(bootDelegation, true);
+        }
+    }
+
+    private void assertBootDelegation(String bootDelegation, boolean 
missingExport) throws Exception {
+        Feature f = new Feature(ArtifactId.fromMvnId("f:f:1"));
+        f.setComplete(true);
+        f.getFrameworkProperties().put(Constants.FRAMEWORK_BOOTDELEGATION, 
bootDelegation);
+        FeatureDescriptor fd = new FeatureDescriptorImpl(f);
+        fdAddBundle(fd, "g:b1:1", "test-bundle1.jar");
+        fdAddBundle(fd, "g:b3:1", "test-bundle3.jar");
+
+        AnalyserTaskContext ctx = Mockito.mock(AnalyserTaskContext.class);
+        Mockito.when(ctx.getFeature()).thenReturn(f);
+        Mockito.when(ctx.getFeatureDescriptor()).thenReturn(fd);
+        new CheckApiRegionsBundleExportsImports().execute(ctx);
+
+        Mockito.verify(ctx, Mockito.times(missingExport ? 1 : 0))
+                
.reportArtifactError(Mockito.eq(ArtifactId.fromMvnId("g:b3:1")), 
Mockito.contains("org.foo.e"));
+        Mockito.verify(ctx, Mockito.times(missingExport ? 1 : 
0)).reportError(Mockito.anyString());
+        Mockito.verify(ctx, 
Mockito.never()).reportArtifactWarning(Mockito.any(), Mockito.anyString());
+    }
+
+    @Test
+    public void testBootDelegationWithNonMatchingExportVersion() throws 
Exception {
+        for (boolean bootDelegation : new boolean[] {false, true}) {
+            Feature f = new Feature(ArtifactId.fromMvnId("f:f:1"));
+            if (bootDelegation) {
+                
f.getFrameworkProperties().put(Constants.FRAMEWORK_BOOTDELEGATION, "org.foo.b");
+            }
+            FeatureDescriptor fd = new FeatureDescriptorImpl(f);
+            fdAddBundle(fd, "g:b1:1", "test-bundle1.jar");
+            BundleDescriptor importer = Mockito.mock(
+                    BundleDescriptor.class,
+                    
Mockito.withSettings().useConstructor("g:b2:1").defaultAnswer(Mockito.CALLS_REAL_METHODS));
+            Mockito.when(importer.getArtifact()).thenReturn(new 
Artifact(ArtifactId.fromMvnId("g:b2:1")));
+            importer.getImportedPackages().add(new PackageInfo("org.foo.b", 
"[99,100)", false, Collections.emptySet()));
+            fd.getBundleDescriptors().add(importer);
+
+            AnalyserTaskContext ctx = Mockito.mock(AnalyserTaskContext.class);
+            Mockito.when(ctx.getFeature()).thenReturn(f);
+            Mockito.when(ctx.getFeatureDescriptor()).thenReturn(fd);
+            new CheckApiRegionsBundleExportsImports().execute(ctx);
+
+            Mockito.verify(ctx, Mockito.times(bootDelegation ? 0 : 1))
+                    
.reportArtifactError(Mockito.eq(ArtifactId.fromMvnId("g:b2:1")), 
Mockito.contains("org.foo.b"));
+        }
+    }
+
     @Test
     /*
      * Bundle 2 imports org.foo.b from bundle 1, but bundle 1 exports it in a

Reply via email to