Skip to content

Commit 69c020c

Browse files
committed
Code refactored.
Signed-off-by: jjhz <[email protected]>
1 parent e51d785 commit 69c020c

10 files changed

+273
-81
lines changed

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
import com.fasterxml.jackson.annotation.JsonIgnore;
2626
import com.fasterxml.jackson.annotation.JsonUnwrapped;
27+
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
2728
import org.cyclonedx.Version;
2829
import org.cyclonedx.model.component.ModelCard;
2930
import org.cyclonedx.model.component.crypto.CryptoProperties;
@@ -44,6 +45,7 @@
4445
import com.github.packageurl.PackageURL;
4546
import org.cyclonedx.util.deserializer.LicenseDeserializer;
4647
import org.cyclonedx.util.deserializer.PropertiesDeserializer;
48+
import org.cyclonedx.util.serializer.ComponentAuthorsSerializer;
4749

4850
@SuppressWarnings("unused")
4951
@JacksonXmlRootElement(localName = "component")
@@ -224,6 +226,10 @@ public String getScopeName() {
224226
private Tags tags;
225227

226228
@VersionFilter(Version.VERSION_16)
229+
@JsonProperty("authors")
230+
@JacksonXmlElementWrapper(localName = "authors")
231+
@JsonSerialize(using = ComponentAuthorsSerializer.class)
232+
@JsonDeserialize(using = ComponentAuthorsDeserializer.class)
227233
private List<OrganizationalContact> authors;
228234

229235
@VersionFilter(Version.VERSION_16)
@@ -556,13 +562,10 @@ public void setTags(final Tags tags) {
556562
this.tags = tags;
557563
}
558564

559-
@JacksonXmlElementWrapper(localName = "authors")
560-
@JacksonXmlProperty(localName = "author")
561565
public List<OrganizationalContact> getAuthors() {
562566
return authors;
563567
}
564568

565-
@JsonDeserialize(using = ComponentAuthorsDeserializer.class)
566569
public void setAuthors(final List<OrganizationalContact> authors) {
567570
this.authors = authors;
568571
}

src/main/java/org/cyclonedx/util/deserializer/ComponentAuthorsDeserializer.java

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,19 @@ public List<OrganizationalContact> deserialize(JsonParser p, DeserializationCont
3737
if (p instanceof FromXmlParser) { // Handle XML
3838
while (p.nextToken() != JsonToken.END_OBJECT && p.currentToken() != JsonToken.END_OBJECT) {
3939
if (p.currentToken() == JsonToken.FIELD_NAME) {
40-
// Handles both <author> and <authors> as the item tag
41-
p.nextToken();
42-
OrganizationalContact contact = p.readValueAs(OrganizationalContact.class);
43-
contacts.add(contact);
40+
String fieldName = p.currentName();
41+
if ("author".equals(fieldName) || "authors".equals(fieldName)) {
42+
// Handles both <author> and <authors> as the item tag
43+
p.nextToken();
44+
contacts.add(p.readValueAs(OrganizationalContact.class));
45+
} else {
46+
ctxt.reportInputMismatch(
47+
List.class,
48+
"Unexpected field '%s' in %s",
49+
fieldName,
50+
getClass().getSimpleName()
51+
);
52+
}
4453
}
4554
}
4655
} else { // Handle JSON
@@ -53,4 +62,4 @@ public List<OrganizationalContact> deserialize(JsonParser p, DeserializationCont
5362
}
5463
return contacts;
5564
}
56-
}
65+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* This file is part of CycloneDX Core (Java).
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* SPDX-License-Identifier: Apache-2.0
17+
* Copyright (c) OWASP Foundation. All Rights Reserved.
18+
*/
19+
20+
package org.cyclonedx.util.serializer;
21+
22+
import com.fasterxml.jackson.core.JsonGenerator;
23+
import com.fasterxml.jackson.databind.JsonSerializer;
24+
import com.fasterxml.jackson.databind.SerializerProvider;
25+
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
26+
import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator;
27+
import org.cyclonedx.model.OrganizationalContact;
28+
29+
import javax.xml.namespace.QName;
30+
import java.io.IOException;
31+
import java.util.List;
32+
33+
public class ComponentAuthorsSerializer extends StdSerializer<List<OrganizationalContact>> {
34+
35+
36+
public ComponentAuthorsSerializer() {
37+
super(List.class, false);
38+
}
39+
40+
41+
@Override
42+
public void serialize(List<OrganizationalContact> authors, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
43+
if (jsonGenerator instanceof ToXmlGenerator) { // Handle XML
44+
ToXmlGenerator xmlGenerator = (ToXmlGenerator) jsonGenerator;
45+
xmlGenerator.writeStartArray();
46+
47+
for (OrganizationalContact author : authors) {
48+
xmlGenerator.setNextName(new QName("author"));
49+
xmlGenerator.writeObject(author);
50+
}
51+
52+
xmlGenerator.writeEndArray();
53+
} else { // Handle JSON, as default.
54+
JsonSerializer<Object> defaultSerializer =
55+
serializerProvider.findValueSerializer(List.class, null);
56+
defaultSerializer.serialize(authors, jsonGenerator, serializerProvider);
57+
}
58+
}
59+
60+
}

src/test/java/org/cyclonedx/BomJsonGeneratorTest.java

Lines changed: 63 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,14 @@
2121
import com.fasterxml.jackson.databind.JsonNode;
2222

2323
import java.nio.charset.StandardCharsets;
24+
25+
import com.fasterxml.jackson.databind.ObjectMapper;
2426
import org.apache.commons.io.IOUtils;
2527
import org.cyclonedx.generators.BomGeneratorFactory;
2628
import org.cyclonedx.generators.json.BomJsonGenerator;
2729
import org.cyclonedx.generators.xml.BomXmlGenerator;
28-
import org.cyclonedx.model.Bom;
29-
import org.cyclonedx.model.Component;
30+
import org.cyclonedx.model.*;
3031
import org.cyclonedx.model.Component.Type;
31-
import org.cyclonedx.model.License;
32-
import org.cyclonedx.model.LicenseChoice;
33-
import org.cyclonedx.model.Metadata;
34-
import org.cyclonedx.model.Service;
3532
import org.cyclonedx.model.license.Expression;
3633
import org.cyclonedx.parsers.JsonParser;
3734
import org.cyclonedx.parsers.XmlParser;
@@ -48,6 +45,8 @@
4845
import java.nio.file.Files;
4946
import java.nio.file.Path;
5047
import java.util.ArrayList;
48+
import java.util.Iterator;
49+
import java.util.List;
5150
import java.util.stream.Stream;
5251
import java.util.Objects;
5352

@@ -603,15 +602,64 @@ public void testIssue492() throws Exception {
603602
}
604603

605604
@Test
606-
public void testComponentAuthorsDeserializationJsonObject16() throws Exception {
607-
Bom bom = createCommonJsonBom("/1.6/valid-component-authors-json-object-1.6.json");
608-
Component component = bom.getComponents().get(0);
609-
assertNotNull(component.getAuthors());
610-
assertEquals(2, component.getAuthors().size());
611-
assertEquals("Test Author 1", component.getAuthors().get(0).getName());
612-
assertEquals("[email protected]", component.getAuthors().get(0).getEmail());
613-
assertEquals("Test Author 2", component.getAuthors().get(1).getName());
614-
assertNull(component.getAuthors().get(1).getEmail());
605+
public void testComponentAuthorsSerializationAndDeserialization() throws Exception {
606+
Version version = Version.VERSION_16;
607+
Bom bom = createCommonJsonBom("/1.6/valid-component-authors-1.6.json");
608+
609+
assertNotNull(bom.getComponents());
610+
assertEquals(1, bom.getComponents().size());
611+
612+
Component bomComponent = bom.getComponents().get(0);
613+
assertEquals("Outer Author with String value", bomComponent.getAuthor());
614+
615+
List<OrganizationalContact> bomAuthors = bomComponent.getAuthors();
616+
assertNotNull(bomAuthors);
617+
assertEquals(2, bomAuthors.size());
618+
619+
OrganizationalContact bomAuthor1 = bomAuthors.get(0);
620+
OrganizationalContact bomAuthor2 = bomAuthors.get(1);
621+
622+
assertNotNull(bomAuthor1);
623+
assertEquals("Test Author 1", bomAuthor1.getName());
624+
assertEquals("[email protected]", bomAuthor1.getEmail());
625+
assertEquals("123", bomAuthor1.getPhone());
626+
627+
assertNotNull(bomAuthor2);
628+
assertEquals("Test Author 2", bomAuthor2.getName());
629+
assertEquals("[email protected]", bomAuthor2.getEmail());
630+
assertEquals("456", bomAuthor2.getPhone());
631+
632+
BomJsonGenerator generator = BomGeneratorFactory.createJson(version, bom);
633+
String jsonString = generator.toJsonString();
634+
635+
File loadedFile = writeToFile(jsonString);
636+
JsonParser parser = new JsonParser();
637+
assertTrue(parser.isValid(loadedFile, version));
638+
639+
// Verify the json content
640+
ObjectMapper mapper = new ObjectMapper();
641+
JsonNode rootNode = mapper.readTree(jsonString);
642+
643+
JsonNode component = rootNode.path("components").get(0);
644+
assertNotNull(component);
645+
646+
String outerAuthor = component.path("author").asText();
647+
assertEquals("Outer Author with String value", outerAuthor, "Outer author value mismatch");
648+
649+
JsonNode authorsNode = component.path("authors");
650+
assertTrue(authorsNode.isArray());
651+
assertEquals(2, authorsNode.size(), "Authors list size mismatch");
652+
653+
Iterator<JsonNode> elements = authorsNode.elements();
654+
JsonNode author1 = elements.next();
655+
assertEquals("Test Author 1", author1.path("name").asText());
656+
assertEquals("[email protected]", author1.path("email").asText());
657+
assertEquals("123", author1.path("phone").asText());
658+
659+
JsonNode author2 = elements.next();
660+
assertEquals("Test Author 2", author2.path("name").asText());
661+
assertEquals("[email protected]", author2.path("email").asText());
662+
assertEquals("456", author2.path("phone").asText());
615663
}
616664

617665
private void assertExternalReferenceInfo(Bom bom) {

src/test/java/org/cyclonedx/BomXmlGeneratorTest.java

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import org.junit.jupiter.params.ParameterizedTest;
4646
import org.junit.jupiter.params.provider.Arguments;
4747
import org.junit.jupiter.params.provider.MethodSource;
48+
import org.junit.jupiter.params.provider.ValueSource;
4849
import org.w3c.dom.Document;
4950
import org.w3c.dom.NodeList;
5051

@@ -794,10 +795,37 @@ public void testIssue408Regression_jsonToXml_externalReferenceBom() throws Excep
794795
assertTrue(parser.isValid(loadedFile, version));
795796
}
796797

797-
@Test
798-
public void testComponentAuthorSerializationOutputAsString() throws Exception {
798+
@ParameterizedTest
799+
@ValueSource(strings = {
800+
"/1.6/valid-component-authors-1.6.xml",
801+
"/1.6/invalid-component-authors-legacy-1.6.xml"
802+
})
803+
public void testComponentAuthorsSerializationAndDeserialization(String xmlFilePath) throws Exception {
799804
Version version = Version.VERSION_16;
800-
Bom bom = createCommonBomXml("/1.6/valid-component-authors-1.6.xml");
805+
Bom bom = createCommonBomXml(xmlFilePath);
806+
807+
assertNotNull(bom.getComponents());
808+
assertEquals(1, bom.getComponents().size());
809+
810+
Component bomComponent = bom.getComponents().get(0);
811+
assertEquals("Outer Author with String value", bomComponent.getAuthor());
812+
813+
List<OrganizationalContact> bomAuthors = bomComponent.getAuthors();
814+
assertNotNull(bomAuthors);
815+
assertEquals(2, bomAuthors.size());
816+
817+
OrganizationalContact bomAuthor1 = bomAuthors.get(0);
818+
OrganizationalContact bomAuthor2 = bomAuthors.get(1);
819+
820+
assertNotNull(bomAuthor1);
821+
assertEquals("Test Author 1", bomAuthor1.getName());
822+
assertEquals("[email protected]", bomAuthor1.getEmail());
823+
assertEquals("123", bomAuthor1.getPhone());
824+
825+
assertNotNull(bomAuthor2);
826+
assertEquals("Test Author 2", bomAuthor2.getName());
827+
assertEquals("[email protected]", bomAuthor2.getEmail());
828+
assertEquals("456", bomAuthor2.getPhone());
801829

802830
BomXmlGenerator generator = BomGeneratorFactory.createXml(version, bom);
803831
String xmlString = generator.toXmlString();
@@ -840,16 +868,26 @@ public Iterator<String> getPrefixes(String namespaceURI) {
840868

841869
assertEquals("Test Author 1", author1);
842870
assertEquals("Test Author 2", author2);
871+
872+
String email1 = xpath.evaluate("//bom:component/bom:authors/bom:author[1]/bom:email", doc);
873+
String email2 = xpath.evaluate("//bom:component/bom:authors/bom:author[2]/bom:email", doc);
874+
875+
assertEquals("[email protected]", email1);
876+
assertEquals("[email protected]", email2);
877+
878+
String phone1 = xpath.evaluate("//bom:component/bom:authors/bom:author[1]/bom:phone", doc);
879+
String phone2 = xpath.evaluate("//bom:component/bom:authors/bom:author[2]/bom:phone", doc);
880+
881+
assertEquals("123", phone1);
882+
assertEquals("456", phone2);
883+
884+
String outerAuthorStr = (String) xpath.evaluate("//bom:component/bom:author", doc, XPathConstants.STRING);
885+
assertEquals("Outer Author with String value", outerAuthorStr);
843886
}
844887

845888
@Test
846-
public void testComponentAuthorsDeserializationLegacy() throws Exception {
847-
Bom bom = createCommonBomXml("/1.6/invalid-component-authors-legacy-1.6.xml");
848-
Component component = bom.getComponents().get(0);
849-
assertNotNull(component.getAuthors());
850-
assertEquals(2, component.getAuthors().size());
851-
assertEquals("Test Author 1", component.getAuthors().get(0).getName());
852-
assertEquals("Test Author 2", component.getAuthors().get(1).getName());
889+
public void testComponentAuthorsWithInvalidItemTag(){
890+
assertThrows(ParseException.class, () -> createCommonBomXml("/1.6/invalid-component-authors-bad-item-name-1.6.xml"));
853891
}
854892

855893
private void assertExternalReferenceInfo(Bom bom) {
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<bom serialNumber="urn:uuid:e1acbeda-240f-4ab6-bd4e-749ab4183fec" version="1" xmlns="http://cyclonedx.org/schema/bom/1.6">
3+
<components>
4+
<component type="application">
5+
<authors>
6+
<bad-authors>
7+
<name>Test Author 1</name>
8+
<email>[email protected]</email>
9+
<phone>123</phone>
10+
</bad-authors>
11+
<bad-authors>
12+
<name>Test Author 2</name>
13+
<email>[email protected]</email>
14+
<phone>456</phone>
15+
</bad-authors>
16+
</authors>
17+
<author>Outer Author with String value</author>
18+
<name>Test Component</name>
19+
</component>
20+
</components>
21+
</bom>
Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<bom serialNumber="urn:uuid:e1acbeda-240f-4ab6-bd4e-749ab4183fec" version="1" xmlns="http://cyclonedx.org/schema/bom/1.6">
3-
<components>
4-
<component type="application">
5-
<authors>
6-
<authors>
7-
<name>Test Author 1</name>
8-
</authors>
9-
<authors>
10-
<name>Test Author 2</name>
11-
</authors>
12-
</authors>
13-
<name>Test Component</name>
14-
</component>
15-
</components>
3+
<components>
4+
<component type="application">
5+
<authors>
6+
<authors>
7+
<name>Test Author 1</name>
8+
<email>[email protected]</email>
9+
<phone>123</phone>
10+
</authors>
11+
<authors>
12+
<name>Test Author 2</name>
13+
<email>[email protected]</email>
14+
<phone>456</phone>
15+
</authors>
16+
</authors>
17+
<author>Outer Author with String value</author>
18+
<name>Test Component</name>
19+
</component>
20+
</components>
1621
</bom>

0 commit comments

Comments
 (0)