diff --git a/src/main/java/org/cyclonedx/model/metadata/ToolInformation.java b/src/main/java/org/cyclonedx/model/metadata/ToolInformation.java index e3448fd7c..4f91c28be 100644 --- a/src/main/java/org/cyclonedx/model/metadata/ToolInformation.java +++ b/src/main/java/org/cyclonedx/model/metadata/ToolInformation.java @@ -1,6 +1,7 @@ package org.cyclonedx.model.metadata; import java.util.List; +import java.util.Objects; import com.fasterxml.jackson.annotation.JsonInclude; import org.cyclonedx.model.Component; @@ -28,4 +29,18 @@ public List getServices() { public void setServices(final List services) { this.services = services; } + + @Override + public int hashCode() { + return Objects.hash(components, services); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ToolInformation other = (ToolInformation) o; + return Objects.equals(components, other.components) && + Objects.equals(services, other.services); + } } diff --git a/src/main/java/org/cyclonedx/util/deserializer/MetadataDeserializer.java b/src/main/java/org/cyclonedx/util/deserializer/MetadataDeserializer.java index 0314ac251..f614c28ab 100644 --- a/src/main/java/org/cyclonedx/util/deserializer/MetadataDeserializer.java +++ b/src/main/java/org/cyclonedx/util/deserializer/MetadataDeserializer.java @@ -140,6 +140,13 @@ private void parseComponents(JsonNode componentsNode, ToolInformation toolInform List components = mapper.convertValue(componentsNode, new TypeReference>() {}); toolInformation.setComponents(components); } else if (componentsNode.isObject()) { + if (componentsNode.has("component")) { + JsonNode componentNode = componentsNode.get("component"); + if (componentNode.isArray()) { + parseComponents(componentNode, toolInformation); + return; + } + } Component component = mapper.convertValue(componentsNode, Component.class); toolInformation.setComponents(Collections.singletonList(component)); } @@ -152,6 +159,13 @@ private void parseServices(JsonNode servicesNode, ToolInformation toolInformatio List services = mapper.convertValue(servicesNode, new TypeReference>() {}); toolInformation.setServices(services); } else if (servicesNode.isObject()) { + if (servicesNode.has("service")) { + JsonNode serviceNode = servicesNode.get("service"); + if (serviceNode.isArray()) { + parseServices(serviceNode, toolInformation); + return; + } + } Service service = mapper.convertValue(servicesNode, Service.class); toolInformation.setServices(Collections.singletonList(service)); } diff --git a/src/main/java/org/cyclonedx/util/serializer/MetadataSerializer.java b/src/main/java/org/cyclonedx/util/serializer/MetadataSerializer.java index c2864f9f6..f368b4463 100644 --- a/src/main/java/org/cyclonedx/util/serializer/MetadataSerializer.java +++ b/src/main/java/org/cyclonedx/util/serializer/MetadataSerializer.java @@ -150,14 +150,13 @@ private void writeArrayFieldJSON(JsonGenerator jsonGenerator, String fieldNa private void writeArrayFieldXML(List items, ToXmlGenerator xmlGenerator, String fieldName) throws IOException { if (items != null) { - xmlGenerator.writeFieldName(fieldName + "s"); - xmlGenerator.writeStartArray(); + xmlGenerator.writeObjectFieldStart(fieldName + "s"); + xmlGenerator.writeArrayFieldStart(fieldName); for (T item : items) { - xmlGenerator.writeStartObject(); - xmlGenerator.writeObjectField(fieldName, item); - xmlGenerator.writeEndObject(); + xmlGenerator.writeObject(item); } xmlGenerator.writeEndArray(); + xmlGenerator.writeEndObject(); } } diff --git a/src/test/java/org/cyclonedx/BomXmlGeneratorTest.java b/src/test/java/org/cyclonedx/BomXmlGeneratorTest.java index 07b048621..e5d0e7e62 100644 --- a/src/test/java/org/cyclonedx/BomXmlGeneratorTest.java +++ b/src/test/java/org/cyclonedx/BomXmlGeneratorTest.java @@ -27,6 +27,8 @@ import org.cyclonedx.model.ExternalReference; import org.cyclonedx.model.License; import org.cyclonedx.model.LicenseChoice; +import org.cyclonedx.model.Metadata; +import org.cyclonedx.model.metadata.ToolInformation; import org.cyclonedx.parsers.JsonParser; import org.cyclonedx.parsers.XmlParser; import org.junit.jupiter.api.AfterEach; @@ -41,6 +43,9 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; + import javax.xml.XMLConstants; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; @@ -375,4 +380,29 @@ private String documentToString(Document doc) { return null; } } + + @Test + public void toolInformationSerialization() throws Exception { + final Component toolA = new Component(); + toolA.setName("Tool-A"); + toolA.setType(Component.Type.APPLICATION); + toolA.setVersion("1.0.0"); + final Component toolB = new Component(); + toolB.setName("Tool-B"); + toolB.setVersion("2.0.0"); + + final Bom bom = new Bom(); + final Metadata metadata = new Metadata(); + final ToolInformation toolInformation = new ToolInformation(); + toolInformation.setComponents(Arrays.asList(toolA, toolB)); + metadata.setToolChoice(toolInformation); + bom.setMetadata(metadata); + bom.addComponent(toolA); + + final BomXmlGenerator generator = BomGeneratorFactory.createXml(CycloneDxSchema.Version.VERSION_15, bom); + testDocument(generator.generate()); + + final Bom actual = new XmlParser().parse(writeToFile(generator.toXmlString())); + assertEquals(toolInformation, actual.getMetadata().getToolChoice()); + } } diff --git a/src/test/resources/1.5/valid-metadata-tools-1.5.json b/src/test/resources/1.5/valid-metadata-tools-1.5.json new file mode 100644 index 000000000..d51fe1c27 --- /dev/null +++ b/src/test/resources/1.5/valid-metadata-tools-1.5.json @@ -0,0 +1,67 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79", + "version": 1, + "metadata": { + "tools": { + "components": [ + { + "type": "application", + "group": "Awesome Vendor", + "name": "Awesome Tool", + "version": "9.1.2", + "hashes": [ + { + "alg": "SHA-1", + "content": "25ed8e31b995bb927966616df2a42b979a2717f0" + }, + { + "alg": "SHA-256", + "content": "a74f733635a19aefb1f73e5947cef59cd7440c6952ef0f03d09d974274cbd6df" + } + ] + }, + { + "type": "application", + "group": "Awesome Vendor2", + "name": "Awesome Tool2", + "version": "7.4.0" + } + ], + "services": [ + { + "provider": { + "name": "Acme Org", + "url": [ + "https://example.com" + ] + }, + "group": "com.example", + "name": "Acme Signing Server", + "description": "Signs artifacts", + "endpoints": [ + "https://example.com/sign", + "https://example.com/verify", + "https://example.com/tsa" + ] + }, + { + "provider": { + "name": "Example Org", + "url": [ + "https://example.com" + ] + }, + "group": "com.example", + "name": "Some service", + "description": "Provides services", + "endpoints": [ + "https://example.com/whatever" + ] + } + ] + } + }, + "components": [] +} \ No newline at end of file diff --git a/src/test/resources/1.5/valid-metadata-tools-1.5.xml b/src/test/resources/1.5/valid-metadata-tools-1.5.xml new file mode 100644 index 000000000..ff3e5682e --- /dev/null +++ b/src/test/resources/1.5/valid-metadata-tools-1.5.xml @@ -0,0 +1,52 @@ + + + + + + + Awesome Vendor + Awesome Tool + 9.1.2 + + 25ed8e31b995bb927966616df2a42b979a2717f0 + a74f733635a19aefb1f73e5947cef59cd7440c6952ef0f03d09d974274cbd6df + + + + Awesome Vendor2 + Awesome Tool2 + 7.4.0 + + + + + + Acme Org + https://example.com + + com.example + Acme Signing Server + Signs artifacts + + https://example.com/sign + https://example.com/verify + https://example.com/tsa + + + + + Example Org + https://example.com + + com.example + Some service + Provides services + + https://example.com/whatever + + + + + + + \ No newline at end of file