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