Skip to content

Commit 81f443c

Browse files
authored
Merge pull request #730 from CycloneDX/issue_638
Fixes #638
2 parents bf70f1c + ce20318 commit 81f443c

File tree

8 files changed

+1014
-2
lines changed

8 files changed

+1014
-2
lines changed

src/main/java/org/cyclonedx/generators/AbstractBomGenerator.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import org.cyclonedx.Format;
77
import org.cyclonedx.Version;
88
import org.cyclonedx.model.Bom;
9+
import org.cyclonedx.util.serializer.AuthorsBeanSerializerModifier;
910
import org.cyclonedx.util.serializer.CustomSerializerModifier;
1011
import org.cyclonedx.util.serializer.EvidenceSerializer;
1112
import org.cyclonedx.util.serializer.ExternalReferenceSerializer;
@@ -98,6 +99,10 @@ protected void setupObjectMapper(boolean isXml) {
9899
hash1Module.addSerializer(new HashSerializer(version));
99100
mapper.registerModule(hash1Module);
100101

102+
SimpleModule authorsModule = new SimpleModule();
103+
authorsModule.setSerializerModifier(new AuthorsBeanSerializerModifier(version));
104+
mapper.registerModule(authorsModule);
105+
101106
SimpleModule propertiesModule = new SimpleModule();
102107
propertiesModule.setSerializerModifier(new CustomSerializerModifier(isXml, version));
103108
mapper.registerModule(propertiesModule);

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

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
import java.util.List;
2323
import java.util.Objects;
2424

25+
import com.fasterxml.jackson.annotation.JsonGetter;
2526
import com.fasterxml.jackson.annotation.JsonIgnore;
27+
import com.fasterxml.jackson.annotation.JsonSetter;
2628
import com.fasterxml.jackson.annotation.JsonUnwrapped;
2729
import org.cyclonedx.Version;
2830
import org.cyclonedx.model.component.ModelCard;
@@ -223,7 +225,6 @@ public String getScopeName() {
223225
private Tags tags;
224226

225227
@VersionFilter(Version.VERSION_16)
226-
@JsonProperty("authors")
227228
private List<OrganizationalContact> authors;
228229

229230
@VersionFilter(Version.VERSION_16)
@@ -258,10 +259,26 @@ public void setSupplier(OrganizationalEntity supplier) {
258259
this.supplier = supplier;
259260
}
260261

262+
/**
263+
* Gets the deprecated author field as a string.
264+
* @return the author name as a string
265+
* @deprecated since version 1.6, use {@link #getAuthors()} instead
266+
*/
267+
@Deprecated
268+
@JsonGetter("author")
269+
@JacksonXmlProperty(localName = "author")
261270
public String getAuthor() {
262271
return author;
263272
}
264273

274+
/**
275+
* Sets the deprecated author field as a string.
276+
* @param author the author name as a string
277+
* @deprecated since version 1.6, use {@link #setAuthors(List)} instead
278+
*/
279+
@Deprecated
280+
@JsonSetter("author")
281+
@JacksonXmlProperty(localName = "author")
265282
public void setAuthor(String author) {
266283
this.author = author;
267284
}
@@ -556,10 +573,24 @@ public void setTags(final Tags tags) {
556573
this.tags = tags;
557574
}
558575

576+
/**
577+
* Gets the component authors as a list of contacts.
578+
* This replaces the deprecated string-based author field.
579+
* @return the list of authors, or null if not set
580+
* @since 1.6
581+
*/
582+
@JsonGetter("authors")
559583
public List<OrganizationalContact> getAuthors() {
560584
return authors;
561585
}
562586

587+
/**
588+
* Sets the component authors as a list of contacts.
589+
* This replaces the deprecated string-based author field.
590+
* @param authors the list of authors
591+
* @since 1.6
592+
*/
593+
@JsonSetter("authors")
563594
public void setAuthors(final List<OrganizationalContact> authors) {
564595
this.authors = authors;
565596
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,12 @@
2424
import com.fasterxml.jackson.annotation.JsonProperty;
2525
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
2626
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
27+
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
2728
import org.cyclonedx.Version;
2829

2930
import java.util.Objects;
3031

32+
@JacksonXmlRootElement(localName = "author")
3133
@JsonIgnoreProperties(ignoreUnknown = true)
3234
@JsonInclude(Include.NON_EMPTY)
3335
@JsonPropertyOrder({"name", "email", "phone"})

src/main/java/org/cyclonedx/util/introspector/VersionJsonAnnotationIntrospector.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public boolean hasIgnoreMarker(final AnnotatedMember m) {
4747

4848
// Check if the field has the XmlOnly annotation
4949
if (m.hasAnnotation(XmlOnly.class)) {
50-
// If true, the field should be ignored for XML serialization
50+
// If true, the field should be ignored for JSON serialization
5151
return true;
5252
}
5353

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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+
package org.cyclonedx.util.serializer;
20+
21+
import com.fasterxml.jackson.databind.BeanDescription;
22+
import com.fasterxml.jackson.databind.JsonSerializer;
23+
import com.fasterxml.jackson.databind.SerializationConfig;
24+
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
25+
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
26+
import org.cyclonedx.Version;
27+
import org.cyclonedx.model.Component;
28+
29+
import java.util.List;
30+
31+
/**
32+
* Bean serializer modifier for Component.authors field.
33+
* Applies the AuthorsSerializer only to the authors field in Component class.
34+
*/
35+
public class AuthorsBeanSerializerModifier extends BeanSerializerModifier {
36+
private final Version version;
37+
38+
public AuthorsBeanSerializerModifier(Version version) {
39+
this.version = version;
40+
}
41+
42+
@Override
43+
public List<BeanPropertyWriter> changeProperties(
44+
SerializationConfig config,
45+
BeanDescription beanDesc,
46+
List<BeanPropertyWriter> beanProperties) {
47+
48+
// Only modify Component class
49+
if (Component.class.isAssignableFrom(beanDesc.getBeanClass())) {
50+
java.util.Iterator<BeanPropertyWriter> iterator = beanProperties.iterator();
51+
while (iterator.hasNext()) {
52+
BeanPropertyWriter writer = iterator.next();
53+
// Find the authors property
54+
if ("authors".equals(writer.getName())) {
55+
// Check if the current version supports the authors field (v1.6+)
56+
if (version.getVersion() < Version.VERSION_16.getVersion()) {
57+
// Remove the property for versions earlier than 1.6
58+
iterator.remove();
59+
} else {
60+
// Assign the custom serializer for v1.6+
61+
JsonSerializer<?> serializer = new AuthorsSerializer();
62+
writer.assignSerializer((JsonSerializer<Object>) serializer);
63+
}
64+
break;
65+
}
66+
}
67+
}
68+
return beanProperties;
69+
}
70+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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+
package org.cyclonedx.util.serializer;
20+
21+
import com.fasterxml.jackson.core.JsonGenerator;
22+
import com.fasterxml.jackson.databind.SerializerProvider;
23+
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
24+
import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator;
25+
import org.cyclonedx.model.OrganizationalContact;
26+
27+
import java.io.IOException;
28+
import java.util.List;
29+
30+
/**
31+
* Custom serializer for the Component.authors field to handle XML element naming.
32+
* This serializer ensures that:
33+
* - JSON: serializes as "authors": [...]
34+
* - XML: serializes as authors-author
35+
* This is necessary because the deprecated "author" field also uses author element,
36+
* creating a naming conflict that standard Jackson annotations cannot resolve.
37+
* Version filtering is handled by AuthorsBeanSerializerModifier, which removes this
38+
* property entirely for versions prior to 1.6.
39+
*/
40+
public class AuthorsSerializer extends StdSerializer<List<OrganizationalContact>> {
41+
42+
@SuppressWarnings("unchecked")
43+
public AuthorsSerializer() {
44+
super((Class<List<OrganizationalContact>>) (Class<?>) List.class);
45+
}
46+
47+
@Override
48+
public void serialize(List<OrganizationalContact> authors, JsonGenerator gen, SerializerProvider serializers)
49+
throws IOException {
50+
51+
if (authors == null || authors.isEmpty()) {
52+
return;
53+
}
54+
55+
// Check if we're serializing to XML
56+
if (gen instanceof ToXmlGenerator) {
57+
ToXmlGenerator xmlGen = (ToXmlGenerator) gen;
58+
59+
// For XML: The property name "authors" creates the wrapper <authors>
60+
// We need to write the array structure, and each item gets its own <author> tag
61+
xmlGen.writeStartArray();
62+
for (OrganizationalContact contact : authors) {
63+
// Set the element name for this array item to "author"
64+
xmlGen.setNextName(new javax.xml.namespace.QName("author"));
65+
serializers.defaultSerializeValue(contact, xmlGen);
66+
}
67+
xmlGen.writeEndArray();
68+
} else {
69+
// For JSON, write as a standard array
70+
gen.writeStartArray();
71+
for (OrganizationalContact contact : authors) {
72+
serializers.defaultSerializeValue(contact, gen);
73+
}
74+
gen.writeEndArray();
75+
}
76+
}
77+
}

0 commit comments

Comments
 (0)