Skip to content

Commit c30b6bc

Browse files
authoredJun 16, 2024··
Merge pull request #428 from CycloneDX/Improve_backwards_compatibility
Serializer for Properties and Hashes for backwards compatibility
2 parents 6839872 + dc3d56f commit c30b6bc

File tree

8 files changed

+228
-4
lines changed

8 files changed

+228
-4
lines changed
 

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

+10
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
import org.cyclonedx.CycloneDxSchema;
66
import org.cyclonedx.Version;
77
import org.cyclonedx.model.Bom;
8+
import org.cyclonedx.util.serializer.CustomSerializerModifier;
89
import org.cyclonedx.util.serializer.EvidenceSerializer;
910
import org.cyclonedx.util.serializer.ExternalReferenceSerializer;
11+
import org.cyclonedx.util.serializer.HashSerializer;
1012
import org.cyclonedx.util.serializer.InputTypeSerializer;
1113
import org.cyclonedx.util.serializer.LicenseChoiceSerializer;
1214
import org.cyclonedx.util.serializer.LifecycleSerializer;
@@ -68,5 +70,13 @@ protected void setupObjectMapper(boolean isXml) {
6870
SimpleModule externalSerializer = new SimpleModule();
6971
externalSerializer.addSerializer(new ExternalReferenceSerializer(getSchemaVersion()));
7072
mapper.registerModule(externalSerializer);
73+
74+
SimpleModule hash1Module = new SimpleModule();
75+
hash1Module.addSerializer(new HashSerializer(version));
76+
mapper.registerModule(hash1Module);
77+
78+
SimpleModule propertiesModule = new SimpleModule();
79+
propertiesModule.setSerializerModifier(new CustomSerializerModifier(isXml, version));
80+
mapper.registerModule(propertiesModule);
7181
}
7282
}

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

+1
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ public String getScopeName() {
183183

184184
@VersionFilter(Version.VERSION_11)
185185
private List<ExternalReference> externalReferences;
186+
186187
@VersionFilter(Version.VERSION_13)
187188
private List<Property> properties;
188189

‎src/main/java/org/cyclonedx/model/formulation/common/InputType.java

-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package org.cyclonedx.model.formulation.common;
22

33
import java.util.List;
4-
import java.util.Objects;
54

65
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
76
import com.fasterxml.jackson.annotation.JsonInclude;

‎src/main/java/org/cyclonedx/util/deserializer/OrganizationalChoiceDeserializer.java

-2
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,8 @@
2323
import com.fasterxml.jackson.core.JsonParser;
2424
import com.fasterxml.jackson.core.JsonProcessingException;
2525
import com.fasterxml.jackson.databind.JsonDeserializer;
26-
import com.fasterxml.jackson.databind.node.ArrayNode;
2726
import com.fasterxml.jackson.databind.DeserializationContext;
2827
import com.fasterxml.jackson.databind.JsonNode;
29-
import com.fasterxml.jackson.databind.node.ObjectNode;
3028
import org.cyclonedx.model.OrganizationalChoice;
3129
import org.cyclonedx.model.OrganizationalContact;
3230
import org.cyclonedx.model.OrganizationalEntity;

‎src/main/java/org/cyclonedx/util/deserializer/TagsDeserializer.java

-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import com.fasterxml.jackson.databind.DeserializationContext;
99
import com.fasterxml.jackson.databind.JsonDeserializer;
1010
import com.fasterxml.jackson.databind.JsonNode;
11-
import com.fasterxml.jackson.databind.ObjectMapper;
1211
import com.fasterxml.jackson.databind.node.ArrayNode;
1312
import org.cyclonedx.model.component.Tags;
1413

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package org.cyclonedx.util.serializer;
2+
3+
import com.fasterxml.jackson.databind.BeanDescription;
4+
import com.fasterxml.jackson.databind.JsonSerializer;
5+
import com.fasterxml.jackson.databind.SerializationConfig;
6+
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
7+
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
8+
import org.cyclonedx.Version;
9+
import org.cyclonedx.model.Bom;
10+
11+
import java.util.Iterator;
12+
import java.util.List;
13+
14+
public class CustomSerializerModifier
15+
extends BeanSerializerModifier
16+
{
17+
private final Version version;
18+
19+
private final boolean isXml;
20+
21+
public CustomSerializerModifier(boolean isXml, Version version) {
22+
this.version = version;
23+
this.isXml = isXml;
24+
}
25+
26+
@Override
27+
public List<BeanPropertyWriter> changeProperties(
28+
SerializationConfig config,
29+
BeanDescription beanDesc,
30+
List<BeanPropertyWriter> beanProperties)
31+
{
32+
//Properties were introduced in 1.3 for XML and 1.5 for JSON
33+
//Meaning that we should only serialize properties if the version is 1.3 or higher for XML
34+
//and 1.5 or higher for JSON
35+
//This is to ensure backwards compatibility with older versions of the schema
36+
if (Bom.class.isAssignableFrom(beanDesc.getBeanClass())) {
37+
Iterator<BeanPropertyWriter> iterator = beanProperties.iterator();
38+
while (iterator.hasNext()) {
39+
BeanPropertyWriter writer = iterator.next();
40+
if (isValidAttribute(writer)) {
41+
if (shouldSerializeProperties(version)) {
42+
JsonSerializer<?> serializer = new PropertiesSerializer(isXml);
43+
writer.assignSerializer((JsonSerializer<Object>) serializer);
44+
}
45+
else {
46+
// Remove the properties field from the list of properties
47+
iterator.remove();
48+
}
49+
}
50+
}
51+
}
52+
return beanProperties;
53+
}
54+
55+
private boolean shouldSerializeProperties(Version version) {
56+
// Check the version and decide if properties should be serialized
57+
return (isXml && version.getVersion() >= Version.VERSION_13.getVersion())
58+
|| (!isXml && version.getVersion() >= Version.VERSION_15.getVersion());
59+
}
60+
61+
private boolean isValidAttribute(BeanPropertyWriter writer) {
62+
if (isXml) {
63+
return "properties".equals(writer.getWrapperName().getSimpleName());
64+
}
65+
else {
66+
return "properties".equals(writer.getName());
67+
}
68+
}
69+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package org.cyclonedx.util.serializer;
2+
3+
import java.io.IOException;
4+
5+
import com.fasterxml.jackson.core.JsonGenerator;
6+
import com.fasterxml.jackson.databind.SerializerProvider;
7+
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
8+
import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator;
9+
import org.cyclonedx.Version;
10+
import org.cyclonedx.model.Hash;
11+
import org.cyclonedx.model.Hash.Algorithm;
12+
import org.cyclonedx.model.VersionFilter;
13+
14+
public class HashSerializer
15+
extends StdSerializer<Hash>
16+
{
17+
private final Version version;
18+
19+
public HashSerializer(final Version version) {
20+
this(Hash.class, version);
21+
}
22+
23+
public HashSerializer(final Class<Hash> t, final Version version) {
24+
super(t);
25+
this.version = version;
26+
}
27+
28+
@Override
29+
public void serialize(
30+
final Hash hash, final JsonGenerator gen, final SerializerProvider provider) throws IOException
31+
{
32+
if (!shouldSerializeField(hash.getAlgorithm())) {
33+
return;
34+
}
35+
36+
if (gen instanceof ToXmlGenerator) {
37+
serializeXml((ToXmlGenerator) gen, hash);
38+
}
39+
else {
40+
serializeJson(gen, hash);
41+
}
42+
}
43+
44+
private void serializeXml(final ToXmlGenerator toXmlGenerator, final Hash hash) throws IOException {
45+
toXmlGenerator.writeStartObject();
46+
47+
toXmlGenerator.setNextIsAttribute(true);
48+
toXmlGenerator.writeFieldName("alg");
49+
toXmlGenerator.writeString(hash.getAlgorithm());
50+
toXmlGenerator.setNextIsAttribute(false);
51+
52+
toXmlGenerator.setNextIsUnwrapped(true);
53+
toXmlGenerator.writeStringField("", hash.getValue());
54+
55+
toXmlGenerator.writeEndObject();
56+
}
57+
58+
private void serializeJson(final JsonGenerator gen, final Hash hash)
59+
throws IOException
60+
{
61+
gen.writeStartObject();
62+
gen.writeStringField("alg", hash.getAlgorithm());
63+
gen.writeStringField("content", hash.getValue());
64+
gen.writeEndObject();
65+
}
66+
67+
@Override
68+
public Class<Hash> handledType() {
69+
return Hash.class;
70+
}
71+
72+
private boolean shouldSerializeField(String value) {
73+
try {
74+
Algorithm algorithm = Algorithm.fromSpec(value);
75+
VersionFilter filter = algorithm.getClass().getField(algorithm.name()).getAnnotation(VersionFilter.class);
76+
return filter == null || filter.value().getVersion() <= version.getVersion();
77+
}
78+
catch (NoSuchFieldException e) {
79+
return false;
80+
}
81+
}
82+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package org.cyclonedx.util.serializer;
2+
3+
import com.fasterxml.jackson.core.JsonGenerator;
4+
import com.fasterxml.jackson.databind.JsonSerializer;
5+
import com.fasterxml.jackson.databind.SerializerProvider;
6+
import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator;
7+
import org.apache.commons.collections4.CollectionUtils;
8+
import org.cyclonedx.model.Property;
9+
10+
import java.io.IOException;
11+
import java.util.List;
12+
13+
public class PropertiesSerializer
14+
extends JsonSerializer<List<Property>>
15+
{
16+
private boolean isXml;
17+
18+
public PropertiesSerializer(boolean isXml) {
19+
this.isXml = isXml;
20+
}
21+
22+
public PropertiesSerializer() {
23+
// Default constructor
24+
}
25+
26+
@Override
27+
public void serialize(List<Property> properties, JsonGenerator jsonGenerator, SerializerProvider serializers)
28+
throws IOException
29+
{
30+
if (CollectionUtils.isEmpty(properties)) {
31+
return; // Do not serialize if the list is null or empty
32+
}
33+
34+
if (isXml) {
35+
ToXmlGenerator xmlGenerator = (ToXmlGenerator) jsonGenerator;
36+
xmlGenerator.writeStartArray();
37+
for (Property property : properties) {
38+
xmlGenerator.writeStartObject("property");
39+
xmlGenerator.setNextIsAttribute(true);
40+
xmlGenerator.writeFieldName("name");
41+
xmlGenerator.writeString(property.getName());
42+
xmlGenerator.setNextIsAttribute(false);
43+
44+
xmlGenerator.setNextIsUnwrapped(true);
45+
xmlGenerator.writeStringField("", property.getValue());
46+
xmlGenerator.writeEndObject();
47+
}
48+
xmlGenerator.writeEndArray();
49+
}
50+
else {
51+
jsonGenerator.writeStartArray();
52+
for (Property property : properties) {
53+
jsonGenerator.writeStartObject();
54+
jsonGenerator.writeObjectField("name", property.getName());
55+
jsonGenerator.writeObjectField("value", property.getValue());
56+
jsonGenerator.writeEndObject();
57+
}
58+
jsonGenerator.writeEndArray();
59+
}
60+
}
61+
62+
@Override
63+
public Class<List<Property>> handledType() {
64+
return (Class<List<Property>>) (Class<?>) List.class;
65+
}
66+
}

0 commit comments

Comments
 (0)
Please sign in to comment.