diff --git a/src/main/java/org/cyclonedx/model/metadata/ToolInformation.java b/src/main/java/org/cyclonedx/model/metadata/ToolInformation.java index 74d12618a..b0d59fb8c 100644 --- a/src/main/java/org/cyclonedx/model/metadata/ToolInformation.java +++ b/src/main/java/org/cyclonedx/model/metadata/ToolInformation.java @@ -1,5 +1,6 @@ package org.cyclonedx.model.metadata; +import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -21,6 +22,9 @@ public class ToolInformation private List services; public List getComponents() { + if(components==null) { + components = new ArrayList<>(); + } return components; } @@ -29,6 +33,9 @@ public void setComponents(final List components) { } public List getServices() { + if(services==null) { + services = new ArrayList<>(); + } return services; } diff --git a/src/main/java/org/cyclonedx/model/vulnerability/Vulnerability.java b/src/main/java/org/cyclonedx/model/vulnerability/Vulnerability.java index 8bb66328a..dfd9654b2 100644 --- a/src/main/java/org/cyclonedx/model/vulnerability/Vulnerability.java +++ b/src/main/java/org/cyclonedx/model/vulnerability/Vulnerability.java @@ -150,7 +150,7 @@ public void setReferences(final List references) { this.references = references; } - private void addReference(Reference reference) { + public void addReference(Reference reference) { if (references == null) { references = new ArrayList<>(); } @@ -225,6 +225,13 @@ public void setAdvisories(final List advisories) { this.advisories = advisories; } + public void addAdvisory(Advisory advisory) { + if (advisories == null) { + advisories = new ArrayList<>(); + } + advisories.add(advisory); + } + public Date getCreated() { return created; } @@ -824,6 +831,13 @@ public void setResponses(final List responses) { this.responses = responses; } + public void addResponse(Response response) { + if (this.responses == null) { + this.responses = new ArrayList<>(); + } + this.responses.add(response); + } + public String getDetail() { return detail; } @@ -896,6 +910,13 @@ public void setVersions(final List versions) { this.versions = versions; } + public void addVersion(Version version) { + if (versions == null) { + versions = new ArrayList<>(); + } + versions.add(version); + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -992,6 +1013,13 @@ public void setIndividuals(final List individuals) { this.individuals = individuals; } + public void addIndividual(OrganizationalContact individual) { + if (individuals == null) { + individuals = new ArrayList<>(); + } + individuals.add(individual); + } + public List getOrganizations() { return organizations; } @@ -1000,6 +1028,13 @@ public void setOrganizations(final List organizations) { this.organizations = organizations; } + public void addOrganization(OrganizationalEntity organization) { + if (this.organizations == null) { + this.organizations = new ArrayList<>(); + } + this.organizations.add(organization); + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/src/main/java/org/cyclonedx/util/deserializer/AffectDeserializer.java b/src/main/java/org/cyclonedx/util/deserializer/AffectDeserializer.java index f18efc849..6f6175d96 100644 --- a/src/main/java/org/cyclonedx/util/deserializer/AffectDeserializer.java +++ b/src/main/java/org/cyclonedx/util/deserializer/AffectDeserializer.java @@ -58,10 +58,9 @@ public Vulnerability.Affect deserialize(JsonParser parser, DeserializationContex if (versionNode.isArray()) { List versions = mapper.convertValue(versionNode, new TypeReference>() {}); affect.setVersions(versions); - } else { - affect.setVersions(Collections.singletonList( - mapper.convertValue(versionNode, Vulnerability.Version.class) - )); + } + else { + affect.addVersion(mapper.convertValue(versionNode, Vulnerability.Version.class)); } } } diff --git a/src/main/java/org/cyclonedx/util/deserializer/ToolInformationDeserializer.java b/src/main/java/org/cyclonedx/util/deserializer/ToolInformationDeserializer.java index 8f7c642ed..3a9ce3623 100644 --- a/src/main/java/org/cyclonedx/util/deserializer/ToolInformationDeserializer.java +++ b/src/main/java/org/cyclonedx/util/deserializer/ToolInformationDeserializer.java @@ -29,7 +29,6 @@ import org.cyclonedx.model.metadata.ToolInformation; import java.io.IOException; -import java.util.Collections; import java.util.List; public class ToolInformationDeserializer @@ -71,7 +70,7 @@ else if (componentsNode.isObject() && componentsNode.has("component")) { toolInformation.setComponents(components); } else if (componentNode.isObject()) { Component component = mapper.convertValue(componentNode, Component.class); - toolInformation.setComponents(Collections.singletonList(component)); + toolInformation.getComponents().add(component); } } } @@ -84,7 +83,7 @@ private void parseServices(JsonNode servicesNode, ToolInformation toolInformatio List services = mapper.convertValue(servicesNode, new TypeReference>() {}); toolInformation.setServices(services); } - // Case XML-like input where "services" contains "component" + // Case XML-like input where "services" contains "service" else if (servicesNode.isObject() && servicesNode.has("service")) { JsonNode serviceNode = servicesNode.get("service"); if (serviceNode.isArray()) { @@ -92,7 +91,7 @@ else if (servicesNode.isObject() && servicesNode.has("service")) { toolInformation.setServices(services); } else if (serviceNode.isObject()) { Service service = mapper.convertValue(servicesNode, Service.class); - toolInformation.setServices(Collections.singletonList(service)); + toolInformation.getServices().add(service); } } } diff --git a/src/main/java/org/cyclonedx/util/deserializer/VulnerabilityDeserializer.java b/src/main/java/org/cyclonedx/util/deserializer/VulnerabilityDeserializer.java index b0013f5f6..6604b80a1 100644 --- a/src/main/java/org/cyclonedx/util/deserializer/VulnerabilityDeserializer.java +++ b/src/main/java/org/cyclonedx/util/deserializer/VulnerabilityDeserializer.java @@ -20,7 +20,6 @@ import java.io.IOException; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import com.fasterxml.jackson.core.JsonParser; @@ -108,8 +107,7 @@ private Vulnerability parseVulnerability(JsonNode node, JsonParser jsonParser, D new TypeReference>() {}); vulnerability.setReferences(references); } else { - vulnerability.setReferences(Collections.singletonList( - mapper.convertValue(referenceNode, Vulnerability.Reference.class))); + vulnerability.addReference(mapper.convertValue(referenceNode, Vulnerability.Reference.class)); } } } @@ -126,9 +124,9 @@ private Vulnerability parseVulnerability(JsonNode node, JsonParser jsonParser, D List ratings = mapper.convertValue(ratingNode, new TypeReference>() { }); vulnerability.setRatings(ratings); - } else { - vulnerability.setRatings(Collections.singletonList( - mapper.convertValue(ratingNode, Vulnerability.Rating.class))); + } + else { + vulnerability.addRating(mapper.convertValue(ratingNode, Vulnerability.Rating.class)); } } } @@ -145,7 +143,7 @@ private Vulnerability parseVulnerability(JsonNode node, JsonParser jsonParser, D }); vulnerability.setCwes(codes); } else { - vulnerability.setCwes(Collections.singletonList(cweNode.asInt())); + vulnerability.addCwe(cweNode.asInt()); } } } @@ -163,8 +161,7 @@ private Vulnerability parseVulnerability(JsonNode node, JsonParser jsonParser, D new TypeReference>() {}); vulnerability.setAdvisories(advisories); } else { - vulnerability.setAdvisories(Collections.singletonList( - mapper.convertValue(advisoryNode, Vulnerability.Advisory.class))); + vulnerability.addAdvisory(mapper.convertValue(advisoryNode, Vulnerability.Advisory.class)); } } } @@ -251,8 +248,9 @@ private void parseAnalysis(JsonNode analysisNode, Vulnerability vulnerability, O analysis.setResponses(responses); } else if (responseNode.isTextual()) { - Vulnerability.Analysis.Response response = Vulnerability.Analysis.Response.fromString(responseNode.asText()); - analysis.setResponses(Collections.singletonList(response)); + Vulnerability.Analysis.Response response = + Vulnerability.Analysis.Response.fromString(responseNode.asText()); + analysis.addResponse(response); } } } @@ -279,7 +277,7 @@ private void parseOrganizations(JsonNode organizationsNode, Vulnerability.Credit credits.setOrganizations(organizations); } else if (organizationsNode.isObject()) { OrganizationalEntity organization = mapper.convertValue(organizationsNode.get("organization"), OrganizationalEntity.class); - credits.setOrganizations(Collections.singletonList(organization)); + credits.addOrganization(organization); } } } @@ -291,7 +289,7 @@ private void parseIndividuals(JsonNode individualsNode, Vulnerability.Credits cr credits.setIndividuals(individuals); } else if (individualsNode.isObject()) { OrganizationalContact individual = mapper.convertValue(individualsNode.get("individual"), OrganizationalContact.class); - credits.setIndividuals(Collections.singletonList(individual)); + credits.addIndividual(individual); } } } diff --git a/src/test/java/org/cyclonedx/BomJsonGeneratorTest.java b/src/test/java/org/cyclonedx/BomJsonGeneratorTest.java index 35357f095..6a162c9f6 100644 --- a/src/test/java/org/cyclonedx/BomJsonGeneratorTest.java +++ b/src/test/java/org/cyclonedx/BomJsonGeneratorTest.java @@ -19,18 +19,24 @@ package org.cyclonedx; 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.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; @@ -46,6 +52,10 @@ 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; @@ -550,6 +560,23 @@ public void testIssue562() throws Exception { Version version = Version.VERSION_16; Bom bom = createCommonJsonBom("/regression/issue562.json"); + BomJsonGenerator generator = BomGeneratorFactory.createJson(version, bom); + File loadedFile = writeToFile(generator.toJsonString()); + + JsonParser parser = new JsonParser(); + assertTrue(parser.isValid(loadedFile, version)); + } + + @Test + public void testIssue571() throws Exception { + Version version = Version.VERSION_16; + Bom bom = createCommonJsonBom("/regression/issue571.json"); + + Component component = new Component(); + component.setName("test"); + component.setVersion("v2"); + component.setType(Type.APPLICATION); + bom.getMetadata().getToolChoice().getComponents().add(component); BomJsonGenerator generator = BomGeneratorFactory.createJson(version, bom); File loadedFile = writeToFile(generator.toJsonString()); diff --git a/src/test/java/org/cyclonedx/BomXmlGeneratorTest.java b/src/test/java/org/cyclonedx/BomXmlGeneratorTest.java index dd1ecacbf..bf041845b 100644 --- a/src/test/java/org/cyclonedx/BomXmlGeneratorTest.java +++ b/src/test/java/org/cyclonedx/BomXmlGeneratorTest.java @@ -27,6 +27,7 @@ import org.cyclonedx.model.Attribute; import org.cyclonedx.model.Bom; import org.cyclonedx.model.Component; +import org.cyclonedx.model.Component.Type; import org.cyclonedx.model.ExtensibleType; import org.cyclonedx.model.ExternalReference; import org.cyclonedx.model.License; @@ -730,6 +731,24 @@ public void testIssue562() throws Exception { assertTrue(parser.isValid(loadedFile, version)); } + @Test + public void testIssue571() throws Exception { + Version version = Version.VERSION_16; + Bom bom = createCommonBomXml("/regression/issue571.xml"); + + Component component = new Component(); + component.setName("test"); + component.setVersion("v2"); + component.setType(Type.APPLICATION); + bom.getMetadata().getToolChoice().getComponents().add(component); + + BomJsonGenerator generator = BomGeneratorFactory.createJson(version, bom); + File loadedFile = writeToFile(generator.toJsonString()); + + JsonParser parser = new JsonParser(); + assertTrue(parser.isValid(loadedFile, version)); + } + @Test public void testIssue492() throws Exception { Version version = Version.VERSION_15; diff --git a/src/test/resources/regression/issue571.json b/src/test/resources/regression/issue571.json new file mode 100644 index 000000000..41e037498 --- /dev/null +++ b/src/test/resources/regression/issue571.json @@ -0,0 +1,21 @@ +{ + "bomFormat":"CycloneDX", + "specVersion":"1.6", + "serialNumber":"urn:uuid:0c81ff2e-d64e-4897-bfa4-2f0f7d8ab767", + "version" : 1, + "metadata" : { + "timestamp":"2024-12-09T21:56:45Z", + "tools" : { + "components" : [ { + "type":"application", + "name":"TOOL 1", + "version":"v1" + } ] + }, + "authors" : [ { + "name":"Author 1" + }, { + "name":"Author 2" + } ] + } +} \ No newline at end of file diff --git a/src/test/resources/regression/issue571.xml b/src/test/resources/regression/issue571.xml new file mode 100644 index 000000000..cedea26a7 --- /dev/null +++ b/src/test/resources/regression/issue571.xml @@ -0,0 +1,22 @@ + + + + 2024-12-09T21:56:45Z + + + + TOOL 1 + v1 + + + + + + Author 1 + + + Author 2 + + + +