diff --git a/src/main/java/org/cyclonedx/util/deserializer/ComponentWrapperDeserializer.java b/src/main/java/org/cyclonedx/util/deserializer/ComponentWrapperDeserializer.java index ecf6b5910..39b757a19 100644 --- a/src/main/java/org/cyclonedx/util/deserializer/ComponentWrapperDeserializer.java +++ b/src/main/java/org/cyclonedx/util/deserializer/ComponentWrapperDeserializer.java @@ -20,10 +20,15 @@ import java.io.IOException; import java.util.Arrays; +import java.util.Collections; +import java.util.List; import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.dataformat.xml.deser.FromXmlParser; import org.cyclonedx.model.Ancestors; import org.cyclonedx.model.Component; @@ -72,10 +77,25 @@ public ComponentWrapper deserialize( return null; } - Component[] components = parser.readValueAs(Component[].class); - - wrapper.setComponents(Arrays.asList(components)); - + List components = Collections.emptyList(); + JsonToken currentToken = parser.currentToken(); + if (currentToken == JsonToken.START_ARRAY) { + components = Arrays.asList(parser.readValueAs(Component[].class)); + } else if (currentToken == JsonToken.START_OBJECT) { + // This is possible for XML input when tree has been read, then parsed with token buffer parser + ObjectNode node = parser.readValueAs(ObjectNode.class); + if (node.has("component")) { + JsonNode component = node.get("component"); + JsonParser componentsParser = component.traverse(parser.getCodec()); + if (component.isArray()) { + components = Arrays.asList(componentsParser.readValueAs(Component[].class)); + } else { + components = Collections.singletonList(componentsParser.readValueAs(Component.class)); + } + } + } + wrapper.setComponents(components); return wrapper; + } } diff --git a/src/main/java/org/cyclonedx/util/deserializer/MetadataDeserializer.java b/src/main/java/org/cyclonedx/util/deserializer/MetadataDeserializer.java index fa74481bd..e7b3b42c8 100644 --- a/src/main/java/org/cyclonedx/util/deserializer/MetadataDeserializer.java +++ b/src/main/java/org/cyclonedx/util/deserializer/MetadataDeserializer.java @@ -3,10 +3,10 @@ import java.io.IOException; import java.text.ParseException; import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.List; import java.util.ArrayList; import java.util.Collections; +import java.util.Date; +import java.util.List; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.type.TypeReference; @@ -28,8 +28,6 @@ public class MetadataDeserializer extends JsonDeserializer { - private final ObjectMapper mapper = new ObjectMapper(); - private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX"); private final LifecycleDeserializer lifecycleDeserializer = new LifecycleDeserializer(); @@ -42,6 +40,13 @@ public Metadata deserialize(JsonParser jsonParser, DeserializationContext ctxt) Metadata metadata = new Metadata(); + ObjectMapper mapper; + if (jsonParser.getCodec() instanceof ObjectMapper) { + mapper = (ObjectMapper) jsonParser.getCodec(); + } else { + mapper = new ObjectMapper(); + } + // Parsing other fields in the Metadata object if (node.has("authors")) { JsonNode authorsNode = node.get("authors"); @@ -127,10 +132,10 @@ else if (toolsNode.has("tool")) { else { ToolInformation toolInformation = new ToolInformation(); if (toolsNode.has("components")) { - parseComponents(toolsNode.get("components"), toolInformation); + parseComponents(toolsNode.get("components"), toolInformation, mapper); } if (toolsNode.has("services")) { - parseServices(toolsNode.get("services"), toolInformation); + parseServices(toolsNode.get("services"), toolInformation, mapper); } metadata.setToolChoice(toolInformation); } @@ -139,7 +144,7 @@ else if (toolsNode.has("tool")) { return metadata; } - private void parseComponents(JsonNode componentsNode, ToolInformation toolInformation) { + private void parseComponents(JsonNode componentsNode, ToolInformation toolInformation, ObjectMapper mapper) { if (componentsNode != null) { if (componentsNode.isArray()) { List components = mapper.convertValue(componentsNode, new TypeReference>() {}); @@ -151,7 +156,7 @@ private void parseComponents(JsonNode componentsNode, ToolInformation toolInform } } - private void parseServices(JsonNode servicesNode, ToolInformation toolInformation) { + private void parseServices(JsonNode servicesNode, ToolInformation toolInformation, ObjectMapper mapper) { if (servicesNode != null) { if (servicesNode.isArray()) { List services = mapper.convertValue(servicesNode, new TypeReference>() {}); diff --git a/src/test/java/org/cyclonedx/parsers/XmlParserTest.java b/src/test/java/org/cyclonedx/parsers/XmlParserTest.java index 711b36729..e4d576b56 100644 --- a/src/test/java/org/cyclonedx/parsers/XmlParserTest.java +++ b/src/test/java/org/cyclonedx/parsers/XmlParserTest.java @@ -18,6 +18,8 @@ */ package org.cyclonedx.parsers; +import org.cyclonedx.CycloneDxSchema; +import org.cyclonedx.CycloneDxSchema.Version; import org.cyclonedx.Version; import org.cyclonedx.model.Bom; import org.cyclonedx.model.Component; @@ -74,6 +76,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; public class XmlParserTest extends AbstractParserTest @@ -165,6 +168,20 @@ private void testPedigreeFromExample(final Pedigree pedigree) { assertEquals(2, pedigree.getPatches().get(1).getResolves().size()); } + @Test + public void testValid12BomWithMetadataPedigree() throws Exception { + final File file = new File(Objects.requireNonNull(this.getClass().getResource("/bom-1.2-metadata-pedigree.xml")).getFile()); + final XmlParser parser = new XmlParser(); + final boolean valid = parser.isValid(file, CycloneDxSchema.Version.VERSION_12); + assertTrue(valid); + + final Bom bom = parser.parse(file); + Pedigree pedigree = bom.getMetadata().getComponent().getPedigree(); + assertEquals(2, pedigree.getAncestors().getComponents().size()); + assertEquals(1, pedigree.getDescendants().getComponents().size()); + assertEquals(0, pedigree.getVariants().getComponents().size()); + } + @Test public void testParsedObjects10Bom() throws Exception { final Bom bom = getXmlBom("bom-1.0.xml"); @@ -388,7 +405,7 @@ public void testIssue336Regression() throws Exception { assertEquals("foo", bom.getMetadata().getComponent().getProperties().get(0).getName()); assertEquals("bar", bom.getMetadata().getComponent().getProperties().get(0).getValue()); } - + @Test public void testIssue338RegressionWithSingleTool() throws Exception { final Bom bom = getXmlBom("regression/issue338-single-tool.xml"); diff --git a/src/test/resources/bom-1.2-metadata-pedigree.xml b/src/test/resources/bom-1.2-metadata-pedigree.xml new file mode 100644 index 000000000..e4776a2dd --- /dev/null +++ b/src/test/resources/bom-1.2-metadata-pedigree.xml @@ -0,0 +1,35 @@ + + + + + com.acme + sample-library + 1.0.0 + + + + org.example + sample-library-ancestor-1 + 1.0.0 + + + org.example + sample-library-ancestor-2 + 1.0.0 + + + + + org.example + sample-library-descendant + 1.0.1 + + + + + + + +