From 150666d1bb56638e206f243a6ddcf0c2a73a031f Mon Sep 17 00:00:00 2001 From: Andrea Vibelli Date: Tue, 28 Jan 2025 14:52:06 +0100 Subject: [PATCH 1/2] feat: Add missing provides mapping to Dependency to comply with CycloneDX 1.6 spec Signed-off-by: Andrea Vibelli --- .../java/org/cyclonedx/model/Dependency.java | 26 +++++++++++++++++++ .../util/serializer/DependencySerializer.java | 13 ++++++++++ .../resources/1.6/valid-dependency-1.6.json | 12 +++++++++ .../1.6/valid-dependency-1.6.textproto | 11 ++++++++ .../resources/1.6/valid-dependency-1.6.xml | 7 +++++ 5 files changed, 69 insertions(+) diff --git a/src/main/java/org/cyclonedx/model/Dependency.java b/src/main/java/org/cyclonedx/model/Dependency.java index 125d18b4e..49d22c41e 100644 --- a/src/main/java/org/cyclonedx/model/Dependency.java +++ b/src/main/java/org/cyclonedx/model/Dependency.java @@ -18,6 +18,7 @@ */ package org.cyclonedx.model; +import org.cyclonedx.Version; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; @@ -36,6 +37,11 @@ public class Dependency extends BomReference { @JacksonXmlProperty(localName = "dependency") private List dependencies; + @VersionFilter(Version.VERSION_16) + @JsonProperty("provides") + @JacksonXmlProperty(localName = "provides") + private List provides; + public Dependency(final String ref) { super(ref); } @@ -60,6 +66,26 @@ public void addDependency(final Dependency dependency) { } } + @VersionFilter(Version.VERSION_16) + public List getProvides() { + return provides; + } + + @VersionFilter(Version.VERSION_16) + public void setProvides(final List provides) { + this.provides = provides; + } + + @VersionFilter(Version.VERSION_16) + public void addProvides(final Dependency dependency) { + if (provides == null) { + provides = new ArrayList<>(); + } + boolean found = provides.stream().anyMatch(d -> d.getRef().equals(dependency.getRef())); + if (!found) { + provides.add(dependency); + } + } @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/src/main/java/org/cyclonedx/util/serializer/DependencySerializer.java b/src/main/java/org/cyclonedx/util/serializer/DependencySerializer.java index c4af1de98..60b4e6a61 100644 --- a/src/main/java/org/cyclonedx/util/serializer/DependencySerializer.java +++ b/src/main/java/org/cyclonedx/util/serializer/DependencySerializer.java @@ -97,6 +97,13 @@ private void writeJSONDependenciesWithGenerator(final JsonGenerator generator, f } } generator.writeEndArray(); + if (CollectionUtils.isNotEmpty(dependency.getProvides())) { + generator.writeArrayFieldStart("provides"); + for (Dependency subDependency : dependency.getProvides()) { + generator.writeString(subDependency.getRef()); + } + generator.writeEndArray(); + } generator.writeEndObject(); } } @@ -141,6 +148,12 @@ private void writeXMLDependency(final Dependency dependency, final ToXmlGenerato } } + if (CollectionUtils.isNotEmpty(dependency.getProvides())) { + for (Dependency subDependency : dependency.getProvides()) { + writeXMLDependency(subDependency, generator); + } + } + if (CollectionUtils.isNotEmpty(dependency.getDependencies())) { generator.writeEndArray(); } diff --git a/src/test/resources/1.6/valid-dependency-1.6.json b/src/test/resources/1.6/valid-dependency-1.6.json index 1e87f38ef..289cf8b5b 100644 --- a/src/test/resources/1.6/valid-dependency-1.6.json +++ b/src/test/resources/1.6/valid-dependency-1.6.json @@ -22,6 +22,12 @@ "type": "library", "name": "library-c", "version": "1.0.0" + }, + { + "bom-ref": "library-d", + "type": "library", + "name": "library-d", + "version": "1.0.0" } ], "dependencies": [ @@ -34,6 +40,12 @@ "dependsOn": [ "library-c" ] + }, + { + "ref": "library-c", + "provides": [ + "library-d" + ] } ] } diff --git a/src/test/resources/1.6/valid-dependency-1.6.textproto b/src/test/resources/1.6/valid-dependency-1.6.textproto index 363dfba93..a636b12af 100644 --- a/src/test/resources/1.6/valid-dependency-1.6.textproto +++ b/src/test/resources/1.6/valid-dependency-1.6.textproto @@ -22,6 +22,12 @@ components { name: "library-c" version: "1.0.0" } +components { + type: CLASSIFICATION_LIBRARY + bom_ref: "library-d" + name: "library-d" + version: "1.0.0" +} dependencies { ref: "library-a" } @@ -31,3 +37,8 @@ dependencies { ref: "library-c" } } +dependencies { + ref: "library-c" + provides: ["library-d"] + } +} \ No newline at end of file diff --git a/src/test/resources/1.6/valid-dependency-1.6.xml b/src/test/resources/1.6/valid-dependency-1.6.xml index 7fab83476..9cdd44fa9 100644 --- a/src/test/resources/1.6/valid-dependency-1.6.xml +++ b/src/test/resources/1.6/valid-dependency-1.6.xml @@ -13,11 +13,18 @@ library-c 1.0.0 + + library-d + 1.0.0 + + + + From 4e13d3a0eaddec33608be0be648012f92dd536ad Mon Sep 17 00:00:00 2001 From: Andrea Vibelli Date: Sat, 29 Mar 2025 14:20:04 +0100 Subject: [PATCH 2/2] Fixed mapping to have a list of BomReference for provides; added tests Signed-off-by: Andrea Vibelli --- .../java/org/cyclonedx/model/Dependency.java | 12 ++- .../util/serializer/DependencySerializer.java | 35 +++++++-- .../org/cyclonedx/BomJsonGeneratorTest.java | 75 +++++++++++++++++-- .../org/cyclonedx/BomXmlGeneratorTest.java | 65 ++++++++++++++++ .../resources/1.6/valid-dependency-1.6.json | 14 +--- .../1.6/valid-dependency-1.6.textproto | 11 --- .../resources/1.6/valid-dependency-1.6.xml | 9 +-- .../1.6/valid-dependency-provides-1.6.json | 51 +++++++++++++ .../valid-dependency-provides-1.6.textproto | 44 +++++++++++ .../1.6/valid-dependency-provides-1.6.xml | 30 ++++++++ 10 files changed, 291 insertions(+), 55 deletions(-) create mode 100644 src/test/resources/1.6/valid-dependency-provides-1.6.json create mode 100644 src/test/resources/1.6/valid-dependency-provides-1.6.textproto create mode 100644 src/test/resources/1.6/valid-dependency-provides-1.6.xml diff --git a/src/main/java/org/cyclonedx/model/Dependency.java b/src/main/java/org/cyclonedx/model/Dependency.java index 49d22c41e..eb3e4343a 100644 --- a/src/main/java/org/cyclonedx/model/Dependency.java +++ b/src/main/java/org/cyclonedx/model/Dependency.java @@ -39,8 +39,9 @@ public class Dependency extends BomReference { @VersionFilter(Version.VERSION_16) @JsonProperty("provides") + @JacksonXmlElementWrapper(useWrapping = false) @JacksonXmlProperty(localName = "provides") - private List provides; + private List provides; public Dependency(final String ref) { super(ref); @@ -66,18 +67,15 @@ public void addDependency(final Dependency dependency) { } } - @VersionFilter(Version.VERSION_16) - public List getProvides() { + public List getProvides() { return provides; } - @VersionFilter(Version.VERSION_16) - public void setProvides(final List provides) { + public void setProvides(final List provides) { this.provides = provides; } - @VersionFilter(Version.VERSION_16) - public void addProvides(final Dependency dependency) { + public void addProvides(final BomReference dependency) { if (provides == null) { provides = new ArrayList<>(); } diff --git a/src/main/java/org/cyclonedx/util/serializer/DependencySerializer.java b/src/main/java/org/cyclonedx/util/serializer/DependencySerializer.java index 60b4e6a61..06c6c907a 100644 --- a/src/main/java/org/cyclonedx/util/serializer/DependencySerializer.java +++ b/src/main/java/org/cyclonedx/util/serializer/DependencySerializer.java @@ -34,6 +34,7 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.cyclonedx.CycloneDxSchema; +import org.cyclonedx.model.BomReference; import org.cyclonedx.model.Dependency; import org.cyclonedx.model.DependencyList; @@ -99,7 +100,7 @@ private void writeJSONDependenciesWithGenerator(final JsonGenerator generator, f generator.writeEndArray(); if (CollectionUtils.isNotEmpty(dependency.getProvides())) { generator.writeArrayFieldStart("provides"); - for (Dependency subDependency : dependency.getProvides()) { + for (BomReference subDependency : dependency.getProvides()) { generator.writeString(subDependency.getRef()); } generator.writeEndArray(); @@ -141,6 +142,11 @@ private void writeXMLDependency(final Dependency dependency, final ToXmlGenerato generator.writeString(dependency.getRef()); generator.setNextIsAttribute(false); + // Write provides + if (CollectionUtils.isNotEmpty(dependency.getProvides())) { + writeXMLProvides(dependency, generator); + } + if (CollectionUtils.isNotEmpty(dependency.getDependencies())) { for (Dependency subDependency : dependency.getDependencies()) { // You got Shay'd @@ -148,17 +154,30 @@ private void writeXMLDependency(final Dependency dependency, final ToXmlGenerato } } - if (CollectionUtils.isNotEmpty(dependency.getProvides())) { - for (Dependency subDependency : dependency.getProvides()) { - writeXMLDependency(subDependency, generator); - } + if (CollectionUtils.isNotEmpty(dependency.getDependencies())) { + generator.writeEndArray(); } - if (CollectionUtils.isNotEmpty(dependency.getDependencies())) { - generator.writeEndArray(); + generator.writeEndObject(); } - generator.writeEndObject(); + private void writeXMLProvides(final Dependency dependency, final ToXmlGenerator generator) + throws IOException, XMLStreamException + { + QName qName = new QName("provides"); + generator.setNextName(qName); + generator.writeFieldName(qName.getLocalPart()); + generator.writeStartArray(); + + for (BomReference ref : dependency.getProvides()) { + generator.writeStartObject(); + generator.setNextIsAttribute(true); + generator.writeFieldName("ref"); + generator.writeString(ref.getRef()); + generator.setNextIsAttribute(false); + generator.writeEndObject(); + } + generator.writeEndArray(); } private void processNamespace(final ToXmlGenerator toXmlGenerator, final String dependencies) diff --git a/src/test/java/org/cyclonedx/BomJsonGeneratorTest.java b/src/test/java/org/cyclonedx/BomJsonGeneratorTest.java index 6a162c9f6..dddb9c91c 100644 --- a/src/test/java/org/cyclonedx/BomJsonGeneratorTest.java +++ b/src/test/java/org/cyclonedx/BomJsonGeneratorTest.java @@ -20,23 +20,20 @@ import com.fasterxml.jackson.databind.JsonNode; -import java.io.FileReader; import java.nio.charset.StandardCharsets; import org.apache.commons.io.IOUtils; -import org.cyclonedx.exception.GeneratorException; import org.cyclonedx.generators.BomGeneratorFactory; import org.cyclonedx.generators.json.BomJsonGenerator; import org.cyclonedx.generators.xml.BomXmlGenerator; import org.cyclonedx.model.Bom; import org.cyclonedx.model.Component; +import org.cyclonedx.model.Dependency; import org.cyclonedx.model.Component.Type; import org.cyclonedx.model.License; import org.cyclonedx.model.LicenseChoice; import org.cyclonedx.model.Metadata; -import org.cyclonedx.model.OrganizationalContact; import org.cyclonedx.model.Service; import org.cyclonedx.model.license.Expression; -import org.cyclonedx.model.metadata.ToolInformation; import org.cyclonedx.parsers.JsonParser; import org.cyclonedx.parsers.XmlParser; import org.junit.jupiter.api.AfterEach; @@ -52,10 +49,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedList; -import java.util.List; -import java.util.UUID; import java.util.stream.Stream; import java.util.Objects; @@ -336,6 +329,72 @@ public void schema16_testAttestations_json() throws Exception { assertTrue(parser.isValid(loadedFile, version)); } + @Test + public void schema16_testDependencyProvides_json() throws Exception { + Version version = Version.VERSION_16; + Bom bom = createCommonJsonBom("/1.6/valid-dependency-provides-1.6.json"); + + BomJsonGenerator generator = BomGeneratorFactory.createJson(version, bom); + File loadedFile = writeToFile(generator.toJsonString()); + + JsonParser parser = new JsonParser(); + assertTrue(parser.isValid(loadedFile, version)); + + Bom parsedBom = parser.parse(loadedFile); + assertNotNull(parsedBom.getDependencies()); + assertEquals(3, parsedBom.getDependencies().size()); + // Test dependency library-a + Dependency libA = parsedBom.getDependencies().get(0); + assertEquals("library-a", libA.getRef()); + assertNotNull(libA.getDependencies()); + assertEquals(0, libA.getDependencies().size()); + assertNull(libA.getProvides()); + // Test dependency library-b + Dependency libB = parsedBom.getDependencies().get(1); + assertEquals("library-b", libB.getRef()); + assertEquals(1, libB.getDependencies().size()); + assertEquals("library-c", libB.getDependencies().get(0).getRef()); + // Test dependency library-c + Dependency libC = parsedBom.getDependencies().get(2); + assertEquals("library-c", libC.getRef()); + assertNotNull(libC.getDependencies()); + assertNotNull(libC.getProvides()); + assertEquals("library-d", libC.getProvides().get(0).getRef()); + } + + @Test + public void schema16_testDependencyProvides() throws Exception { + Version version = Version.VERSION_16; + Bom bom = createCommonXmlBom("/1.6/valid-dependency-provides-1.6.xml"); + + BomJsonGenerator generator = BomGeneratorFactory.createJson(version, bom); + File loadedFile = writeToFile(generator.toJsonString()); + + JsonParser parser = new JsonParser(); + assertTrue(parser.isValid(loadedFile, version)); + + Bom parsedBom = parser.parse(loadedFile); + assertNotNull(parsedBom.getDependencies()); + assertEquals(3, parsedBom.getDependencies().size()); + // Test dependency library-a + Dependency libA = parsedBom.getDependencies().get(0); + assertEquals("library-a", libA.getRef()); + assertNotNull(libA.getDependencies()); + assertEquals(0, libA.getDependencies().size()); + assertNull(libA.getProvides()); + // Test dependency library-b + Dependency libB = parsedBom.getDependencies().get(1); + assertEquals("library-b", libB.getRef()); + assertEquals(1, libB.getDependencies().size()); + assertEquals("library-c", libB.getDependencies().get(0).getRef()); + // Test dependency library-c + Dependency libC = parsedBom.getDependencies().get(2); + assertEquals("library-c", libC.getRef()); + assertNotNull(libC.getDependencies()); + assertNotNull(libC.getProvides()); + assertEquals("library-d", libC.getProvides().get(0).getRef()); + } + @Test public void schema16_testCompositions() throws Exception { Version version = Version.VERSION_16; diff --git a/src/test/java/org/cyclonedx/BomXmlGeneratorTest.java b/src/test/java/org/cyclonedx/BomXmlGeneratorTest.java index bf041845b..63ec017d1 100644 --- a/src/test/java/org/cyclonedx/BomXmlGeneratorTest.java +++ b/src/test/java/org/cyclonedx/BomXmlGeneratorTest.java @@ -28,6 +28,7 @@ import org.cyclonedx.model.Bom; import org.cyclonedx.model.Component; import org.cyclonedx.model.Component.Type; +import org.cyclonedx.model.Dependency; import org.cyclonedx.model.ExtensibleType; import org.cyclonedx.model.ExternalReference; import org.cyclonedx.model.License; @@ -454,6 +455,70 @@ public void schema16_testAttestations_xml() throws Exception { assertTrue(parser.isValid(loadedFile, version)); } + @Test + public void schema16_testDependencyProvides() throws Exception { + Version version = Version.VERSION_16; + Bom bom = createCommonJsonBom("/1.6/valid-dependency-provides-1.6.json"); + + BomXmlGenerator generator = BomGeneratorFactory.createXml(version, bom); + File loadedFile = writeToFile(generator.toXmlString()); + + XmlParser parser = new XmlParser(); + assertTrue(parser.isValid(loadedFile, version)); + + Bom parsedBom = parser.parse(loadedFile); + assertNotNull(parsedBom.getDependencies()); + assertEquals(3, parsedBom.getDependencies().size()); + // Test dependency library-a + Dependency libA = parsedBom.getDependencies().get(0); + assertEquals("library-a", libA.getRef()); + assertNull(libA.getDependencies()); + assertNull(libA.getProvides()); + // Test dependency library-b + Dependency libB = parsedBom.getDependencies().get(1); + assertEquals("library-b", libB.getRef()); + assertEquals(1, libB.getDependencies().size()); + assertEquals("library-c", libB.getDependencies().get(0).getRef()); + // Test dependency library-c + Dependency libC = parsedBom.getDependencies().get(2); + assertEquals("library-c", libC.getRef()); + assertNull(libC.getDependencies()); + assertNotNull(libC.getProvides()); + assertEquals("library-d", libC.getProvides().get(0).getRef()); + } + + @Test + public void schema16_testDependencyProvides_xml() throws Exception { + Version version = Version.VERSION_16; + Bom bom = createCommonBomXml("/1.6/valid-dependency-provides-1.6.xml"); + + BomXmlGenerator generator = BomGeneratorFactory.createXml(version, bom); + File loadedFile = writeToFile(generator.toXmlString()); + + XmlParser parser = new XmlParser(); + assertTrue(parser.isValid(loadedFile, version)); + + Bom parsedBom = parser.parse(loadedFile); + assertNotNull(parsedBom.getDependencies()); + assertEquals(3, parsedBom.getDependencies().size()); + // Test dependency library-a + Dependency libA = parsedBom.getDependencies().get(0); + assertEquals("library-a", libA.getRef()); + assertNull(libA.getDependencies()); + assertNull(libA.getProvides()); + // Test dependency library-b + Dependency libB = parsedBom.getDependencies().get(1); + assertEquals("library-b", libB.getRef()); + assertEquals(1, libB.getDependencies().size()); + assertEquals("library-c", libB.getDependencies().get(0).getRef()); + // Test dependency library-c + Dependency libC = parsedBom.getDependencies().get(2); + assertEquals("library-c", libC.getRef()); + assertNull(libC.getDependencies()); + assertNotNull(libC.getProvides()); + assertEquals("library-d", libC.getProvides().get(0).getRef()); + } + private void addSignature(Bom bom) { List attributes = new ArrayList<>(); attributes.add(new Attribute("xmlns", "http://www.w3.org/2000/09/xmldsig#")); diff --git a/src/test/resources/1.6/valid-dependency-1.6.json b/src/test/resources/1.6/valid-dependency-1.6.json index 289cf8b5b..6e0cf61a4 100644 --- a/src/test/resources/1.6/valid-dependency-1.6.json +++ b/src/test/resources/1.6/valid-dependency-1.6.json @@ -22,12 +22,6 @@ "type": "library", "name": "library-c", "version": "1.0.0" - }, - { - "bom-ref": "library-d", - "type": "library", - "name": "library-d", - "version": "1.0.0" } ], "dependencies": [ @@ -40,12 +34,6 @@ "dependsOn": [ "library-c" ] - }, - { - "ref": "library-c", - "provides": [ - "library-d" - ] } ] -} +} \ No newline at end of file diff --git a/src/test/resources/1.6/valid-dependency-1.6.textproto b/src/test/resources/1.6/valid-dependency-1.6.textproto index a636b12af..a06cfd0f2 100644 --- a/src/test/resources/1.6/valid-dependency-1.6.textproto +++ b/src/test/resources/1.6/valid-dependency-1.6.textproto @@ -22,12 +22,6 @@ components { name: "library-c" version: "1.0.0" } -components { - type: CLASSIFICATION_LIBRARY - bom_ref: "library-d" - name: "library-d" - version: "1.0.0" -} dependencies { ref: "library-a" } @@ -36,9 +30,4 @@ dependencies { dependencies { ref: "library-c" } -} -dependencies { - ref: "library-c" - provides: ["library-d"] - } } \ No newline at end of file diff --git a/src/test/resources/1.6/valid-dependency-1.6.xml b/src/test/resources/1.6/valid-dependency-1.6.xml index 9cdd44fa9..d49d1df0f 100644 --- a/src/test/resources/1.6/valid-dependency-1.6.xml +++ b/src/test/resources/1.6/valid-dependency-1.6.xml @@ -13,18 +13,11 @@ library-c 1.0.0 - - library-d - 1.0.0 - - - - - + \ No newline at end of file diff --git a/src/test/resources/1.6/valid-dependency-provides-1.6.json b/src/test/resources/1.6/valid-dependency-provides-1.6.json new file mode 100644 index 000000000..289cf8b5b --- /dev/null +++ b/src/test/resources/1.6/valid-dependency-provides-1.6.json @@ -0,0 +1,51 @@ +{ + "$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.6", + "serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79", + "version": 1, + "components": [ + { + "bom-ref": "library-a", + "type": "library", + "name": "library-a", + "version": "1.0.0" + }, + { + "bom-ref": "library-b", + "type": "library", + "name": "library-b", + "version": "1.0.0" + }, + { + "bom-ref": "library-c", + "type": "library", + "name": "library-c", + "version": "1.0.0" + }, + { + "bom-ref": "library-d", + "type": "library", + "name": "library-d", + "version": "1.0.0" + } + ], + "dependencies": [ + { + "ref": "library-a", + "dependsOn": [] + }, + { + "ref": "library-b", + "dependsOn": [ + "library-c" + ] + }, + { + "ref": "library-c", + "provides": [ + "library-d" + ] + } + ] +} diff --git a/src/test/resources/1.6/valid-dependency-provides-1.6.textproto b/src/test/resources/1.6/valid-dependency-provides-1.6.textproto new file mode 100644 index 000000000..a636b12af --- /dev/null +++ b/src/test/resources/1.6/valid-dependency-provides-1.6.textproto @@ -0,0 +1,44 @@ +# proto-file: schema/bom-1.6.proto +# proto-message: Bom + +spec_version: "1.6" +version: 1 +serial_number: "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79" +components { + type: CLASSIFICATION_LIBRARY + bom_ref: "library-a" + name: "library-a" + version: "1.0.0" +} +components { + type: CLASSIFICATION_LIBRARY + bom_ref: "library-b" + name: "library-b" + version: "1.0.0" +} +components { + type: CLASSIFICATION_LIBRARY + bom_ref: "library-c" + name: "library-c" + version: "1.0.0" +} +components { + type: CLASSIFICATION_LIBRARY + bom_ref: "library-d" + name: "library-d" + version: "1.0.0" +} +dependencies { + ref: "library-a" +} +dependencies { + ref: "library-b" + dependencies { + ref: "library-c" + } +} +dependencies { + ref: "library-c" + provides: ["library-d"] + } +} \ No newline at end of file diff --git a/src/test/resources/1.6/valid-dependency-provides-1.6.xml b/src/test/resources/1.6/valid-dependency-provides-1.6.xml new file mode 100644 index 000000000..9cdd44fa9 --- /dev/null +++ b/src/test/resources/1.6/valid-dependency-provides-1.6.xml @@ -0,0 +1,30 @@ + + + + + library-a + 1.0.0 + + + library-b + 1.0.0 + + + library-c + 1.0.0 + + + library-d + 1.0.0 + + + + + + + + + + + +