This is an automated email from the ASF dual-hosted git repository.
rombert 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 15df3c3 SLING-10288 - FelixFrameworkScanner sets framework properties
always for current Java version (#47)
15df3c3 is described below
commit 15df3c30c4c53ad83a647928f9f8b0a0d68b238d
Author: Robert Munteanu <[email protected]>
AuthorDate: Mon Jun 17 21:54:36 2024 +0200
SLING-10288 - FelixFrameworkScanner sets framework properties always for
current Java version (#47)
Support an optional "reserved:system.bundle:0.0.0" key in the
analyser-metadata extension. This key, when present, is used to store the
capabilities and exported packages for the system bundle.
---
pom.xml | 11 ++
readme.md | 25 +++-
.../extensions/AnalyserMetaDataExtension.java | 94 +++++++++++---
.../extensions/AnalyserMetaDataHandler.java | 85 ++++++++++++-
.../task/impl/CheckRequirementsCapabilities.java | 4 +-
.../org/apache/sling/feature/scanner/Scanner.java | 55 +++++++--
.../scanner/impl/FelixFrameworkScanner.java | 30 +----
.../scanner/impl/SystemBundleDescriptor.java | 81 ++++++++++++
.../extensions/AnalyserMetaDataExtensionTest.java | 42 ++++++-
.../extensions/AnalyserMetaDataHandlerTest.java | 137 +++++++++++++++++----
.../feature-input-system-bundle.json | 12 ++
.../feature-output-system-bundle.json | 13 ++
12 files changed, 499 insertions(+), 90 deletions(-)
diff --git a/pom.xml b/pom.xml
index 146539e..b4199c2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -212,6 +212,17 @@
<version>1.2.14</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>3.26.0</version>
+ <scope>test</scope>
+ </dependency>
<dependency>
<!-- Used by some tests -->
<groupId>org.apache.felix</groupId>
diff --git a/readme.md b/readme.md
index 7530b1f..622ad59 100644
--- a/readme.md
+++ b/readme.md
@@ -116,16 +116,25 @@ Checks bundle requirements/capabilities for consistency
and completeness.
Generates additional metadata that will be recorded in the feature model
definition. It is configured by defining an `analyser-metadata` section in the
feature model definition. The section will be processed by the extension when
the feature models are aggregated and will be replaced with the required
entries for bundles matching the configuration.
+### Bundle metadata
+
The section can have entries that match individual bundle names and entries
that match based on regular expressions (if the key contains the "*" character).
Each individual entry can contain the following keys:
-
Configuration key | Allowed values | Description
----- | ----- | -----
`manifest` | `null` or Object | If null, the manifest is not generated. If an
object, the values are copied over. If absent, the values are extracted from
the OSGi bundle
`report` | Object with keys `warning` and `error` | If any of the values are
set to `false`, reporting is suppressed for those kind of occurences.
+### Framework metadata
+
+A special case is when an entry with the name `extra-metadata:system.bundle:0`
is found. This will record information about the system bundle exported
packages and capabilites as present at the time of the feature aggregation.
When these are present in an aggregated feature the analysers will use that
information instead of the one discovered during analysis time.
+
+The system bundle information will be extracted from the
`execution-environment` extension. In this extension, the `framework` entry is
required and is used to gather information about the system bundle. The
`javaVersion` property is optional but recommended and is used to validate that
the Java version used to generate the metadata matches the one in the execution
environment.
+
+### Example
+
A typical configuration for platform applications is:
```javascript
@@ -138,9 +147,19 @@ A typical configuration for platform applications is:
"error": false,
"warning": false
}
- }
+ },
+ "extra-metadata:system.bundle:0": {}
+ },
+ "framework":{
+ "id":"org.apache.felix:org.apache.felix.framework:7.0.5"
+ },
+ "javaVersion": "21"
}
}
```
-This ensures that warnings related to the platform are not reported when the
feature is aggregated with downstream (consumer) applications. The manifests
should not be inlined under normal circumstances, since it greatly increases
the size of the resulting features.
+This ensures that
+
+- warnings related to the platform are not reported when the feature is
aggregated with downstream (consumer) applications. The manifests should not be
inlined under normal circumstances, since it greatly increases the size of the
resulting features.
+- metadata related to the system bundle is recorded at the Java 21
compatibility level
+
diff --git
a/src/main/java/org/apache/sling/feature/analyser/extensions/AnalyserMetaDataExtension.java
b/src/main/java/org/apache/sling/feature/analyser/extensions/AnalyserMetaDataExtension.java
index 61c574f..8435e3b 100644
---
a/src/main/java/org/apache/sling/feature/analyser/extensions/AnalyserMetaDataExtension.java
+++
b/src/main/java/org/apache/sling/feature/analyser/extensions/AnalyserMetaDataExtension.java
@@ -16,27 +16,42 @@
*/
package org.apache.sling.feature.analyser.extensions;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.stream.Stream;
+
import org.apache.sling.feature.ArtifactId;
import org.apache.sling.feature.Extension;
import org.apache.sling.feature.ExtensionType;
import org.apache.sling.feature.Feature;
import org.apache.sling.feature.scanner.BundleDescriptor;
+import org.osgi.framework.Constants;
import jakarta.json.Json;
import jakarta.json.JsonObject;
import jakarta.json.JsonObjectBuilder;
import jakarta.json.JsonValue;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.stream.Stream;
public class AnalyserMetaDataExtension {
+
public static final String EXTENSION_NAME = "analyser-metadata";
+ // use ArtifactId.fromMvnId to ensure the string form can be parsed back
to an ArtifactId
+ static final String SYSTEM_BUNDLE_KEY =
ArtifactId.fromMvnId("extra-metadata:" + Constants.SYSTEM_BUNDLE_SYMBOLICNAME +
":0").toString();
+ static final String MANIFEST_KEY = "manifest";
+ static final String REPORT_KEY = "report";
+ static final String WARNING_KEY = "warning";
+ static final String ERROR_KEY = "error";
+ static final String ARTIFACT_ID_KEY = "artifactId";
+ static final String SCANNER_CACHE_KEY = "scannerCacheKey";
+
private final Map<ArtifactId, Map<String, String>> manifests = new
HashMap<>();
private final Map<ArtifactId, Boolean> reportWarnings = new HashMap<>();
private final Map<ArtifactId, Boolean> reportErrors = new HashMap<>();
+ private SystemBundle systemBundle;
+
+
public static AnalyserMetaDataExtension
getAnalyserMetaDataExtension(Feature feature) {
Extension ext = feature == null ? null :
feature.getExtensions().getByName(EXTENSION_NAME);
@@ -54,24 +69,42 @@ public class AnalyserMetaDataExtension {
}
private AnalyserMetaDataExtension(JsonObject json) {
+
for (Map.Entry<String, JsonValue> entry : json.entrySet()) {
+
+ // handle system bundle separately
+ if ( entry.getKey().equals(SYSTEM_BUNDLE_KEY) ) {
+ JsonObject systemBundleConfig =
entry.getValue().asJsonObject();
+ JsonObject manifestObj =
systemBundleConfig.getJsonObject(MANIFEST_KEY);
+ String artifactId =
systemBundleConfig.getJsonString(ARTIFACT_ID_KEY).getString();
+ String scannerCacheKey =
systemBundleConfig.getJsonString(SCANNER_CACHE_KEY).getString();
+
+ Map<String, String> manifest = new HashMap<>();
+ for (String key : manifestObj.keySet()) {
+ manifest.put(key, manifestObj.getString(key));
+ }
+ systemBundle = new SystemBundle(manifest,
ArtifactId.fromMvnId(artifactId), scannerCacheKey);
+
+ continue;
+ }
+
ArtifactId id = ArtifactId.fromMvnId(entry.getKey());
JsonObject headers = entry.getValue().asJsonObject();
- if (headers.containsKey("manifest")) {
+ if (headers.containsKey(MANIFEST_KEY)) {
Map<String, String> manifest = new LinkedHashMap<>();
- JsonObject manifestHeaders = headers.getJsonObject("manifest");
+ JsonObject manifestHeaders =
headers.getJsonObject(MANIFEST_KEY);
for (String name : manifestHeaders.keySet()) {
manifest.put(name, manifestHeaders.getString(name));
}
this.manifests.put(id, manifest);
}
- if (headers.containsKey("report")) {
- JsonObject report = headers.getJsonObject("report");
- if (report.containsKey("warning")) {
- reportWarnings.put(id, report.getBoolean("warning"));
+ if (headers.containsKey(REPORT_KEY)) {
+ JsonObject report = headers.getJsonObject(REPORT_KEY);
+ if (report.containsKey(WARNING_KEY)) {
+ reportWarnings.put(id, report.getBoolean(WARNING_KEY));
}
- if (report.containsKey("error")) {
- reportErrors.put(id, report.getBoolean("error"));
+ if (report.containsKey(ERROR_KEY)) {
+ reportErrors.put(id, report.getBoolean(ERROR_KEY));
}
}
}
@@ -84,6 +117,10 @@ public class AnalyserMetaDataExtension {
public Map<String, String> getManifest(final ArtifactId artifactId) {
return this.manifests.get(artifactId);
}
+
+ public SystemBundle getSystemBundle() {
+ return systemBundle;
+ }
public boolean reportWarning(ArtifactId artifactId) {
return !this.reportWarnings.containsKey(artifactId) ||
this.reportWarnings.get(artifactId);
@@ -102,17 +139,17 @@ public class AnalyserMetaDataExtension {
if (manifests.containsKey(id)) {
JsonObjectBuilder manifest =
Json.createObjectBuilder();
manifests.get(id).forEach(manifest::add);
- metadata.add("manifest", manifest);
+ metadata.add(MANIFEST_KEY, manifest);
}
if (reportErrors.containsKey(id) ||
reportWarnings.containsKey(id)) {
JsonObjectBuilder report =
Json.createObjectBuilder();
if (reportErrors.containsKey(id)) {
- report.add("error", reportErrors.get(id));
+ report.add(ERROR_KEY, reportErrors.get(id));
}
if (reportWarnings.containsKey(id)) {
- report.add("warning", reportWarnings.get(id));
+ report.add(WARNING_KEY,
reportWarnings.get(id));
}
- metadata.add("report", report);
+ metadata.add(REPORT_KEY, report);
}
builder.add(id.toMvnId(), metadata);
}
@@ -139,4 +176,29 @@ public class AnalyserMetaDataExtension {
public void setReportErrors(ArtifactId id, boolean enabled) {
reportErrors.put(id, enabled);
}
+
+ public static class SystemBundle {
+
+ private Map<String, String> manifest = new HashMap<>();
+ private ArtifactId artifactId;
+ private String scannerCacheKey;
+
+ public SystemBundle(Map<String, String> manifest, ArtifactId
artifactId, String scannerCacheKey) {
+ this.manifest = manifest;
+ this.artifactId = artifactId;
+ this.scannerCacheKey = scannerCacheKey;
+ }
+
+ public ArtifactId getArtifactId() {
+ return artifactId;
+ }
+
+ public Map<String, String> getManifest() {
+ return manifest;
+ }
+
+ public String getScannerCacheKey() {
+ return scannerCacheKey;
+ }
+ }
}
diff --git
a/src/main/java/org/apache/sling/feature/analyser/extensions/AnalyserMetaDataHandler.java
b/src/main/java/org/apache/sling/feature/analyser/extensions/AnalyserMetaDataHandler.java
index 830bc7b..65cda8d 100644
---
a/src/main/java/org/apache/sling/feature/analyser/extensions/AnalyserMetaDataHandler.java
+++
b/src/main/java/org/apache/sling/feature/analyser/extensions/AnalyserMetaDataHandler.java
@@ -23,16 +23,29 @@ import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
+import java.util.ServiceLoader;
+import java.util.Set;
+import java.util.StringJoiner;
import java.util.jar.JarFile;
+import org.apache.commons.lang3.SystemUtils;
import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.ExecutionEnvironmentExtension;
import org.apache.sling.feature.Extension;
import org.apache.sling.feature.ExtensionState;
import org.apache.sling.feature.ExtensionType;
import org.apache.sling.feature.Feature;
import org.apache.sling.feature.builder.HandlerContext;
import org.apache.sling.feature.builder.PostProcessHandler;
+import org.apache.sling.feature.impl.felix.utils.resource.ResourceUtils;
import org.apache.sling.feature.io.IOUtils;
+import org.apache.sling.feature.scanner.BundleDescriptor;
+import org.apache.sling.feature.scanner.PackageInfo;
+import org.apache.sling.feature.scanner.impl.SystemBundleDescriptor;
+import org.apache.sling.feature.scanner.spi.FrameworkScanner;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Version;
+import org.osgi.resource.Capability;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -44,20 +57,22 @@ import jakarta.json.JsonValue;
public class AnalyserMetaDataHandler implements PostProcessHandler {
private static final Logger LOG =
LoggerFactory.getLogger(AnalyserMetaDataHandler.class);
- private static final String MANIFEST_KEY = "manifest";
-
@Override
public void postProcess(HandlerContext handlerContext, Feature feature,
Extension extension) {
+
if
(AnalyserMetaDataExtension.EXTENSION_NAME.equals(extension.getName())) {
LOG.debug("Handling analyser-metadata extension {}", extension);
JsonObject extensionJSONStructure =
extension.getJSONStructure().asJsonObject();
JsonObjectBuilder result = Json.createObjectBuilder();
Map<String, JsonValue> directEntries = new HashMap<>();
Map<String, JsonValue> wildcardEntries = new LinkedHashMap<>();
+ JsonObject[] systemBundleHolder = new JsonObject[1];
extensionJSONStructure.entrySet().forEach(
entry -> {
if (entry.getKey().contains("*")) {
wildcardEntries.put(entry.getKey(),
entry.getValue());
+ } else if
(entry.getKey().equals(AnalyserMetaDataExtension.SYSTEM_BUNDLE_KEY)) {
+ systemBundleHolder[0] =
entry.getValue().asJsonObject();
} else {
directEntries.put(entry.getKey(),
entry.getValue());
}
@@ -70,19 +85,59 @@ public class AnalyserMetaDataHandler implements
PostProcessHandler {
json -> {
if (nullManifest(json)) {
JsonObjectBuilder wrapper =
Json.createObjectBuilder(json);
- wrapper.remove(MANIFEST_KEY);
+
wrapper.remove(AnalyserMetaDataExtension.MANIFEST_KEY);
result.add(bundle.getId().toMvnId(),
wrapper);
} else if (noManifest(json)) {
JsonObjectBuilder wrapper =
Json.createObjectBuilder(json);
getManifest(handlerContext,
bundle.getId()).ifPresent(manifest ->
- wrapper.add(MANIFEST_KEY,
manifest));
+
wrapper.add(AnalyserMetaDataExtension.MANIFEST_KEY, manifest));
result.add(bundle.getId().toMvnId(),
wrapper);
} else {
result.add(bundle.getId().toMvnId(),
json);
}
}
)
- );
+ );
+
+ // only process if we have an empty system bundle definition
+ if (JsonValue.EMPTY_JSON_OBJECT.equals(systemBundleHolder[0]) ) {
+ JsonObject systemBundle = systemBundleHolder[0];
+ try {
+ ExecutionEnvironmentExtension executionEnv =
ExecutionEnvironmentExtension.getExecutionEnvironmentExtension(feature);
+ if ( executionEnv != null ) {
+ ArtifactId frameworkId =
executionEnv.getFramework().getId();
+ if ( executionEnv.getJavaVersion() == null ) {
+ LOG.warn("No java version set in execution
environment extension, skipping version validation");
+ } else {
+ Version requiredJavaVersion =
executionEnv.getJavaVersion();
+ Version currentJavaVersion = new
Version(SystemUtils.JAVA_VERSION);
+
+ if ( requiredJavaVersion.getMajor() !=
currentJavaVersion.getMajor() )
+ throw new IllegalStateException("Execution
environment requires Java " + requiredJavaVersion.getMajor() + ", but running
on " + currentJavaVersion.getMajor() + ". Aborting.");
+
+ }
+ FrameworkScanner scanner =
ServiceLoader.load(FrameworkScanner.class).iterator().next();
+
+ BundleDescriptor fw = scanner.scan(frameworkId,
feature.getFrameworkProperties(), handlerContext.getArtifactProvider());
+ JsonObjectBuilder wrapper =
Json.createObjectBuilder(systemBundle);
+ JsonObjectBuilder manifest =
Json.createObjectBuilder();
+ manifest.add(Constants.PROVIDE_CAPABILITY,
capabilitiesToString(fw.getCapabilities()));
+ manifest.add(Constants.EXPORT_PACKAGE,
exportedPackagesToString(fw.getExportedPackages()));
+ wrapper.add(AnalyserMetaDataExtension.MANIFEST_KEY,
manifest);
+ wrapper.add(AnalyserMetaDataExtension.ARTIFACT_ID_KEY,
frameworkId.toMvnId());
+
wrapper.add(AnalyserMetaDataExtension.SCANNER_CACHE_KEY,
SystemBundleDescriptor.createCacheKey(frameworkId,
feature.getFrameworkProperties()));
+
result.add(AnalyserMetaDataExtension.SYSTEM_BUNDLE_KEY, wrapper);
+ } else {
+ LOG.warn("No execution environment found, not creating
framework capabilities");
+ }
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ } else if (systemBundleHolder[0] != null) {
+ // preserve the existing system bundle information
+ result.add(AnalyserMetaDataExtension.SYSTEM_BUNDLE_KEY,
systemBundleHolder[0]);
+ }
+
feature.getExtensions().remove(extension);
@@ -93,6 +148,24 @@ public class AnalyserMetaDataHandler implements
PostProcessHandler {
}
}
+ private static String exportedPackagesToString(Set<PackageInfo>
exportedPackages) {
+ StringJoiner joiner = new StringJoiner(",");
+ for (PackageInfo packageInfo : exportedPackages) {
+ joiner.add(packageInfo.getName() + ";version=" +
packageInfo.getVersion());
+ }
+ return joiner.toString();
+ }
+
+ private static String capabilitiesToString(Set<Capability> capabilities) {
+
+ StringJoiner joiner = new StringJoiner(",");
+ for (Capability c : capabilities) {
+ joiner.add(ResourceUtils.toString(null, c.getNamespace(),
c.getAttributes(), c.getDirectives()));
+ }
+
+ return joiner.toString();
+ }
+
private boolean noManifest(JsonObject object) {
return manifest(object, null) && !object.getBoolean("no-manifest",
false);
}
@@ -102,7 +175,7 @@ public class AnalyserMetaDataHandler implements
PostProcessHandler {
}
private boolean manifest(JsonObject object, Object match) {
- return object.get(MANIFEST_KEY) == match;
+ return object.get(AnalyserMetaDataExtension.MANIFEST_KEY) == match;
}
private Optional<JsonObject> findFirst(Map<String, JsonValue>
directValues, Map<String, JsonValue> wildcardValues, ArtifactId bundle) {
diff --git
a/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckRequirementsCapabilities.java
b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckRequirementsCapabilities.java
index 3492ae3..0979f69 100644
---
a/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckRequirementsCapabilities.java
+++
b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckRequirementsCapabilities.java
@@ -155,7 +155,9 @@ public class CheckRequirementsCapabilities implements
AnalyserTask {
private List<Descriptor> getCandidates(List<Descriptor>
artifactDescriptors, Requirement requirement) {
return artifactDescriptors.stream()
.filter(artifactDescriptor ->
artifactDescriptor.getCapabilities() != null)
- .filter(artifactDescriptor ->
artifactDescriptor.getCapabilities().stream().anyMatch(capability ->
CapabilitySet.matches(capability, requirement)))
+ .filter(artifactDescriptor ->
artifactDescriptor.getCapabilities().stream().anyMatch(
+ capability -> CapabilitySet.matches(capability,
requirement))
+ )
.collect(Collectors.toList());
}
diff --git a/src/main/java/org/apache/sling/feature/scanner/Scanner.java
b/src/main/java/org/apache/sling/feature/scanner/Scanner.java
index a55f8bb..9973736 100644
--- a/src/main/java/org/apache/sling/feature/scanner/Scanner.java
+++ b/src/main/java/org/apache/sling/feature/scanner/Scanner.java
@@ -22,23 +22,30 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
-import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.Manifest;
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import org.apache.felix.utils.manifest.Clause;
+import org.apache.felix.utils.manifest.Parser;
import org.apache.sling.feature.Artifact;
import org.apache.sling.feature.ArtifactId;
import org.apache.sling.feature.Bundles;
import org.apache.sling.feature.Extension;
import org.apache.sling.feature.Feature;
import org.apache.sling.feature.analyser.extensions.AnalyserMetaDataExtension;
+import
org.apache.sling.feature.analyser.extensions.AnalyserMetaDataExtension.SystemBundle;
import org.apache.sling.feature.builder.ArtifactProvider;
+import org.apache.sling.feature.impl.felix.utils.resource.ResourceBuilder;
import org.apache.sling.feature.scanner.impl.BundleDescriptorImpl;
import org.apache.sling.feature.scanner.impl.FeatureDescriptorImpl;
+import org.apache.sling.feature.scanner.impl.SystemBundleDescriptor;
import org.apache.sling.feature.scanner.spi.ExtensionScanner;
import org.apache.sling.feature.scanner.spi.FrameworkScanner;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.resource.Capability;
/**
* The scanner is a service that scans items and provides descriptions for
@@ -248,6 +255,42 @@ public class Scanner {
}
}
}
+
+ SystemBundle systemBundle = extension.getSystemBundle();
+ if ( systemBundle != null ) {
+ URL artifactUrl =
artifactProvider.provide(systemBundle.getArtifactId());
+ if (artifactUrl == null) {
+ throw new IOException("Unable to find file for " +
systemBundle.getArtifactId());
+ }
+
+ BundleDescriptor desc = new
SystemBundleDescriptor(systemBundle.getArtifactId(), artifactUrl);
+
+ String capabilities =
systemBundle.getManifest().get(Constants.PROVIDE_CAPABILITY);
+ if ( capabilities != null ) {
+ try {
+ List<Capability> parsedCapabilities =
ResourceBuilder.parseCapability(null, capabilities);
+ desc.getCapabilities().addAll(parsedCapabilities);
+ } catch (BundleException e) {
+ throw new IOException("Failed to parse capabilites for
the system bundle", e);
+ }
+ }
+
+ String exports =
systemBundle.getManifest().get(Constants.EXPORT_PACKAGE);
+ if ( exports != null ) {
+ Clause[] pcks = Parser.parseHeader(exports);
+ for (final Clause pck : pcks) {
+ String version = pck.getAttribute("version");
+ PackageInfo info = new PackageInfo(pck.getName(),
version, false);
+ desc.getExportedPackages().add(info);
+ }
+ }
+ desc.lock();
+
+ String key = systemBundle.getScannerCacheKey();
+
+ this.cache.put(key, desc);
+
+ }
}
}
@@ -260,15 +303,7 @@ public class Scanner {
* @throws IOException If something goes wrong or a scanner is missing
*/
public BundleDescriptor scan(final ArtifactId framework, final
Map<String,String> props) throws IOException {
- final StringBuilder sb = new StringBuilder();
- sb.append(framework.toMvnId());
- if (props != null) {
- final Map<String, String> sortedMap = new TreeMap<String,
String>(props);
- for (final Map.Entry<String, String> entry : sortedMap.entrySet())
{
-
sb.append(":").append(entry.getKey()).append("=").append(entry.getValue());
- }
- }
- final String key = sb.toString();
+ final String key = SystemBundleDescriptor.createCacheKey(framework,
props);
BundleDescriptor desc = (BundleDescriptor) this.cache.get(key);
if (desc == null) {
for (final FrameworkScanner scanner : this.frameworkScanners) {
diff --git
a/src/main/java/org/apache/sling/feature/scanner/impl/FelixFrameworkScanner.java
b/src/main/java/org/apache/sling/feature/scanner/impl/FelixFrameworkScanner.java
index c7db4b9..d4cab4e 100644
---
a/src/main/java/org/apache/sling/feature/scanner/impl/FelixFrameworkScanner.java
+++
b/src/main/java/org/apache/sling/feature/scanner/impl/FelixFrameworkScanner.java
@@ -35,13 +35,11 @@ import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
-import java.util.jar.Manifest;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.felix.utils.manifest.Parser;
import org.apache.felix.utils.resource.ResourceBuilder;
-import org.apache.sling.feature.Artifact;
import org.apache.sling.feature.ArtifactId;
import org.apache.sling.feature.builder.ArtifactProvider;
import org.apache.sling.feature.io.IOUtils;
@@ -73,33 +71,7 @@ public class FelixFrameworkScanner implements
FrameworkScanner {
final Set<PackageInfo> pcks = calculateSystemPackages(fwkProps);
final List<Capability> capabilities =
calculateSystemCapabilities(fwkProps);
- final BundleDescriptor d = new BundleDescriptor(framework.toMvnId()) {
-
- @Override
- public String getBundleSymbolicName() {
- return Constants.SYSTEM_BUNDLE_SYMBOLICNAME;
- }
-
- @Override
- public String getBundleVersion() {
- return framework.getOSGiVersion().toString();
- }
-
- @Override
- public URL getArtifactFile() {
- return platformFile;
- }
-
- @Override
- public Artifact getArtifact() {
- return new Artifact(framework);
- }
-
- @Override
- public Manifest getManifest() {
- return new Manifest();
- }
- };
+ final BundleDescriptor d = new SystemBundleDescriptor(framework,
platformFile);
d.getCapabilities().addAll(capabilities);
d.getExportedPackages().addAll(pcks);
d.lock();
diff --git
a/src/main/java/org/apache/sling/feature/scanner/impl/SystemBundleDescriptor.java
b/src/main/java/org/apache/sling/feature/scanner/impl/SystemBundleDescriptor.java
new file mode 100644
index 0000000..3972042
--- /dev/null
+++
b/src/main/java/org/apache/sling/feature/scanner/impl/SystemBundleDescriptor.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.feature.scanner.impl;
+
+import java.net.URL;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.jar.Manifest;
+
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.scanner.BundleDescriptor;
+import org.osgi.framework.Constants;
+
+/**
+ * Bundle descriptor that describes the system bundle
+ *
+ * <p>It is based on an existing artifact id and the associated platform
file.</p>
+ */
+public final class SystemBundleDescriptor extends BundleDescriptor {
+
+ public static String createCacheKey(final ArtifactId framework, final
Map<String, String> props) {
+ final StringBuilder sb = new StringBuilder();
+ sb.append(framework.toMvnId());
+ if (props != null) {
+ final Map<String, String> sortedMap = new TreeMap<>(props);
+ for (final Map.Entry<String, String> entry : sortedMap.entrySet())
{
+
sb.append(":").append(entry.getKey()).append("=").append(entry.getValue());
+ }
+ }
+ return sb.toString();
+ }
+
+ private final URL platformFile;
+ private final ArtifactId framework;
+
+ public SystemBundleDescriptor(ArtifactId framework, URL platformFile) {
+ super(framework.toMvnId());
+ this.platformFile = platformFile;
+ this.framework = framework;
+ }
+
+ @Override
+ public String getBundleSymbolicName() {
+ return Constants.SYSTEM_BUNDLE_SYMBOLICNAME;
+ }
+
+ @Override
+ public String getBundleVersion() {
+ return framework.getOSGiVersion().toString();
+ }
+
+ @Override
+ public URL getArtifactFile() {
+ return platformFile;
+ }
+
+ @Override
+ public Artifact getArtifact() {
+ return new Artifact(framework);
+ }
+
+ @Override
+ public Manifest getManifest() {
+ return new Manifest();
+ }
+}
\ No newline at end of file
diff --git
a/src/test/java/org/apache/sling/feature/analyser/extensions/AnalyserMetaDataExtensionTest.java
b/src/test/java/org/apache/sling/feature/analyser/extensions/AnalyserMetaDataExtensionTest.java
index fbb388a..d417aa8 100644
---
a/src/test/java/org/apache/sling/feature/analyser/extensions/AnalyserMetaDataExtensionTest.java
+++
b/src/test/java/org/apache/sling/feature/analyser/extensions/AnalyserMetaDataExtensionTest.java
@@ -18,14 +18,27 @@
*/
package org.apache.sling.feature.analyser.extensions;
-import org.apache.sling.feature.*;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Objects;
+import java.util.jar.Manifest;
+
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.ExtensionState;
+import org.apache.sling.feature.ExtensionType;
+import org.apache.sling.feature.Feature;
+import
org.apache.sling.feature.analyser.extensions.AnalyserMetaDataExtension.SystemBundle;
+import org.apache.sling.feature.io.json.FeatureJSONReader;
import org.apache.sling.feature.scanner.BundleDescriptor;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;
-import java.util.jar.Manifest;
-
public class AnalyserMetaDataExtensionTest {
@Test
public void testAnalyserMetaDataExtension() throws Exception {
@@ -58,4 +71,27 @@ public class AnalyserMetaDataExtensionTest {
Assert.assertFalse(testAnalyserMetaDataExtension.reportError(artifact.getId()));
}
+
+ @Test
+ public void readSystemBundleInformation() throws IOException {
+ InputStream featureStream =
Objects.requireNonNull(getClass().getResourceAsStream("/analyse-metadata/feature-output-system-bundle.json"),
"Unable to locate feature file");
+ Feature feature = FeatureJSONReader.read(new
InputStreamReader(featureStream), null);
+
+ Extension extension =
Objects.requireNonNull(feature.getExtensions().getByName(AnalyserMetaDataExtension.EXTENSION_NAME),
"Failed to load analyser-metadata extension");
+
+ AnalyserMetaDataExtension analyserMetaDataExtension =
AnalyserMetaDataExtension.getAnalyserMetaDataExtension(extension);
+ assertThat(analyserMetaDataExtension).as("metadata
extension").isNotNull();
+
+ SystemBundle systemBundle =
analyserMetaDataExtension.getSystemBundle();
+ assertThat(systemBundle).as("system bundle")
+ .isNotNull()
+ .extracting(SystemBundle::getArtifactId)
+
.isEqualTo(ArtifactId.fromMvnId("org.apache.felix:org.apache.felix.framework:7.0.5"));
+
+ assertThat(systemBundle.getScannerCacheKey()).as("system bundle
scanner cache key")
+ .isNotBlank();
+
+ assertThat(systemBundle.getManifest()).as("system bundle manifest")
+ .containsKeys("Export-Package", "Provide-Capability");
+ }
}
diff --git
a/src/test/java/org/apache/sling/feature/analyser/extensions/AnalyserMetaDataHandlerTest.java
b/src/test/java/org/apache/sling/feature/analyser/extensions/AnalyserMetaDataHandlerTest.java
index 9e4a668..846a620 100644
---
a/src/test/java/org/apache/sling/feature/analyser/extensions/AnalyserMetaDataHandlerTest.java
+++
b/src/test/java/org/apache/sling/feature/analyser/extensions/AnalyserMetaDataHandlerTest.java
@@ -16,16 +16,24 @@
*/
package org.apache.sling.feature.analyser.extensions;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.InstanceOfAssertFactories.MAP;
+import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringWriter;
import java.io.UncheckedIOException;
import java.net.MalformedURLException;
import java.net.URL;
-
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import org.apache.felix.utils.manifest.Clause;
+import org.apache.felix.utils.manifest.Parser;
+import org.apache.sling.feature.ArtifactId;
import org.apache.sling.feature.Feature;
import org.apache.sling.feature.builder.ArtifactProvider;
import org.apache.sling.feature.builder.HandlerContext;
@@ -34,18 +42,117 @@ import org.apache.sling.feature.io.json.FeatureJSONWriter;
import org.junit.Test;
import org.mockito.Mockito;
+import jakarta.json.JsonObject;
+import jakarta.json.JsonString;
+import jakarta.json.JsonValue;
+
public class AnalyserMetaDataHandlerTest {
@Test
public void testMetaDataHandler() throws Exception {
- URL url =
getClass().getResource("/analyse-metadata/feature-input.json");
+
+ Feature feature =
postProcessFeature("/analyse-metadata/feature-input.json");
+
+ URL expectedURL =
getClass().getResource("/analyse-metadata/feature-expected-output.json");
+
+ Feature expected;
+ try (Reader r = new InputStreamReader(expectedURL.openStream())) {
+ expected = FeatureJSONReader.read(r, expectedURL.toString());
+ }
+
+ StringWriter featureWriter = new StringWriter();
+ StringWriter expectedWriter = new StringWriter();
+
+ FeatureJSONWriter.write(featureWriter, feature);
+ FeatureJSONWriter.write(expectedWriter, expected);
+
+
assertThat(expectedWriter.toString()).isEqualTo(featureWriter.toString());
+ }
+
+ @Test
+ public void testMetadataHandlerSystemBundle() throws IOException {
+
+ Feature feature =
postProcessFeature("/analyse-metadata/feature-input-system-bundle.json");
+
+ JsonObject metadata =
feature.getExtensions().getByName("analyser-metadata").getJSONStructure().asJsonObject();
+ assertThat(metadata).as("analyser-metadata extension").isNotNull();
+
+ JsonValue systemBundle =
metadata.get("extra-metadata:system.bundle:0");
+ assertThat(systemBundle).as("system.bundle property of the metadata
extension").isNotNull();
+
+ // ensure the artifactId is recorded and correctly formed
+ JsonValue artifactId = systemBundle.asJsonObject().get("artifactId");
+ assertThat(artifactId).as("artifactId of the system
bundle").isNotNull();
+ ArtifactId.fromMvnId(artifactId.toString());
+
+ JsonValue manifest = systemBundle.asJsonObject().get("manifest");
+ assertThat(manifest).as("manifest of the system bundle").isNotNull();
+
+ JsonString systemBundleCapabilities =
manifest.asJsonObject().getJsonString("Provide-Capability");
+ assertThat(systemBundleCapabilities).as("capabilities for the system
bundle").isNotNull();
+ String capabilities = systemBundleCapabilities.getString();
+
+ // validate that the capabilities header is correctly formed
+ Clause[] clauses = Parser.parseHeader(capabilities);
+
+ // validate that we have exactly one osgi.ee clause with the value
"JavaSE"
+ List<Clause> javaSeEeClauses = Arrays.stream(clauses)
+ .filter( c -> c.getName().equals("osgi.ee") )
+ .filter( c -> c.getAttribute("osgi.ee").equals("JavaSE") )
+ .collect(Collectors.toList());
+
+ assertThat(javaSeEeClauses).as("osgi.ee=JavaSE capabilities")
+ .hasSize(1)
+ .element(0).extracting( c ->
c.getAttribute("version:List<Version>") )
+ .asString().contains("1.8");
+
+ JsonString systemBundleExports =
manifest.asJsonObject().getJsonString("Export-Package");
+ assertThat(systemBundleExports).as("exports for the system
bundle").isNotNull();
+ String exports = systemBundleExports.getString();
+
+ // validate that the exports header is correctly formed
+ Clause[] exportClauses = Parser.parseHeader(exports);
+ List<Clause> javaUtilExports = Arrays.stream(exportClauses)
+ .filter( c -> c.getName().equals("java.util") )
+ .collect(Collectors.toList());
+
+ // validate that the java.util.function export is present
+ assertThat(javaUtilExports).as("java.util package exports")
+ .hasSize(1)
+ .element(0).extracting( c -> c.getAttribute("version") )
+ .isNotNull();
+ }
+
+ @Test
+ public void testMetadataHandlerSystemBundleAlreadyProcessed() throws
IOException {
+
+ // the minimal feature used in this test would not be processed
successfully, as it does not
+ // have an execution-environment extension . This test guarantees that
processing is skipped and the
+ // information preserved
+ Feature feature =
postProcessFeature("/analyse-metadata/feature-output-system-bundle.json");
+
+ JsonObject metadata =
feature.getExtensions().getByName("analyser-metadata").getJSONStructure().asJsonObject();
+ assertThat(metadata).as("analyser-metadata extension").isNotNull();
+
+ JsonValue systemBundle =
metadata.get("extra-metadata:system.bundle:0");
+ assertThat(systemBundle).as("system.bundle property of the metadata
extension")
+ .isNotNull()
+ .extracting(JsonValue::asJsonObject)
+ .asInstanceOf(MAP)
+ .containsKeys("manifest", "artifactId", "scannerCacheKey");
+ }
+
+ private Feature postProcessFeature(String featureLocation) throws
IOException {
+ URL url = getClass().getResource(featureLocation);
+
+ Objects.requireNonNull(url, "Failed to load feature file from " +
featureLocation);
Feature feature;
try (Reader r = new InputStreamReader(url.openStream())) {
feature = FeatureJSONReader.read(r, url.toString());
}
- assertNotNull(feature);
+ assertThat(feature).isNotNull();
HandlerContext ctx = Mockito.mock(HandlerContext.class);
ArtifactProvider provider = artifactId -> {
@@ -58,23 +165,9 @@ public class AnalyserMetaDataHandlerTest {
Mockito.when(ctx.getArtifactProvider()).thenReturn(provider);
- new AnalyserMetaDataHandler().
- postProcess(ctx, feature,
feature.getExtensions().getByName("analyser-metadata"));
-
- URL expectedURL =
getClass().getResource("/analyse-metadata/feature-expected-output.json");
-
- Feature expected;
- try (Reader r = new InputStreamReader(expectedURL.openStream())) {
- expected = FeatureJSONReader.read(r, expectedURL.toString());
- }
-
- StringWriter featureWriter = new StringWriter();
- StringWriter expectedWriter = new StringWriter();
-
- FeatureJSONWriter.write(featureWriter, feature);
- FeatureJSONWriter.write(expectedWriter, expected);
-
- assertEquals(expectedWriter.toString(), featureWriter.toString());
+ new AnalyserMetaDataHandler().postProcess(ctx, feature,
feature.getExtensions().getByName("analyser-metadata"));
+
+ return feature;
}
}
diff --git
a/src/test/resources/analyse-metadata/feature-input-system-bundle.json
b/src/test/resources/analyse-metadata/feature-input-system-bundle.json
new file mode 100644
index 0000000..b860df1
--- /dev/null
+++ b/src/test/resources/analyse-metadata/feature-input-system-bundle.json
@@ -0,0 +1,12 @@
+{
+ "id": "org.acme:acmefeature:slingosgifeature:metadata-feature:0.0.1",
+ "execution-environment:JSON|false":{
+ "framework":{
+ "id":"org.apache.felix:org.apache.felix.framework:7.0.5"
+ }
+ },
+ "analyser-metadata:JSON|true": {
+ "extra-metadata:system.bundle:0" : {
+ }
+ }
+}
\ No newline at end of file
diff --git
a/src/test/resources/analyse-metadata/feature-output-system-bundle.json
b/src/test/resources/analyse-metadata/feature-output-system-bundle.json
new file mode 100644
index 0000000..091cdc3
--- /dev/null
+++ b/src/test/resources/analyse-metadata/feature-output-system-bundle.json
@@ -0,0 +1,13 @@
+{
+ "id": "org.acme:acmefeature:slingosgifeature:metadata-feature:0.0.1",
+ "analyser-metadata:JSON|false": {
+ "extra-metadata:system.bundle:0": {
+ "manifest": {
+ "Provide-Capability": "osgi.ee; osgi.ee=\"JavaSE/compact2\";
version:List<Version>=\"1.8.0,9.0.0,10.0.0,11.0.0,12.0.0,13.0.0,14.0.0,15.0.0,16.0.0,17.0.0,18.0.0,19.0.0,20.0.0,21.0.0\",osgi.ee;
osgi.ee=\"JavaSE/compact3\";
version:List<Version>=\"1.8.0,9.0.0,10.0.0,11.0.0,12.0.0,13.0.0,14.0.0,15.0.0,16.0.0,17.0.0,18.0.0,19.0.0,20.0.0,21.0.0\",osgi.ee;
osgi.ee=\"OSGi/Minimum\"; version:List<Version>=\"1.0.0,1.1.0,1.2.0\",osgi.ee;
osgi.ee=\"JavaSE/compact1\"; version:List<Version [...]
+ "Export-Package":
"com.sun.source.util;version=0.0.0.JavaSE_021,java.security.interfaces;version=0.0.0.JavaSE_021,com.sun.security.auth.module;version=0.0.0.JavaSE_021,org.osgi.framework.launch;version=1.2.0,java.time;version=0.0.0.JavaSE_021,org.osgi.framework.hooks.weaving;version=1.1.0,org.w3c.dom.bootstrap;version=0.0.0.JavaSE_021,javax.swing.text.html.parser;version=0.0.0.JavaSE_021,javax.sound.midi.spi;version=0.0.0.JavaSE_021,javax.swing.filechooser;version=0.0.0.JavaS
[...]
+ },
+ "artifactId": "org.apache.felix:org.apache.felix.framework:7.0.5",
+ "scannerCacheKey":
"org.apache.felix:org.apache.felix.framework:7.0.5:felix.cm.config.plugins=org.apache.felix.configadmin.plugin.interpolation:felix.systempackages.calculate.uses=true:felix.systempackages.substitution=true:localIndexDir=${sling.home}/repository/index:org.osgi.framework.bootdelegation=sun.*,com.sun.*,jdk.internal.reflect,jdk.internal.reflect.*:org.osgi.framework.system.packages.extra={dollar}{sling.jre-{dollar}{java.specification.version}.javax.xml}{dollar}{sli
[...]
+ }
+ }
+}
\ No newline at end of file