This is an automated email from the ASF dual-hosted git repository.
cziegeler pushed a commit to branch SLING-13114
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/SLING-13114 by this push:
new 5f952c8 SLING-13114 : Check for deprecated API does not consider
other bundles exporting the API
5f952c8 is described below
commit 5f952c8aa692715b7ad2a5bafc85a04b83bdf1f9
Author: Carsten Ziegeler <[email protected]>
AuthorDate: Sat Feb 14 14:37:53 2026 +0100
SLING-13114 : Check for deprecated API does not consider other bundles
exporting the API
---
.../apiregions/analyser/CheckDeprecatedApi.java | 125 ++++++++++++-----
.../analyser/CheckDeprecatedApiTest.java | 156 ++++++++++++++++++++-
2 files changed, 242 insertions(+), 39 deletions(-)
diff --git
a/src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckDeprecatedApi.java
b/src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckDeprecatedApi.java
index 944a761..fdd7d7f 100644
---
a/src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckDeprecatedApi.java
+++
b/src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckDeprecatedApi.java
@@ -18,15 +18,18 @@
*/
package org.apache.sling.feature.extension.apiregions.analyser;
+import java.util.ArrayList;
import java.util.Calendar;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import org.apache.sling.feature.ArtifactId;
import org.apache.sling.feature.analyser.task.AnalyserTask;
import org.apache.sling.feature.analyser.task.AnalyserTaskContext;
import org.apache.sling.feature.extension.apiregions.api.ApiExport;
@@ -49,8 +52,6 @@ public class CheckDeprecatedApi implements AnalyserTask {
private static final String CFG_CHECK_OPTIONAL_IMPORTS =
"check-optional-imports";
- private static final String PROP_VERSION = "version";
-
@Override
public String getId() {
return "region-deprecated-api";
@@ -112,7 +113,8 @@ public class CheckDeprecatedApi implements AnalyserTask {
checkDate = null;
}
- final Set<ApiExport> exports =
this.calculateDeprecatedPackages(region, bundleRegions);
+ final Map<String, DeprecatedPackage> deprecatedPackages =
+ this.calculateDeprecatedPackages(region, bundleRegions);
final Set<String> allowedNames = getAllowedRegions(region);
@@ -122,16 +124,10 @@ public class CheckDeprecatedApi implements AnalyserTask {
if (!checkOptionalImports && pi.isOptional()) {
continue;
}
- final VersionRange importRange =
pi.getPackageVersionRange();
DeprecationInfo deprecationInfo = null;
- for (final ApiExport exp : exports) {
- if (pi.getName().equals(exp.getName())) {
- String version =
exp.getProperties().get(PROP_VERSION);
- if (version == null || importRange == null ||
importRange.includes(new Version(version))) {
- deprecationInfo =
exp.getDeprecation().getPackageInfo();
- break;
- }
- }
+ final DeprecatedPackage deprecatedPackage =
deprecatedPackages.get(pi.getName());
+ if (deprecatedPackage != null) {
+ deprecationInfo = deprecatedPackage.isDeprecated(pi);
}
if (deprecationInfo != null) {
String msg = "Usage of deprecated package found : "
@@ -199,41 +195,45 @@ public class CheckDeprecatedApi implements AnalyserTask {
return allowedNames;
}
- Set<ApiExport> calculateDeprecatedPackages(
+ Map<String, DeprecatedPackage> calculateDeprecatedPackages(
final ApiRegion region, final Map<BundleDescriptor, Set<String>>
bundleRegions) {
- final Set<ApiExport> result = new LinkedHashSet<>();
- for (final ApiExport export : region.listAllExports()) {
- if (export.getDeprecation().getPackageInfo() != null) {
- final String version = getVersion(bundleRegions,
region.getName(), export.getName());
- // create new ApiExport to add version
- final ApiExport clone = new ApiExport(export.getName());
-
clone.getDeprecation().setPackageInfo(export.getDeprecation().getPackageInfo());
- if (version != null) {
- clone.getProperties().put(PROP_VERSION, version);
+ final Map<String, DeprecatedPackage> result = new HashMap<>();
+ ApiRegion current = region;
+ while (current != null) {
+ for (final ApiExport export : current.listExports()) {
+ if (export.getDeprecation().getPackageInfo() != null &&
!result.containsKey(export.getName())) {
+ final DeprecatedPackage pck =
getDeprecatedPackage(bundleRegions, current, export);
+ result.put(export.getName(), pck);
}
- result.add(clone);
}
+ current = current.getParent();
}
return result;
}
- String getVersion(
- final Map<BundleDescriptor, Set<String>> bundleRegions, final
String regionName, final String packageName) {
- String version = null;
+ DeprecatedPackage getDeprecatedPackage(
+ final Map<BundleDescriptor, Set<String>> bundleRegions, final
ApiRegion region, final ApiExport export) {
+ final List<PackageInfo> deprecatedList = new ArrayList<>();
+ final List<PackageInfo> nonDeprecatedList = new ArrayList<>();
+
+ final ArtifactId[] regionOrigins = region.getFeatureOrigins();
+
for (final Map.Entry<BundleDescriptor, Set<String>> entry :
bundleRegions.entrySet()) {
- if (entry.getValue().contains(regionName)) {
+ final ArtifactId[] bundleOrigins =
entry.getKey().getArtifact().getFeatureOrigins();
+ if (entry.getValue().contains(region.getName())) {
for (final PackageInfo info :
entry.getKey().getExportedPackages()) {
- if (info.getName().equals(packageName)) {
- version = info.getVersion();
- break;
+ if (info.getName().equals(export.getName())) {
+ if (regionOrigins.length == 0
+ || (bundleOrigins.length > 0 &&
bundleOrigins[0].isSame(regionOrigins[0]))) {
+ deprecatedList.add(info);
+ } else {
+ nonDeprecatedList.add(info);
+ }
}
}
- if (version != null) {
- break;
- }
}
}
- return version;
+ return new DeprecatedPackage(export.getDeprecation().getPackageInfo(),
deprecatedList, nonDeprecatedList);
}
private Set<String> getBundleRegions(final BundleDescriptor info, final
ApiRegions regions) {
@@ -243,4 +243,59 @@ public class CheckDeprecatedApi implements AnalyserTask {
.map(ApiRegion::getName)
.collect(Collectors.toSet());
}
+
+ /**
+ * Represents a deprecated package with its deprecation information and
package versions.
+ */
+ static final class DeprecatedPackage {
+ private final DeprecationInfo deprecationInfo;
+ private final List<PackageInfo> deprecatedList;
+ private final List<PackageInfo> nonDeprecatedList;
+
+ public DeprecationInfo getDeprecationInfo() {
+ return deprecationInfo;
+ }
+
+ /**
+ * Constructs a DeprecatedPackage with deprecation info and lists of
package versions.
+ *
+ * @param deprecationInfo the deprecation information for this package
+ * @param deprecatedList list of deprecated package versions
+ * @param nonDeprecatedList list of non-deprecated package versions
+ */
+ public DeprecatedPackage(
+ final DeprecationInfo deprecationInfo,
+ final List<PackageInfo> deprecatedList,
+ final List<PackageInfo> nonDeprecatedList) {
+ this.deprecationInfo = deprecationInfo;
+ this.deprecatedList = deprecatedList;
+ this.nonDeprecatedList = nonDeprecatedList;
+ }
+
+ /**
+ * Checks if the given package info is deprecated.
+ *
+ * @param pi the package info to check
+ * @return the deprecation info if the package is deprecated, null
otherwise
+ */
+ public DeprecationInfo isDeprecated(final PackageInfo pi) {
+ final VersionRange importVersion = pi.getVersion() != null ?
pi.getPackageVersionRange() : null;
+ // check non deprecated list first
+ for (final PackageInfo nonDeprecated : nonDeprecatedList) {
+ final Version exportVersion =
nonDeprecated.getPackageVersion();
+ if (exportVersion == null || importVersion == null ||
importVersion.includes(exportVersion)) {
+ return null;
+ }
+ }
+ // check deprecated list
+ for (final PackageInfo deprecated : deprecatedList) {
+ final Version exportVersion = deprecated.getPackageVersion();
+ if (exportVersion == null || importVersion == null ||
importVersion.includes(exportVersion)) {
+ return deprecationInfo;
+ }
+ }
+ // not found, do not report
+ return null;
+ }
+ }
}
diff --git
a/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckDeprecatedApiTest.java
b/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckDeprecatedApiTest.java
index 479765b..1525d0e 100644
---
a/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckDeprecatedApiTest.java
+++
b/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckDeprecatedApiTest.java
@@ -34,6 +34,7 @@ import org.apache.sling.feature.ExtensionState;
import org.apache.sling.feature.ExtensionType;
import org.apache.sling.feature.Feature;
import org.apache.sling.feature.analyser.task.AnalyserTaskContext;
+import
org.apache.sling.feature.extension.apiregions.analyser.CheckDeprecatedApi.DeprecatedPackage;
import org.apache.sling.feature.extension.apiregions.api.ApiExport;
import org.apache.sling.feature.extension.apiregions.api.ApiRegion;
import org.apache.sling.feature.extension.apiregions.api.ApiRegions;
@@ -47,6 +48,7 @@ import org.mockito.Mockito;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.never;
@@ -104,13 +106,14 @@ public class CheckDeprecatedApiTest {
region.add(e3);
// only e1 should be returned
- final Set<ApiExport> exports =
analyser.calculateDeprecatedPackages(region, Collections.emptyMap());
+ final Map<String, DeprecatedPackage> exports =
+ analyser.calculateDeprecatedPackages(region,
Collections.emptyMap());
assertEquals(1, exports.size());
- final ApiExport exp = exports.iterator().next();
- assertEquals(e1.getName(), exp.getName());
+ final DeprecatedPackage exp = exports.get("e1");
+ assertNotNull(exp);
assertEquals(
e1.getDeprecation().getPackageInfo().getMessage(),
- exp.getDeprecation().getPackageInfo().getMessage());
+ exp.getDeprecationInfo().getMessage());
}
@Test
@@ -138,6 +141,145 @@ public class CheckDeprecatedApiTest {
Mockito.eq(ArtifactId.fromMvnId("g:b:1.0.0")),
Mockito.contains("org.foo.deprecated"));
}
+ @Test
+ public void testVersionRangeChecking() throws Exception {
+ // Test that version ranges are properly checked
+ final CheckDeprecatedApi analyser = new CheckDeprecatedApi();
+
+ // Create a feature with deprecated package exported at version 2.0.0
+ final Feature feature = new
Feature(ArtifactId.fromMvnId("g:feature:1"));
+ final Extension extension =
+ new Extension(ExtensionType.JSON, ApiRegions.EXTENSION_NAME,
ExtensionState.OPTIONAL);
+
extension.setJSON("[{\"name\":\"global\",\"feature-origins\":[\"g:feature:1\"],"
+ +
"\"exports\":[{\"name\":\"org.foo.deprecated\",\"deprecated\":\"This package is
deprecated\"}]}]");
+ feature.getExtensions().add(extension);
+
+ final FeatureDescriptor fd = new FeatureDescriptorImpl(feature);
+
+ // Bundle that exports the deprecated package at version 2.0.0
+ final Artifact exportBundle = new
Artifact(ArtifactId.fromMvnId("g:exporter:1.0.0"));
+ exportBundle.setFeatureOrigins(feature.getId());
+ final BundleDescriptor exportBd = new
TestBundleDescriptor(exportBundle);
+ exportBd.getExportedPackages()
+ .add(new PackageInfo("org.foo.deprecated", "2.0.0", false,
Collections.emptySet()));
+ fd.getBundleDescriptors().add(exportBd);
+
+ // Bundle that imports with version range [1.0.0,2.0.0) - should NOT
be flagged
+ final Artifact importBundle1 = new
Artifact(ArtifactId.fromMvnId("g:importer1:1.0.0"));
+ importBundle1.setFeatureOrigins(feature.getId());
+ final BundleDescriptor importBd1 = new
TestBundleDescriptor(importBundle1);
+ final PackageInfo import1 =
+ new PackageInfo("org.foo.deprecated", "[1.0.0,2.0.0)", false,
Collections.emptySet());
+ importBd1.getImportedPackages().add(import1);
+ fd.getBundleDescriptors().add(importBd1);
+
+ // Bundle that imports with version range [2.0.0,3.0.0) - SHOULD be
flagged
+ final Artifact importBundle2 = new
Artifact(ArtifactId.fromMvnId("g:importer2:1.0.0"));
+ importBundle2.setFeatureOrigins(feature.getId());
+ final BundleDescriptor importBd2 = new
TestBundleDescriptor(importBundle2);
+ final PackageInfo import2 =
+ new PackageInfo("org.foo.deprecated", "[2.0.0,3.0.0)", false,
Collections.emptySet());
+ importBd2.getImportedPackages().add(import2);
+ fd.getBundleDescriptors().add(importBd2);
+
+ // Bundle that imports without version - SHOULD be flagged (matches
any version)
+ final Artifact importBundle3 = new
Artifact(ArtifactId.fromMvnId("g:importer3:1.0.0"));
+ importBundle3.setFeatureOrigins(feature.getId());
+ final BundleDescriptor importBd3 = new
TestBundleDescriptor(importBundle3);
+ final PackageInfo import3 = new PackageInfo("org.foo.deprecated",
null, false, Collections.emptySet());
+ importBd3.getImportedPackages().add(import3);
+ fd.getBundleDescriptors().add(importBd3);
+
+ final AnalyserTaskContext ctx =
Mockito.mock(AnalyserTaskContext.class);
+ Mockito.when(ctx.getFeature()).thenReturn(feature);
+ Mockito.when(ctx.getFeatureDescriptor()).thenReturn(fd);
+
Mockito.when(ctx.getConfiguration()).thenReturn(Collections.emptyMap());
+
+ analyser.execute(ctx);
+
+ // importer1 should NOT be flagged (version range excludes 2.0.0)
+ Mockito.verify(ctx, never())
+
.reportArtifactWarning(Mockito.eq(ArtifactId.fromMvnId("g:importer1:1.0.0")),
Mockito.anyString());
+
+ // importer2 SHOULD be flagged (version range includes 2.0.0)
+ Mockito.verify(ctx)
+ .reportArtifactWarning(
+ Mockito.eq(ArtifactId.fromMvnId("g:importer2:1.0.0")),
Mockito.contains("org.foo.deprecated"));
+
+ // importer3 SHOULD be flagged (no version constraint)
+ Mockito.verify(ctx)
+ .reportArtifactWarning(
+ Mockito.eq(ArtifactId.fromMvnId("g:importer3:1.0.0")),
Mockito.contains("org.foo.deprecated"));
+ }
+
+ @Test
+ public void testMultipleExportVersions() throws Exception {
+ // Test that when multiple versions of a deprecated package exist,
+ // only imports matching the actual exported versions are flagged
+ final CheckDeprecatedApi analyser = new CheckDeprecatedApi();
+
+ final Feature feature = new
Feature(ArtifactId.fromMvnId("g:feature:1"));
+ final Extension extension =
+ new Extension(ExtensionType.JSON, ApiRegions.EXTENSION_NAME,
ExtensionState.OPTIONAL);
+
extension.setJSON("[{\"name\":\"global\",\"feature-origins\":[\"g:feature:1\"],"
+ +
"\"exports\":[{\"name\":\"org.foo.deprecated\",\"deprecated\":\"This package is
deprecated\"}]}]");
+ feature.getExtensions().add(extension);
+
+ final FeatureDescriptor fd = new FeatureDescriptorImpl(feature);
+
+ // Bundle that exports the deprecated package at version 1.0.0
+ final Artifact exportBundle1 = new
Artifact(ArtifactId.fromMvnId("g:exporter1:1.0.0"));
+ exportBundle1.setFeatureOrigins(feature.getId());
+ final BundleDescriptor exportBd1 = new
TestBundleDescriptor(exportBundle1);
+ exportBd1
+ .getExportedPackages()
+ .add(new PackageInfo("org.foo.deprecated", "1.0.0", false,
Collections.emptySet()));
+ fd.getBundleDescriptors().add(exportBd1);
+
+ // DIFFERENT bundle that exports the SAME package at version 3.0.0
+ final Artifact exportBundle2 = new
Artifact(ArtifactId.fromMvnId("g:exporter2:1.0.0"));
+ exportBundle2.setFeatureOrigins(feature.getId());
+ final BundleDescriptor exportBd2 = new
TestBundleDescriptor(exportBundle2);
+ exportBd2
+ .getExportedPackages()
+ .add(new PackageInfo("org.foo.deprecated", "3.0.0", false,
Collections.emptySet()));
+ fd.getBundleDescriptors().add(exportBd2);
+
+ // Bundle that imports version [1.0.0,2.0.0) - matches only the 1.0.0
export
+ final Artifact importBundle1 = new
Artifact(ArtifactId.fromMvnId("g:importer1:1.0.0"));
+ importBundle1.setFeatureOrigins(feature.getId());
+ final BundleDescriptor importBd1 = new
TestBundleDescriptor(importBundle1);
+ importBd1
+ .getImportedPackages()
+ .add(new PackageInfo("org.foo.deprecated", "[1.0.0,2.0.0)",
false, Collections.emptySet()));
+ fd.getBundleDescriptors().add(importBd1);
+
+ // Bundle that imports version [2.5.0,4.0.0) - matches only the 3.0.0
export
+ final Artifact importBundle2 = new
Artifact(ArtifactId.fromMvnId("g:importer2:1.0.0"));
+ importBundle2.setFeatureOrigins(feature.getId());
+ final BundleDescriptor importBd2 = new
TestBundleDescriptor(importBundle2);
+ importBd2
+ .getImportedPackages()
+ .add(new PackageInfo("org.foo.deprecated", "[2.5.0,4.0.0)",
false, Collections.emptySet()));
+ fd.getBundleDescriptors().add(importBd2);
+
+ final AnalyserTaskContext ctx =
Mockito.mock(AnalyserTaskContext.class);
+ Mockito.when(ctx.getFeature()).thenReturn(feature);
+ Mockito.when(ctx.getFeatureDescriptor()).thenReturn(fd);
+
Mockito.when(ctx.getConfiguration()).thenReturn(Collections.emptyMap());
+
+ analyser.execute(ctx);
+
+ // BOTH importers should be flagged since both versions (1.0.0 and
3.0.0) are available
+ // The implementation checks all matching exported versions for this
package.
+ Mockito.verify(ctx)
+ .reportArtifactWarning(
+ Mockito.eq(ArtifactId.fromMvnId("g:importer1:1.0.0")),
Mockito.contains("org.foo.deprecated"));
+ Mockito.verify(ctx)
+ .reportArtifactWarning(
+ Mockito.eq(ArtifactId.fromMvnId("g:importer2:1.0.0")),
Mockito.contains("org.foo.deprecated"));
+ }
+
private AnalyserTaskContext createContext(final Map<String, String>
config, final boolean optionalImport) {
final Feature feature = new
Feature(ArtifactId.fromMvnId("g:feature:1"));
final Extension extension =
@@ -154,6 +296,12 @@ public class CheckDeprecatedApiTest {
.add(new PackageInfo("org.foo.deprecated", "1.0",
optionalImport, Collections.emptySet()));
fd.getBundleDescriptors().add(bd);
+ final Artifact apiBundle = new
Artifact(ArtifactId.fromMvnId("g:c:1.0.0"));
+ apiBundle.setFeatureOrigins(feature.getId());
+ final BundleDescriptor apiDesc = new TestBundleDescriptor(apiBundle);
+ apiDesc.getExportedPackages().add(new
PackageInfo("org.foo.deprecated", "1.0", false, Collections.emptySet()));
+ fd.getBundleDescriptors().add(apiDesc);
+
final AnalyserTaskContext ctx =
Mockito.mock(AnalyserTaskContext.class);
Mockito.when(ctx.getFeature()).thenReturn(feature);
Mockito.when(ctx.getFeatureDescriptor()).thenReturn(fd);