This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ant-antlibs-cyclonedx.git
The following commit(s) were added to refs/heads/main by this push:
new 581ae00 extract SBOM link reading from Component class
581ae00 is described below
commit 581ae00b98e7e050a6eab32b53b6b7a49a42ca1e
Author: Stefan Bodewig <[email protected]>
AuthorDate: Sun Jul 19 16:40:07 2026 +0200
extract SBOM link reading from Component class
---
docs/component.html | 2 +-
src/main/org/apache/ant/cyclonedx/Component.java | 123 ++++------------
.../ant/cyclonedx/SbomLinkComponentResolver.java | 158 +++++++++++++++++++++
3 files changed, 185 insertions(+), 98 deletions(-)
diff --git a/docs/component.html b/docs/component.html
index 6aa3f9c..3a77bd8 100644
--- a/docs/component.html
+++ b/docs/component.html
@@ -168,7 +168,7 @@ <h4 id="sbomLink">sbomLink</h4>
component unless it is explicitly specified on the component
element itself or <code>supplierismanufacturer</code>
is <code>true</code>.</li>
- <li>Tags are merged wiht those of the SBOM's metadata
+ <li>Tags are merged with those of the SBOM's metadata
component.</li>
<li><code>author</code>s, <code>license</code>s,
<code>externalReference</code>s,
<code>dependency</code>s and nested <code>components</code>
diff --git a/src/main/org/apache/ant/cyclonedx/Component.java
b/src/main/org/apache/ant/cyclonedx/Component.java
index 146e998..d7c783c 100644
--- a/src/main/org/apache/ant/cyclonedx/Component.java
+++ b/src/main/org/apache/ant/cyclonedx/Component.java
@@ -17,10 +17,8 @@
*/
package org.apache.ant.cyclonedx;
-import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
-import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -39,18 +37,13 @@ import org.apache.tools.ant.types.Reference;
import org.apache.tools.ant.types.Resource;
import org.apache.tools.ant.types.resources.FileProvider;
import org.apache.tools.ant.types.resources.Union;
-import org.apache.tools.ant.types.resources.URLProvider;
import org.cyclonedx.Version;
-import org.cyclonedx.exception.ParseException;
-import org.cyclonedx.model.Bom;
import org.cyclonedx.model.LicenseChoice;
import org.cyclonedx.model.OrganizationalContact;
import org.cyclonedx.model.OrganizationalEntity;
import org.cyclonedx.model.Property;
import org.cyclonedx.model.component.Tags;
-import org.cyclonedx.parsers.BomParserFactory;
-import org.cyclonedx.parsers.Parser;
import org.cyclonedx.util.BomUtils;
/**
@@ -563,6 +556,20 @@ public class Component extends DataType {
return result;
}
+ /**
+ * Gets the dependencies of the component.
+ *
+ * @return component's dependencies
+ * @since CycloneDX Antlib 0.2
+ */
+ Collection<org.cyclonedx.model.ExternalReference> getExternalReferences() {
+ if (isReference()) {
+ return getRef().getExternalReferences();
+ }
+ dieOnCircularReference();
+ return externalReferences;
+ }
+
/**
* Read the linked SBOM (if any) and merge its content with the
* one already defined for this component.
@@ -580,35 +587,8 @@ public class Component extends DataType {
if (sbomLink != null && !sbomLinkResolved) {
sbomLinkResolved = true;
- Bom bom = readLinkedSbom();
- if (bom.getMetadata() == null) {
- throw new BuildException("referenced SBOM file lacks
metadata");
- }
- org.cyclonedx.model.Component real =
bom.getMetadata().getComponent();
- if (real == null) {
- throw new BuildException("referenced SBOM file lacks
component");
- }
- List<org.cyclonedx.model.Dependency> allDependencies =
bom.getDependencies();
- fillFromBomLink(real, allDependencies);
- if (sbomLink.getCreateBomExternalReference()
- && !externalReferences.stream()
- .anyMatch(e ->
e.getType().equals(org.cyclonedx.model.ExternalReference.Type.BOM))) {
- Resource sbom = sbomLink.iterator().next();
- URLProvider up = sbom.as(URLProvider.class);
- if (up != null) {
- ExternalReference e = new ExternalReference();
- e.setUrl(up.getURL().toExternalForm());
-
e.setType(org.cyclonedx.model.ExternalReference.Type.BOM.name());
- addConfiguredExternalReference(e);
- }
- }
-
- if (!areDependenciesUnknown() && !dependencies.isEmpty()) {
- List<org.cyclonedx.model.Component> additionalComponents =
bom.getComponents();
- if (additionalComponents != null) {
- return
extractComponentsThatAreDirectDependencies(additionalComponents);
- }
- }
+ SbomLinkComponentResolver resolver = new
SbomLinkComponentResolver(getProject(), sbomLink);
+ return resolver.resolve(this);
}
return Collections.emptyList();
@@ -769,31 +749,16 @@ public class Component extends DataType {
return component;
}
- private List<Component>
extractComponentsThatAreDirectDependencies(List<org.cyclonedx.model.Component>
cs) {
- List<Component> toReturn = new ArrayList<>();
- for (org.cyclonedx.model.Component c : cs) {
- Component dep = from(c, Collections.emptyList());
- if (dependencies.stream().anyMatch(d ->
Objects.equals(dep.getBomRef(), d.getBomRef()))) {
- // only include "additional components" this component depends
on directly.
- // we don't want to resolve transitive dependencies
automatically
- dep.setUnknownDependencies(true);
- toReturn.add(dep);
- }
- }
- return toReturn;
- }
-
- private static Component from(
+ static Component from(
org.cyclonedx.model.Component real,
List<org.cyclonedx.model.Dependency> dependencies) {
Component c = new Component();
- c.fillFromBomLink(real, dependencies);
+ c.fillFrom(real, dependencies);
return c;
}
- private void fillFromBomLink(
- org.cyclonedx.model.Component real,
- List<org.cyclonedx.model.Dependency> allDependencies) {
+ void fillFrom(org.cyclonedx.model.Component real,
+ List<org.cyclonedx.model.Dependency> allDependencies) {
if (type == null) {
setType(ComponentType.from(real.getType()));
}
@@ -878,13 +843,15 @@ public class Component extends DataType {
.collect(Collectors.toList()));
}
}
- if (dependencies.isEmpty() && allDependencies != null) {
- fillDependenciesFromBomLink(allDependencies);
+ if (!areDependenciesUnknown()) {
+ fillDependencies(allDependencies);
}
}
- private void
fillDependenciesFromBomLink(List<org.cyclonedx.model.Dependency>
allDependencies) {
- setUnknownDependencies(true);
+ private void fillDependencies(List<org.cyclonedx.model.Dependency>
allDependencies) {
+ if (dependencies.isEmpty()) {
+ setUnknownDependencies(true);
+ }
org.cyclonedx.model.Dependency myDependencies = allDependencies
.stream()
.filter(d -> Objects.equals(d.getRef(), getBomRef()))
@@ -931,44 +898,6 @@ public class Component extends DataType {
component.setHashes(BomUtils.calculateHashes(file, bomVersion));
}
- private Bom readLinkedSbom() throws IOException {
- if (sbomLink.size() != 1) {
- throw new BuildException("sbomLink requires exactly one nested
resource");
- }
- Resource sbom = sbomLink.iterator().next();
- logSbom(sbom);
- try (InputStream data = sbom.getInputStream();
- ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
- byte[] buf = new byte[4096];
- int count = data.read(buf, 0, buf.length);
- while (count >= 0) {
- baos.write(buf, 0, count);
- count = data.read(buf, 0, buf.length);
- }
- byte[] content = baos.toByteArray();
- try {
- Parser parser = BomParserFactory.createParser(content);
- return parser.parse(content);
- } catch (ParseException ex) {
- throw new BuildException("failed to parse sbomlink " +
sbom.getName(), ex);
- }
- }
- }
-
- private void logSbom(Resource r) {
- String name = r.getName();
- FileProvider fp = r.as(FileProvider.class);
- if (fp != null) {
- name = fp.getFile().getAbsolutePath();
- } else {
- URLProvider up = r.as(URLProvider.class);
- if (up != null) {
- name = up.getURL().toExternalForm();
- }
- }
- log("reading SBOM from " + name, Project.MSG_VERBOSE);
- }
-
/**
* Represents a dependency of a component.
*/
diff --git a/src/main/org/apache/ant/cyclonedx/SbomLinkComponentResolver.java
b/src/main/org/apache/ant/cyclonedx/SbomLinkComponentResolver.java
new file mode 100644
index 0000000..4142921
--- /dev/null
+++ b/src/main/org/apache/ant/cyclonedx/SbomLinkComponentResolver.java
@@ -0,0 +1,158 @@
+/*
+ * 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
+ *
+ * https://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.ant.cyclonedx;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.types.Resource;
+import org.apache.tools.ant.types.resources.FileProvider;
+import org.apache.tools.ant.types.resources.URLProvider;
+
+import org.cyclonedx.exception.ParseException;
+import org.cyclonedx.model.Bom;
+import org.cyclonedx.parsers.BomParserFactory;
+import org.cyclonedx.parsers.Parser;
+
+/**
+ * Reads a linked SBOM and configures the component containing the link.
+ *
+ * @since CycloneDX Antlib 0.2
+ */
+class SbomLinkComponentResolver {
+ private final Project project;
+ private final Component.SbomLink sbomLink;
+
+ SbomLinkComponentResolver(Project project, Component.SbomLink sbomLink) {
+ this.project = project;
+ this.sbomLink = sbomLink;
+ }
+
+ /**
+ * Configures the given component and only adds dependencies if the
component doesn't already know about any
+ * dependencies itself.
+ *
+ * @param parent component the link applies to
+ * @return Components that are direct dependencies of the parent component
+ */
+ Collection<Component> resolve(Component parent) throws IOException {
+ Bom bom = readLinkedSbom();
+ if (bom.getMetadata() == null) {
+ throw new BuildException("referenced SBOM file lacks metadata");
+ }
+ org.cyclonedx.model.Component real = bom.getMetadata().getComponent();
+ if (real == null) {
+ throw new BuildException("referenced SBOM file lacks component");
+ }
+ List<org.cyclonedx.model.Dependency> allDependencies =
bom.getDependencies();
+
+ // only add dependencies if the component doesn't already have any
dependency configuration itself
+ if (!parent.areDependenciesUnknown()
+ && !parent.getDependencies().iterator().hasNext()
+ && allDependencies != null) {
+ parent.fillFrom(real, allDependencies);
+ } else {
+ parent.fillFrom(real, Collections.emptyList());
+ }
+
+ if (sbomLink.getCreateBomExternalReference()
+ && !parent.getExternalReferences().stream()
+ .anyMatch(e ->
e.getType().equals(org.cyclonedx.model.ExternalReference.Type.BOM))) {
+ Resource sbom = sbomLink.iterator().next();
+ URLProvider up = sbom.as(URLProvider.class);
+ if (up != null) {
+ ExternalReference e = new ExternalReference();
+ e.setUrl(up.getURL().toExternalForm());
+
e.setType(org.cyclonedx.model.ExternalReference.Type.BOM.name());
+ parent.addConfiguredExternalReference(e);
+ }
+ }
+
+ if (!parent.areDependenciesUnknown() &&
parent.getDependencies().iterator().hasNext()) {
+ List<org.cyclonedx.model.Component> additionalComponents =
bom.getComponents();
+ if (additionalComponents != null) {
+ return extractComponentsThatAreDirectDependencies(parent,
additionalComponents);
+ }
+ }
+
+ return Collections.emptyList();
+ }
+
+ private Bom readLinkedSbom() throws IOException {
+ if (sbomLink.size() != 1) {
+ throw new BuildException("sbomLink requires exactly one nested
resource");
+ }
+ Resource sbom = sbomLink.iterator().next();
+ logSbom(sbom);
+ try (InputStream data = sbom.getInputStream();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+ byte[] buf = new byte[4096];
+ int count = data.read(buf, 0, buf.length);
+ while (count >= 0) {
+ baos.write(buf, 0, count);
+ count = data.read(buf, 0, buf.length);
+ }
+ byte[] content = baos.toByteArray();
+ try {
+ Parser parser = BomParserFactory.createParser(content);
+ return parser.parse(content);
+ } catch (ParseException ex) {
+ throw new BuildException("failed to parse sbomlink " +
sbom.getName(), ex);
+ }
+ }
+ }
+
+ private void logSbom(Resource r) {
+ String name = r.getName();
+ FileProvider fp = r.as(FileProvider.class);
+ if (fp != null) {
+ name = fp.getFile().getAbsolutePath();
+ } else {
+ URLProvider up = r.as(URLProvider.class);
+ if (up != null) {
+ name = up.getURL().toExternalForm();
+ }
+ }
+ project.log("reading SBOM from " + name, Project.MSG_VERBOSE);
+ }
+
+ private List<Component>
extractComponentsThatAreDirectDependencies(Component parent,
+ List<org.cyclonedx.model.Component> cs) {
+ List<Component> toReturn = new ArrayList<>();
+ List<Component.Dependency> dependencies = new ArrayList<>();
+
parent.getDependencies().iterator().forEachRemaining(dependencies::add);
+ for (org.cyclonedx.model.Component c : cs) {
+ Component dep = Component.from(c, Collections.emptyList());
+ if (dependencies.stream().anyMatch(d ->
Objects.equals(dep.getBomRef(), d.getBomRef()))) {
+ // only include "additional components" the parent component
depends on directly.
+ // we don't want to resolve transitive dependencies
automatically here
+ dep.setUnknownDependencies(true);
+ toReturn.add(dep);
+ }
+ }
+ return toReturn;
+ }
+}