Skip to content

Commit 93492e1

Browse files
committed
Update External Reference
Signed-off-by: Alex Alzate <aalzate@sonatype.com>
1 parent adb8897 commit 93492e1

File tree

6 files changed

+117
-136
lines changed

6 files changed

+117
-136
lines changed

pom.xml

+7
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,13 @@
161161
<artifactId>commons-lang3</artifactId>
162162
<version>3.14.0</version>
163163
</dependency>
164+
165+
<dependency>
166+
<groupId>org.apache.commons</groupId>
167+
<artifactId>commons-collections4</artifactId>
168+
<version>4.4</version>
169+
</dependency>
170+
164171

165172
<!-- Package URL -->
166173

src/main/java/org/cyclonedx/model/ExternalReference.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
@JsonIgnoreProperties(ignoreUnknown = true)
3636
@JsonInclude(JsonInclude.Include.NON_NULL)
3737
@JsonSerialize(using = ExternalReferenceSerializer.class)
38-
@JsonPropertyOrder({"url", "comment", "hashes"})
38+
@JsonPropertyOrder({"bom", "url", "comment", "hashes"})
3939
public class ExternalReference {
4040

4141
public enum Type {
@@ -59,6 +59,8 @@ public enum Type {
5959
DOCUMENTATION("documentation"),
6060
@JsonProperty("support")
6161
SUPPORT("support"),
62+
@JsonProperty("source-distribution")
63+
SOURCE_DISTRIBUTION("source-distribution"),
6264
@JsonProperty("distribution")
6365
DISTRIBUTION("distribution"),
6466
@JsonProperty("distribution-intake")
@@ -113,6 +115,8 @@ public enum Type {
113115
EVIDENCE("evidence"),
114116
@JsonProperty("formulation")
115117
FORMULATION("formulation"),
118+
@JsonProperty("rfc-9116")
119+
RFC_9116("rfc-9116"),
116120
@JsonProperty("other")
117121
OTHER("other");
118122

src/main/java/org/cyclonedx/util/serializer/ExternalReferenceSerializer.java

+65-45
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,19 @@
2020

2121
import java.io.IOException;
2222
import java.util.function.BiPredicate;
23-
import javax.xml.stream.XMLStreamException;
24-
import javax.xml.stream.XMLStreamWriter;
2523

2624
import com.fasterxml.jackson.core.JsonGenerator;
2725
import com.fasterxml.jackson.databind.SerializerProvider;
2826
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
2927
import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator;
28+
import org.apache.commons.collections4.CollectionUtils;
3029
import org.cyclonedx.model.ExternalReference;
3130
import org.cyclonedx.model.ExternalReference.Type;
3231
import org.cyclonedx.model.Hash;
3332
import org.cyclonedx.util.BomUtils;
3433

35-
public class ExternalReferenceSerializer extends StdSerializer<ExternalReference>
34+
public class ExternalReferenceSerializer
35+
extends StdSerializer<ExternalReference>
3636
{
3737
public ExternalReferenceSerializer() {
3838
this(null);
@@ -46,52 +46,72 @@ public ExternalReferenceSerializer(final Class<ExternalReference> t) {
4646
public void serialize(
4747
final ExternalReference extRef, final JsonGenerator gen, final SerializerProvider provider) throws IOException
4848
{
49-
final BiPredicate<Type, String> validateExternalReference = (type, url) -> (type != null && url != null && BomUtils.validateUriString(url));
49+
final BiPredicate<Type, String> validateExternalReference =
50+
(type, url) -> (type != null && url != null && BomUtils.validateUriString(url));
51+
52+
if (!validateExternalReference.test(extRef.getType(), extRef.getUrl())) {
53+
return;
54+
}
55+
5056
if (gen instanceof ToXmlGenerator) {
51-
final ToXmlGenerator toXmlGenerator = (ToXmlGenerator) gen;
52-
final XMLStreamWriter staxWriter = toXmlGenerator.getStaxWriter();
57+
serializeXml((ToXmlGenerator) gen, extRef);
58+
}
59+
else {
60+
serializeJson(gen, extRef);
61+
}
62+
}
5363

54-
if (validateExternalReference.test(extRef.getType(), extRef.getUrl())) {
55-
try {
56-
staxWriter.writeStartElement("reference");
57-
staxWriter.writeAttribute("type", extRef.getType().getTypeName());
58-
staxWriter.writeStartElement("url");
59-
staxWriter.writeCharacters(extRef.getUrl());
60-
staxWriter.writeEndElement();
61-
if (extRef.getComment() != null) {
62-
staxWriter.writeStartElement("comment");
63-
staxWriter.writeCharacters(extRef.getComment());
64-
staxWriter.writeEndElement();
65-
}
66-
if (extRef.getHashes() != null && !extRef.getHashes().isEmpty()) {
67-
staxWriter.writeStartElement("hashes");
68-
for (Hash hash : extRef.getHashes()) {
69-
if (hash != null) {
70-
staxWriter.writeStartElement("hash");
71-
staxWriter.writeAttribute("alg", hash.getAlgorithm());
72-
staxWriter.writeCharacters(hash.getValue());
73-
staxWriter.writeEndElement();
74-
}
75-
}
76-
staxWriter.writeEndElement();
77-
}
78-
staxWriter.writeEndElement();
79-
}
80-
catch (XMLStreamException ex) {
81-
throw new IOException(ex);
82-
}
83-
}
84-
} else if (validateExternalReference.test(extRef.getType(), extRef.getUrl())) {
85-
gen.writeStartObject();
86-
gen.writeStringField("type", extRef.getType().getTypeName());
87-
gen.writeStringField("url", extRef.getUrl());
88-
if (extRef.getComment() != null) {
89-
gen.writeStringField("comment", extRef.getComment());
64+
private void serializeXml(final ToXmlGenerator toXmlGenerator, final ExternalReference extRef) throws IOException {
65+
toXmlGenerator.writeStartObject();
66+
67+
toXmlGenerator.setNextIsAttribute(true);
68+
toXmlGenerator.writeFieldName("type");
69+
toXmlGenerator.writeString(extRef.getType().getTypeName());
70+
toXmlGenerator.setNextIsAttribute(false);
71+
72+
toXmlGenerator.writeStringField("url", extRef.getUrl());
73+
if (extRef.getComment() != null) {
74+
toXmlGenerator.writeStringField("comment", extRef.getComment());
75+
}
76+
if (CollectionUtils.isNotEmpty(extRef.getHashes())) {
77+
toXmlGenerator.writeFieldName("hashes");
78+
toXmlGenerator.writeStartObject();
79+
for (Hash hash : extRef.getHashes()) {
80+
toXmlGenerator.writeFieldName("hash");
81+
toXmlGenerator.writeStartObject();
82+
toXmlGenerator.setNextIsAttribute(true);
83+
toXmlGenerator.writeFieldName("alg");
84+
toXmlGenerator.writeString(hash.getAlgorithm());
85+
toXmlGenerator.setNextIsAttribute(false);
86+
87+
toXmlGenerator.setNextIsUnwrapped(true);
88+
toXmlGenerator.writeStringField("", hash.getValue());
89+
90+
toXmlGenerator.writeEndObject();
9091
}
91-
if (extRef.getHashes() != null && !extRef.getHashes().isEmpty()) {
92-
gen.writePOJOField("hashes", extRef.getHashes());
92+
toXmlGenerator.writeEndObject();
93+
}
94+
toXmlGenerator.writeEndObject();
95+
}
96+
97+
private void serializeJson(final JsonGenerator gen, final ExternalReference extRef) throws IOException {
98+
gen.writeStartObject();
99+
gen.writeStringField("type", extRef.getType().getTypeName());
100+
gen.writeStringField("url", extRef.getUrl());
101+
if (extRef.getComment() != null) {
102+
gen.writeStringField("comment", extRef.getComment());
103+
}
104+
if (CollectionUtils.isNotEmpty(extRef.getHashes())) {
105+
gen.writeFieldName("hashes");
106+
gen.writeStartArray();
107+
for (Hash hash : extRef.getHashes()) {
108+
gen.writeStartObject();
109+
gen.writeStringField("alg", hash.getAlgorithm());
110+
gen.writeStringField("content", hash.getValue());
111+
gen.writeEndObject();
93112
}
94-
gen.writeEndObject();
113+
gen.writeEndArray();
95114
}
115+
gen.writeEndObject();
96116
}
97117
}

src/test/java/org/cyclonedx/Issue214RegressionTest.java

+20-70
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,14 @@
44
import java.io.FileWriter;
55
import java.io.IOException;
66
import java.io.InputStream;
7-
import java.io.StringWriter;
87
import java.nio.charset.StandardCharsets;
98
import java.util.Arrays;
109
import java.util.regex.Pattern;
11-
import javax.xml.XMLConstants;
12-
import javax.xml.parsers.ParserConfigurationException;
13-
import javax.xml.transform.OutputKeys;
14-
import javax.xml.transform.Transformer;
15-
import javax.xml.transform.TransformerException;
16-
import javax.xml.transform.TransformerFactory;
17-
import javax.xml.transform.dom.DOMSource;
18-
import javax.xml.transform.stream.StreamResult;
10+
import java.util.stream.Stream;
1911

2012
import org.apache.commons.io.IOUtils;
2113
import org.cyclonedx.generators.BomGeneratorFactory;
14+
import org.cyclonedx.exception.GeneratorException;
2215
import org.cyclonedx.generators.json.BomJsonGenerator;
2316
import org.cyclonedx.generators.xml.BomXmlGenerator;
2417
import org.cyclonedx.model.Bom;
@@ -29,58 +22,39 @@
2922
import org.cyclonedx.parsers.Parser;
3023
import org.cyclonedx.parsers.XmlParser;
3124
import org.junit.jupiter.api.Assertions;
32-
import org.junit.jupiter.api.Test;
33-
import org.w3c.dom.Document;
25+
import org.junit.jupiter.params.ParameterizedTest;
26+
import org.junit.jupiter.params.provider.Arguments;
27+
import org.junit.jupiter.params.provider.MethodSource;
3428

3529

3630
public class Issue214RegressionTest
3731
{
38-
@Test
39-
public void schema13JsonObjectGenerationTest()
40-
throws IOException, ReflectiveOperationException
41-
{
42-
performJsonTest(Version.VERSION_13);
43-
}
44-
45-
@Test
46-
public void schema14JsonObjectGenerationTest()
47-
throws IOException, ReflectiveOperationException
48-
{
49-
performJsonTest(Version.VERSION_14);
50-
}
51-
52-
@Test
53-
public void schema13XmlObjectGenerationTest()
54-
throws ParserConfigurationException, IOException, ReflectiveOperationException
55-
{
56-
performXmlTest(Version.VERSION_13);
57-
}
58-
59-
@Test
60-
public void schema14XmlObjectGenerationTest()
61-
throws ParserConfigurationException, IOException, ReflectiveOperationException
62-
{
63-
performXmlTest(Version.VERSION_14);
32+
static Stream<Arguments> testData() {
33+
return Stream.of(
34+
Arguments.of(Version.VERSION_16),
35+
Arguments.of(Version.VERSION_15),
36+
Arguments.of(Version.VERSION_14),
37+
Arguments.of(Version.VERSION_13)
38+
);
6439
}
6540

66-
@Test
67-
public void schema15XmlObjectGenerationTest()
68-
throws ParserConfigurationException, IOException, ReflectiveOperationException
69-
{
70-
performXmlTest(Version.VERSION_15);
41+
@ParameterizedTest
42+
@MethodSource("testData")
43+
public void testObjectGeneration(Version version) throws IOException, ReflectiveOperationException, GeneratorException {
44+
performJsonTest(version);
45+
performXmlTest(version);
7146
}
7247

7348
private void performXmlTest(final Version pSpecVersion)
74-
throws ParserConfigurationException, IOException, ReflectiveOperationException
49+
throws GeneratorException, ReflectiveOperationException, IOException
7550
{
7651
final Bom inputBom = createIssue214Bom();
7752
BomXmlGenerator generator = BomGeneratorFactory.createXml(pSpecVersion, inputBom);
78-
Document doc = generator.generate();
7953

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

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

104-
private String xmlDocumentToString(final Document doc)
105-
{
106-
Assertions.assertNotNull(doc);
107-
TransformerFactory tf = TransformerFactory.newInstance();
108-
Transformer transformer;
109-
try {
110-
tf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
111-
transformer = tf.newTransformer();
112-
113-
transformer.setOutputProperty(OutputKeys.ENCODING, StandardCharsets.UTF_8.name());
114-
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
115-
transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, "yes");
116-
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
117-
118-
StringWriter sw = new StringWriter();
119-
transformer.transform(new DOMSource(doc), new StreamResult(sw));
120-
return sw.getBuffer().toString().trim();
121-
}
122-
catch (TransformerException ex) {
123-
Assertions.fail("Failed to serialize XML document", ex);
124-
}
125-
return null;
126-
}
127-
12878
private String readFixture(final String pPath, final Version pSpecVersion)
12979
{
13080
try (InputStream is = getClass().getResourceAsStream(pPath)) {
13181
if (is != null) {
13282
String result = IOUtils.toString(is, StandardCharsets.UTF_8);
13383
result = result.replaceAll(Pattern.quote("${specVersion}"), pSpecVersion.getVersionString());
134-
return result.trim();
84+
return result;
13585
}
13686
else {
13787
Assertions.fail("failed to read expected data file: " + pPath);

src/test/resources/regression/issue214-expected-output.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,4 @@
3131
"type" : "library"
3232
}
3333
]
34-
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<bom xmlns="http://cyclonedx.org/schema/bom/${specVersion}" version="1">
3-
<components>
4-
<component type="library">
5-
<group>org.example</group>
6-
<name>mylibrary</name>
7-
<version>1.0.0</version>
8-
<externalReferences>
9-
<reference type="bom">
10-
<url>https://example.org/support/sbom/portal-server/1.0.0</url>
11-
<comment>An external SBOM that describes what this component includes</comment>
12-
<hashes>
13-
<hash alg="MD5">2cd42512b65500dc7ba0ff13490b0b73</hash>
14-
<hash alg="SHA-1">226247b40160f2892fa4c7851b5b913d5d10912d</hash>
15-
<hash alg="SHA-256">09a72795a920c1a9c0209cfb8395f8d97089832d249cba8c0938a3423b3ed1d1</hash>
16-
</hashes>
17-
</reference>
18-
</externalReferences>
19-
</component>
20-
</components>
2+
<bom version="1" xmlns="http://cyclonedx.org/schema/bom/${specVersion}">
3+
<components>
4+
<component type="library">
5+
<group>org.example</group>
6+
<name>mylibrary</name>
7+
<version>1.0.0</version>
8+
<externalReferences>
9+
<reference type="bom">
10+
<url>https://example.org/support/sbom/portal-server/1.0.0</url>
11+
<comment>An external SBOM that describes what this component includes</comment>
12+
<hashes>
13+
<hash alg="MD5">2cd42512b65500dc7ba0ff13490b0b73</hash>
14+
<hash alg="SHA-1">226247b40160f2892fa4c7851b5b913d5d10912d</hash>
15+
<hash alg="SHA-256">09a72795a920c1a9c0209cfb8395f8d97089832d249cba8c0938a3423b3ed1d1</hash>
16+
</hashes>
17+
</reference>
18+
</externalReferences>
19+
</component>
20+
</components>
2121
</bom>

0 commit comments

Comments
 (0)