diff --git a/src/main/java/org/cyclonedx/model/Dependency.java b/src/main/java/org/cyclonedx/model/Dependency.java index 125d18b4e..eb3e4343a 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,12 @@ public class Dependency extends BomReference { @JacksonXmlProperty(localName = "dependency") private List dependencies; + @VersionFilter(Version.VERSION_16) + @JsonProperty("provides") + @JacksonXmlElementWrapper(useWrapping = false) + @JacksonXmlProperty(localName = "provides") + private List provides; + public Dependency(final String ref) { super(ref); } @@ -60,6 +67,23 @@ public void addDependency(final Dependency dependency) { } } + public List getProvides() { + return provides; + } + + public void setProvides(final List provides) { + this.provides = provides; + } + + public void addProvides(final BomReference 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..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; @@ -97,6 +98,13 @@ private void writeJSONDependenciesWithGenerator(final JsonGenerator generator, f } } generator.writeEndArray(); + if (CollectionUtils.isNotEmpty(dependency.getProvides())) { + generator.writeArrayFieldStart("provides"); + for (BomReference subDependency : dependency.getProvides()) { + generator.writeString(subDependency.getRef()); + } + generator.writeEndArray(); + } generator.writeEndObject(); } } @@ -134,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 @@ -142,12 +155,31 @@ private void writeXMLDependency(final Dependency dependency, final ToXmlGenerato } if (CollectionUtils.isNotEmpty(dependency.getDependencies())) { - generator.writeEndArray(); - } + generator.writeEndArray(); + } 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) throws XMLStreamException, IOException { 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 1e87f38ef..6e0cf61a4 100644 --- a/src/test/resources/1.6/valid-dependency-1.6.json +++ b/src/test/resources/1.6/valid-dependency-1.6.json @@ -36,4 +36,4 @@ ] } ] -} +} \ 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 363dfba93..a06cfd0f2 100644 --- a/src/test/resources/1.6/valid-dependency-1.6.textproto +++ b/src/test/resources/1.6/valid-dependency-1.6.textproto @@ -30,4 +30,4 @@ dependencies { dependencies { ref: "library-c" } -} +} \ 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..d49d1df0f 100644 --- a/src/test/resources/1.6/valid-dependency-1.6.xml +++ b/src/test/resources/1.6/valid-dependency-1.6.xml @@ -20,4 +20,4 @@ - + \ 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 + + + + + + + + + + + +