Skip to content

Commit

Permalink
Update External Reference (#380)
Browse files Browse the repository at this point in the history
Signed-off-by: Alex Alzate <aalzate@sonatype.com>
  • Loading branch information
mr-zepol committed Apr 22, 2024
1 parent fa6b721 commit 3107a5e
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 136 deletions.
7 changes: 7 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,13 @@
<artifactId>commons-lang3</artifactId>
<version>3.14.0</version>
</dependency>

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>


<!-- Package URL -->

Expand Down
6 changes: 5 additions & 1 deletion src/main/java/org/cyclonedx/model/ExternalReference.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonSerialize(using = ExternalReferenceSerializer.class)
@JsonPropertyOrder({"url", "comment", "hashes"})
@JsonPropertyOrder({"bom", "url", "comment", "hashes"})
public class ExternalReference {

public enum Type {
Expand All @@ -59,6 +59,8 @@ public enum Type {
DOCUMENTATION("documentation"),
@JsonProperty("support")
SUPPORT("support"),
@JsonProperty("source-distribution")
SOURCE_DISTRIBUTION("source-distribution"),
@JsonProperty("distribution")
DISTRIBUTION("distribution"),
@JsonProperty("distribution-intake")
Expand Down Expand Up @@ -113,6 +115,8 @@ public enum Type {
EVIDENCE("evidence"),
@JsonProperty("formulation")
FORMULATION("formulation"),
@JsonProperty("rfc-9116")
RFC_9116("rfc-9116"),
@JsonProperty("other")
OTHER("other");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,19 @@

import java.io.IOException;
import java.util.function.BiPredicate;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator;
import org.apache.commons.collections4.CollectionUtils;
import org.cyclonedx.model.ExternalReference;
import org.cyclonedx.model.ExternalReference.Type;
import org.cyclonedx.model.Hash;
import org.cyclonedx.util.BomUtils;

public class ExternalReferenceSerializer extends StdSerializer<ExternalReference>
public class ExternalReferenceSerializer
extends StdSerializer<ExternalReference>
{
public ExternalReferenceSerializer() {
this(null);
Expand All @@ -46,52 +46,72 @@ public ExternalReferenceSerializer(final Class<ExternalReference> t) {
public void serialize(
final ExternalReference extRef, final JsonGenerator gen, final SerializerProvider provider) throws IOException
{
final BiPredicate<Type, String> validateExternalReference = (type, url) -> (type != null && url != null && BomUtils.validateUriString(url));
final BiPredicate<Type, String> validateExternalReference =
(type, url) -> (type != null && url != null && BomUtils.validateUriString(url));

if (!validateExternalReference.test(extRef.getType(), extRef.getUrl())) {
return;
}

if (gen instanceof ToXmlGenerator) {
final ToXmlGenerator toXmlGenerator = (ToXmlGenerator) gen;
final XMLStreamWriter staxWriter = toXmlGenerator.getStaxWriter();
serializeXml((ToXmlGenerator) gen, extRef);
}
else {
serializeJson(gen, extRef);
}
}

if (validateExternalReference.test(extRef.getType(), extRef.getUrl())) {
try {
staxWriter.writeStartElement("reference");
staxWriter.writeAttribute("type", extRef.getType().getTypeName());
staxWriter.writeStartElement("url");
staxWriter.writeCharacters(extRef.getUrl());
staxWriter.writeEndElement();
if (extRef.getComment() != null) {
staxWriter.writeStartElement("comment");
staxWriter.writeCharacters(extRef.getComment());
staxWriter.writeEndElement();
}
if (extRef.getHashes() != null && !extRef.getHashes().isEmpty()) {
staxWriter.writeStartElement("hashes");
for (Hash hash : extRef.getHashes()) {
if (hash != null) {
staxWriter.writeStartElement("hash");
staxWriter.writeAttribute("alg", hash.getAlgorithm());
staxWriter.writeCharacters(hash.getValue());
staxWriter.writeEndElement();
}
}
staxWriter.writeEndElement();
}
staxWriter.writeEndElement();
}
catch (XMLStreamException ex) {
throw new IOException(ex);
}
}
} else if (validateExternalReference.test(extRef.getType(), extRef.getUrl())) {
gen.writeStartObject();
gen.writeStringField("type", extRef.getType().getTypeName());
gen.writeStringField("url", extRef.getUrl());
if (extRef.getComment() != null) {
gen.writeStringField("comment", extRef.getComment());
private void serializeXml(final ToXmlGenerator toXmlGenerator, final ExternalReference extRef) throws IOException {
toXmlGenerator.writeStartObject();

toXmlGenerator.setNextIsAttribute(true);
toXmlGenerator.writeFieldName("type");
toXmlGenerator.writeString(extRef.getType().getTypeName());
toXmlGenerator.setNextIsAttribute(false);

toXmlGenerator.writeStringField("url", extRef.getUrl());
if (extRef.getComment() != null) {
toXmlGenerator.writeStringField("comment", extRef.getComment());
}
if (CollectionUtils.isNotEmpty(extRef.getHashes())) {
toXmlGenerator.writeFieldName("hashes");
toXmlGenerator.writeStartObject();
for (Hash hash : extRef.getHashes()) {
toXmlGenerator.writeFieldName("hash");
toXmlGenerator.writeStartObject();
toXmlGenerator.setNextIsAttribute(true);
toXmlGenerator.writeFieldName("alg");
toXmlGenerator.writeString(hash.getAlgorithm());
toXmlGenerator.setNextIsAttribute(false);

toXmlGenerator.setNextIsUnwrapped(true);
toXmlGenerator.writeStringField("", hash.getValue());

toXmlGenerator.writeEndObject();
}
if (extRef.getHashes() != null && !extRef.getHashes().isEmpty()) {
gen.writePOJOField("hashes", extRef.getHashes());
toXmlGenerator.writeEndObject();
}
toXmlGenerator.writeEndObject();
}

private void serializeJson(final JsonGenerator gen, final ExternalReference extRef) throws IOException {
gen.writeStartObject();
gen.writeStringField("type", extRef.getType().getTypeName());
gen.writeStringField("url", extRef.getUrl());
if (extRef.getComment() != null) {
gen.writeStringField("comment", extRef.getComment());
}
if (CollectionUtils.isNotEmpty(extRef.getHashes())) {
gen.writeFieldName("hashes");
gen.writeStartArray();
for (Hash hash : extRef.getHashes()) {
gen.writeStartObject();
gen.writeStringField("alg", hash.getAlgorithm());
gen.writeStringField("content", hash.getValue());
gen.writeEndObject();
}
gen.writeEndObject();
gen.writeEndArray();
}
gen.writeEndObject();
}
}
90 changes: 20 additions & 70 deletions src/test/java/org/cyclonedx/Issue214RegressionTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,14 @@
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.regex.Pattern;
import javax.xml.XMLConstants;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.util.stream.Stream;

import org.apache.commons.io.IOUtils;
import org.cyclonedx.generators.BomGeneratorFactory;
import org.cyclonedx.exception.GeneratorException;
import org.cyclonedx.generators.json.BomJsonGenerator;
import org.cyclonedx.generators.xml.BomXmlGenerator;
import org.cyclonedx.model.Bom;
Expand All @@ -29,58 +22,39 @@
import org.cyclonedx.parsers.Parser;
import org.cyclonedx.parsers.XmlParser;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.w3c.dom.Document;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;


public class Issue214RegressionTest
{
@Test
public void schema13JsonObjectGenerationTest()
throws IOException, ReflectiveOperationException
{
performJsonTest(Version.VERSION_13);
}

@Test
public void schema14JsonObjectGenerationTest()
throws IOException, ReflectiveOperationException
{
performJsonTest(Version.VERSION_14);
}

@Test
public void schema13XmlObjectGenerationTest()
throws ParserConfigurationException, IOException, ReflectiveOperationException
{
performXmlTest(Version.VERSION_13);
}

@Test
public void schema14XmlObjectGenerationTest()
throws ParserConfigurationException, IOException, ReflectiveOperationException
{
performXmlTest(Version.VERSION_14);
static Stream<Arguments> testData() {
return Stream.of(
Arguments.of(Version.VERSION_16),
Arguments.of(Version.VERSION_15),
Arguments.of(Version.VERSION_14),
Arguments.of(Version.VERSION_13)
);
}

@Test
public void schema15XmlObjectGenerationTest()
throws ParserConfigurationException, IOException, ReflectiveOperationException
{
performXmlTest(Version.VERSION_15);
@ParameterizedTest
@MethodSource("testData")
public void testObjectGeneration(Version version) throws IOException, ReflectiveOperationException, GeneratorException {
performJsonTest(version);
performXmlTest(version);
}

private void performXmlTest(final Version pSpecVersion)
throws ParserConfigurationException, IOException, ReflectiveOperationException
throws GeneratorException, ReflectiveOperationException, IOException
{
final Bom inputBom = createIssue214Bom();
BomXmlGenerator generator = BomGeneratorFactory.createXml(pSpecVersion, inputBom);
Document doc = generator.generate();

Assertions.assertTrue(BomXmlGenerator.class.isAssignableFrom(generator.getClass()));
Assertions.assertEquals(pSpecVersion, generator.getSchemaVersion());

final String actual = xmlDocumentToString(doc);
final String actual = generator.toXmlString();
final String expected = readFixture("/regression/issue214-expected-output.xml", pSpecVersion);
Assertions.assertEquals(expected, actual);
validate(actual, XmlParser.class, pSpecVersion);
Expand All @@ -101,37 +75,13 @@ private void performJsonTest(final Version pSpecVersion)
validate(actual, JsonParser.class, pSpecVersion);
}

private String xmlDocumentToString(final Document doc)
{
Assertions.assertNotNull(doc);
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer;
try {
tf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
transformer = tf.newTransformer();

transformer.setOutputProperty(OutputKeys.ENCODING, StandardCharsets.UTF_8.name());
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, "yes");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");

StringWriter sw = new StringWriter();
transformer.transform(new DOMSource(doc), new StreamResult(sw));
return sw.getBuffer().toString().trim();
}
catch (TransformerException ex) {
Assertions.fail("Failed to serialize XML document", ex);
}
return null;
}

private String readFixture(final String pPath, final Version pSpecVersion)
{
try (InputStream is = getClass().getResourceAsStream(pPath)) {
if (is != null) {
String result = IOUtils.toString(is, StandardCharsets.UTF_8);
result = result.replaceAll(Pattern.quote("${specVersion}"), pSpecVersion.getVersionString());
return result.trim();
return result;
}
else {
Assertions.fail("failed to read expected data file: " + pPath);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,4 @@
"type" : "library"
}
]
}
}
38 changes: 19 additions & 19 deletions src/test/resources/regression/issue214-expected-output.xml
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<bom xmlns="http://cyclonedx.org/schema/bom/${specVersion}" version="1">
<components>
<component type="library">
<group>org.example</group>
<name>mylibrary</name>
<version>1.0.0</version>
<externalReferences>
<reference type="bom">
<url>https://example.org/support/sbom/portal-server/1.0.0</url>
<comment>An external SBOM that describes what this component includes</comment>
<hashes>
<hash alg="MD5">2cd42512b65500dc7ba0ff13490b0b73</hash>
<hash alg="SHA-1">226247b40160f2892fa4c7851b5b913d5d10912d</hash>
<hash alg="SHA-256">09a72795a920c1a9c0209cfb8395f8d97089832d249cba8c0938a3423b3ed1d1</hash>
</hashes>
</reference>
</externalReferences>
</component>
</components>
<bom version="1" xmlns="http://cyclonedx.org/schema/bom/${specVersion}">
<components>
<component type="library">
<group>org.example</group>
<name>mylibrary</name>
<version>1.0.0</version>
<externalReferences>
<reference type="bom">
<url>https://example.org/support/sbom/portal-server/1.0.0</url>
<comment>An external SBOM that describes what this component includes</comment>
<hashes>
<hash alg="MD5">2cd42512b65500dc7ba0ff13490b0b73</hash>
<hash alg="SHA-1">226247b40160f2892fa4c7851b5b913d5d10912d</hash>
<hash alg="SHA-256">09a72795a920c1a9c0209cfb8395f8d97089832d249cba8c0938a3423b3ed1d1</hash>
</hashes>
</reference>
</externalReferences>
</component>
</components>
</bom>

0 comments on commit 3107a5e

Please sign in to comment.