in src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsBundleExportsImports.java [77:221]
public void execute(final AnalyserTaskContext ctx) throws Exception {
boolean ignoreAPIRegions = ctx.getConfiguration().getOrDefault(
IGNORE_API_REGIONS_CONFIG_KEY, "false").equalsIgnoreCase("true");
final Map<BundleDescriptor, Report> reports = new HashMap<>();
final SortedMap<Integer, List<BundleDescriptor>> bundlesMap = new TreeMap<>();
for(final BundleDescriptor bi : ctx.getFeatureDescriptor().getBundleDescriptors()) {
final List<BundleDescriptor> list = bundlesMap.computeIfAbsent(bi.getArtifact().getStartOrder(), key -> new ArrayList<>());
list.add(bi);
}
// add all system packages
final List<BundleDescriptor> exportingBundles = new ArrayList<>();
if ( ctx.getFrameworkDescriptor() != null ) {
exportingBundles.add(ctx.getFrameworkDescriptor());
}
// extract and check the api-regions
ApiRegions apiRegions = null;
try {
apiRegions = ApiRegions.getApiRegions(ctx.getFeature());
} catch (final IllegalArgumentException e) {
ctx.reportError("API Region does not represent a valid JSON 'api-regions': " + e.getMessage());
return;
}
if ( apiRegions == null ) {
apiRegions = new ApiRegions(); // Empty region as default
}
for(final Map.Entry<Integer, List<BundleDescriptor>> entry : bundlesMap.entrySet()) {
// first add all exporting bundles
for(final BundleDescriptor info : entry.getValue()) {
if ( !info.getExportedPackages().isEmpty() ) {
exportingBundles.add(info);
}
}
// check importing bundles
for(final BundleDescriptor info : entry.getValue()) {
for(final PackageInfo pck : info.getImportedPackages() ) {
final Map<BundleDescriptor, Set<String>> candidates =
getCandidates(exportingBundles, pck, info, apiRegions, ignoreAPIRegions);
if ( candidates.isEmpty() ) {
if ( pck.isOptional() ) {
getReport(reports, info).missingExportsForOptional.add(pck);
} else {
getReport(reports, info).missingExports.add(pck);
}
} else {
final List<BundleDescriptor> matchingCandidates = new ArrayList<>();
Set<String> exportingRegions = new HashSet<>();
Set<String> importingRegions = new HashSet<>();
for(final Map.Entry<BundleDescriptor, Set<String>> candidate : candidates.entrySet()) {
BundleDescriptor bd = candidate.getKey();
if (bd.isExportingPackage(pck)) {
Set<String> exRegions = candidate.getValue();
if (exRegions.contains(NO_REGION)) {
// If an export is defined outside of a region, it always matches
matchingCandidates.add(bd);
continue;
}
if (exRegions.contains(GLOBAL_REGION)) {
// Everyone can import from the global regin
matchingCandidates.add(bd);
continue;
}
if (exRegions.contains(OWN_FEATURE)) {
// A feature can always import packages from bundles in itself
matchingCandidates.add(bd);
continue;
}
// Find out what regions the importing bundle is in
Set<String> imRegions =
getBundleRegions(info, apiRegions, ignoreAPIRegions);
// Record the exporting and importing regions for diagnostics
exportingRegions.addAll(exRegions);
Set<String> regions = new HashSet<>();
for (String region : imRegions) {
for (ApiRegion r = apiRegions.getRegionByName(region); r != null; r = r.getParent()) {
regions.add(r.getName());
}
}
importingRegions.addAll(regions);
// Only keep the regions that also export the package
regions.retainAll(exRegions);
if (!regions.isEmpty()) {
// there is an overlapping region
matchingCandidates.add(bd);
}
}
}
if ( matchingCandidates.isEmpty() ) {
if ( pck.isOptional() ) {
getReport(reports, info).missingExportsForOptional.add(pck);
} else {
getReport(reports, info).missingExportsWithVersion.add(pck);
getReport(reports, info).regionInfo.put(pck, new AbstractMap.SimpleEntry<>(exportingRegions, importingRegions));
}
} else if ( matchingCandidates.size() > 1 ) {
getReport(reports, info).exportMatchingSeveral.add(pck);
}
}
}
}
}
boolean errorReported = false;
for(final Map.Entry<BundleDescriptor, Report> entry : reports.entrySet()) {
final String key = "Bundle " + entry.getKey().getArtifact().getId().getArtifactId() + ":" + entry.getKey().getArtifact().getId().getVersion();
if ( !entry.getValue().missingExports.isEmpty() ) {
ctx.reportArtifactError(entry.getKey().getArtifact().getId(),
key + " is importing package(s) " + getPackageInfo(entry.getValue().missingExports, false) + " in start level " +
String.valueOf(entry.getKey().getArtifact().getStartOrder())
+ " but no bundle is exporting these for that start level.");
errorReported = true;
}
if ( !entry.getValue().missingExportsWithVersion.isEmpty() ) {
StringBuilder message = new StringBuilder(key + " is importing package(s) " + getPackageInfo(entry.getValue().missingExportsWithVersion, true) + " in start level " +
String.valueOf(entry.getKey().getArtifact().getStartOrder())
+ " but no visible bundle is exporting these for that start level in the required version range.");
for (Map.Entry<PackageInfo, Map.Entry<Set<String>, Set<String>>> regionInfoEntry : entry.getValue().regionInfo.entrySet()) {
PackageInfo pkg = regionInfoEntry.getKey();
Map.Entry<Set<String>, Set<String>> regions = regionInfoEntry.getValue();
if (regions.getKey().size() > 0) {
message.append("\n" + pkg.getName() + " is exported in regions " + regions.getKey() + " but it is imported in regions " + regions.getValue());
}
}
ctx.reportArtifactError(entry.getKey().getArtifact().getId(), message.toString());
errorReported = true;
}
}
if (errorReported && ctx.getFeature().isComplete()) {
ctx.reportError(ctx.getFeature().getId().toMvnId() + " is marked as 'complete' but has missing imports.");
}
}