diff --git a/src/main/java/org/cyclonedx/generators/AbstractBomGenerator.java b/src/main/java/org/cyclonedx/generators/AbstractBomGenerator.java index 056670d5f..025a91288 100644 --- a/src/main/java/org/cyclonedx/generators/AbstractBomGenerator.java +++ b/src/main/java/org/cyclonedx/generators/AbstractBomGenerator.java @@ -37,7 +37,7 @@ public Version getSchemaVersion() { protected void setupObjectMapper(boolean isXml) { SimpleModule licenseModule = new SimpleModule(); - licenseModule.addSerializer(new LicenseChoiceSerializer(isXml)); + licenseModule.addSerializer(new LicenseChoiceSerializer(isXml, version)); mapper.registerModule(licenseModule); SimpleModule lifecycleModule = new SimpleModule(); diff --git a/src/main/java/org/cyclonedx/model/License.java b/src/main/java/org/cyclonedx/model/License.java index ace179b03..ae1206fbd 100644 --- a/src/main/java/org/cyclonedx/model/License.java +++ b/src/main/java/org/cyclonedx/model/License.java @@ -50,6 +50,7 @@ public class License extends ExtensibleElement { @JacksonXmlProperty(isAttribute = true, localName = "acknowledgement") @JsonProperty("acknowledgement") + @VersionFilter(Version.VERSION_16) private String acknowledgement; @VersionFilter(Version.VERSION_15) diff --git a/src/main/java/org/cyclonedx/model/license/Expression.java b/src/main/java/org/cyclonedx/model/license/Expression.java index 18b510bd0..987fdf76f 100644 --- a/src/main/java/org/cyclonedx/model/license/Expression.java +++ b/src/main/java/org/cyclonedx/model/license/Expression.java @@ -8,6 +8,8 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText; +import org.cyclonedx.Version; +import org.cyclonedx.model.VersionFilter; import org.cyclonedx.util.deserializer.ExpressionDeserializer; @JsonIgnoreProperties(ignoreUnknown = true) @@ -18,9 +20,11 @@ public class Expression { @JacksonXmlProperty(isAttribute = true, localName = "bom-ref") @JsonProperty("bom-ref") + @VersionFilter(Version.VERSION_16) private String bomRef; @JacksonXmlProperty(isAttribute = true, localName = "acknowledgement") @JsonProperty("acknowledgement") + @VersionFilter(Version.VERSION_16) private String acknowledgement; @JacksonXmlText diff --git a/src/main/java/org/cyclonedx/util/deserializer/MetadataDeserializer.java b/src/main/java/org/cyclonedx/util/deserializer/MetadataDeserializer.java index e7b3b42c8..2e4a2deda 100644 --- a/src/main/java/org/cyclonedx/util/deserializer/MetadataDeserializer.java +++ b/src/main/java/org/cyclonedx/util/deserializer/MetadataDeserializer.java @@ -34,6 +34,8 @@ public class MetadataDeserializer private final PropertiesDeserializer propertiesDeserializer = new PropertiesDeserializer(); + private final LicenseDeserializer licenseDeserializer = new LicenseDeserializer(); + @Override public Metadata deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException { JsonNode node = jsonParser.getCodec().readTree(jsonParser); @@ -82,8 +84,10 @@ public Metadata deserialize(JsonParser jsonParser, DeserializationContext ctxt) } if(node.has("licenses")) { - LicenseChoice license = mapper.convertValue(node.get("licenses"), LicenseChoice.class); - metadata.setLicenseChoice(license); + JsonParser licensesParser = node.get("licenses").traverse(jsonParser.getCodec()); + licensesParser.nextToken(); + LicenseChoice licenses = licenseDeserializer.deserialize(licensesParser, ctxt); + metadata.setLicenses(licenses); } if (node.has("timestamp")) { diff --git a/src/main/java/org/cyclonedx/util/serializer/EvidenceSerializer.java b/src/main/java/org/cyclonedx/util/serializer/EvidenceSerializer.java index abd255115..314a2aab9 100644 --- a/src/main/java/org/cyclonedx/util/serializer/EvidenceSerializer.java +++ b/src/main/java/org/cyclonedx/util/serializer/EvidenceSerializer.java @@ -60,7 +60,7 @@ private void serializeJson(final JsonGenerator gen, final Evidence evidence, Ser if (evidence.getLicenses() != null) { gen.writeFieldName("licenses"); - new LicenseChoiceSerializer(isXml).serialize(evidence.getLicenses(), gen, serializerProvider); + new LicenseChoiceSerializer(isXml, version).serialize(evidence.getLicenses(), gen, serializerProvider); } if (CollectionUtils.isNotEmpty(evidence.getCopyright())) { diff --git a/src/main/java/org/cyclonedx/util/serializer/LicenseChoiceSerializer.java b/src/main/java/org/cyclonedx/util/serializer/LicenseChoiceSerializer.java index f222f7147..bba0d90e3 100644 --- a/src/main/java/org/cyclonedx/util/serializer/LicenseChoiceSerializer.java +++ b/src/main/java/org/cyclonedx/util/serializer/LicenseChoiceSerializer.java @@ -19,6 +19,7 @@ package org.cyclonedx.util.serializer; import java.io.IOException; +import java.lang.reflect.Field; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.SerializerProvider; @@ -26,24 +27,28 @@ import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; +import org.cyclonedx.Version; import org.cyclonedx.model.License; import org.cyclonedx.model.LicenseChoice; import org.cyclonedx.model.Property; +import org.cyclonedx.model.VersionFilter; import org.cyclonedx.model.license.Expression; public class LicenseChoiceSerializer extends StdSerializer { - private boolean isXml; + private final boolean isXml; - public LicenseChoiceSerializer(final boolean isXml) { - this(LicenseChoice.class, isXml); - this.isXml = isXml; + private final Version version; + + public LicenseChoiceSerializer(final boolean isXml, final Version version) { + this(LicenseChoice.class, isXml, version); } - public LicenseChoiceSerializer(final Class t, boolean isXml) { + public LicenseChoiceSerializer(final Class t, boolean isXml, final Version version) { super(t); this.isXml = isXml; + this.version = version; } @Override @@ -70,7 +75,7 @@ private void serializeXml(ToXmlGenerator toXmlGenerator, LicenseChoice lc) throw toXmlGenerator.writeFieldName("license"); toXmlGenerator.writeStartArray(); for (License l : lc.getLicenses()) { - serializeXmlAttributes(toXmlGenerator, l.getBomRef(), l.getAcknowledgement()); + serializeXmlAttributes(toXmlGenerator, l.getBomRef(), l.getAcknowledgement(), l); if (StringUtils.isNotBlank(l.getId())) { toXmlGenerator.writeStringField("id", l.getId()); @@ -79,7 +84,7 @@ else if (StringUtils.isNotBlank(l.getName())) { toXmlGenerator.writeStringField("name", l.getName()); } - if (l.getLicensing() != null) { + if (l.getLicensing() != null && shouldSerializeField(l, "licensing")) { toXmlGenerator.writeObjectField("licensing", l.getLicensing()); } @@ -91,7 +96,7 @@ else if (StringUtils.isNotBlank(l.getName())) { toXmlGenerator.writeStringField("url", l.getUrl()); } - if (CollectionUtils.isNotEmpty(l.getProperties())) { + if (CollectionUtils.isNotEmpty(l.getProperties()) && shouldSerializeField(l, "properties")) { toXmlGenerator.writeFieldName("properties"); toXmlGenerator.writeStartObject(); @@ -114,17 +119,18 @@ else if (lc.getExpression() != null) { private void serializeXmlAttributes( final ToXmlGenerator toXmlGenerator, final String bomRef, - final String acknowledgement) throws IOException + final String acknowledgement, + final Object object) throws IOException { toXmlGenerator.writeStartObject(); - if (StringUtils.isNotBlank(bomRef)) { + if (StringUtils.isNotBlank(bomRef) && shouldSerializeField(object, "bomRef")) { toXmlGenerator.setNextIsAttribute(true); toXmlGenerator.writeFieldName("bom-ref"); toXmlGenerator.writeString(bomRef); toXmlGenerator.setNextIsAttribute(false); } - if (StringUtils.isNotBlank(acknowledgement)) { + if (StringUtils.isNotBlank(acknowledgement) && shouldSerializeField(object, "acknowledgement")) { toXmlGenerator.setNextIsAttribute(true); toXmlGenerator.writeFieldName("acknowledgement"); toXmlGenerator.writeString(acknowledgement); @@ -152,7 +158,7 @@ private void serializeExpressionToXml( toXmlGenerator.writeStartObject(); Expression expression = licenseChoice.getExpression(); toXmlGenerator.writeFieldName("expression"); - serializeXmlAttributes(toXmlGenerator, expression.getBomRef(), expression.getAcknowledgement()); + serializeXmlAttributes(toXmlGenerator, expression.getBomRef(), expression.getAcknowledgement(), expression); toXmlGenerator.setNextIsUnwrapped(true); toXmlGenerator.writeStringField("", expression.getValue()); toXmlGenerator.writeEndObject(); @@ -178,13 +184,35 @@ private void serializeExpressionToJson(final LicenseChoice licenseChoice, final gen.writeStartArray(); gen.writeStartObject(); gen.writeStringField("expression", expression.getValue()); - if (StringUtils.isNotBlank(expression.getAcknowledgement())) { + if (StringUtils.isNotBlank(expression.getAcknowledgement()) && + shouldSerializeField(expression, "acknowledgement")) { gen.writeStringField("acknowledgement", expression.getAcknowledgement()); } - if (StringUtils.isNotBlank(expression.getBomRef())) { + if (StringUtils.isNotBlank(expression.getBomRef()) && shouldSerializeField(expression, "bomRef")) { gen.writeStringField("bom-ref", expression.getBomRef()); } gen.writeEndObject(); gen.writeEndArray(); } + + private boolean shouldSerialize(Object obj) { + for (Field field : obj.getClass().getDeclaredFields()) { + VersionFilter filter = field.getAnnotation(VersionFilter.class); + if (filter != null && filter.value().getVersion() > version.getVersion()) { + return false; + } + } + return true; + } + + private boolean shouldSerializeField(Object obj, String fieldName) { + try { + Field field = obj.getClass().getDeclaredField(fieldName); + VersionFilter filter = field.getAnnotation(VersionFilter.class); + return filter == null || filter.value().getVersion() <= version.getVersion(); + } catch (NoSuchFieldException e) { + // If the field does not exist, assume it should be serialized + return true; + } + } } diff --git a/src/main/java/org/cyclonedx/util/serializer/MetadataSerializer.java b/src/main/java/org/cyclonedx/util/serializer/MetadataSerializer.java index 4fc599154..c0c27033d 100644 --- a/src/main/java/org/cyclonedx/util/serializer/MetadataSerializer.java +++ b/src/main/java/org/cyclonedx/util/serializer/MetadataSerializer.java @@ -86,7 +86,7 @@ private void createMetadataInfo(final Metadata metadata, final JsonGenerator jso if(metadata.getLicenses() != null) { jsonGenerator.writeFieldName("licenses"); - new LicenseChoiceSerializer(isXml).serialize(metadata.getLicenses(), jsonGenerator, serializerProvider); + new LicenseChoiceSerializer(isXml, version).serialize(metadata.getLicenses(), jsonGenerator, serializerProvider); } if(metadata.getProperties()!=null) { diff --git a/src/test/java/org/cyclonedx/BomJsonGeneratorTest.java b/src/test/java/org/cyclonedx/BomJsonGeneratorTest.java index 938b52a3d..2d316e5e9 100644 --- a/src/test/java/org/cyclonedx/BomJsonGeneratorTest.java +++ b/src/test/java/org/cyclonedx/BomJsonGeneratorTest.java @@ -194,6 +194,30 @@ public void testIssue408Regression_1_5() throws Exception { assertTrue(parser.isValid(loadedFile, version)); } + @Test + public void testIssue408Regression_16To15() throws Exception { + Version version = Version.VERSION_15; + Bom bom = createCommonJsonBom("/regression/issue408.json"); + + BomXmlGenerator generator = BomGeneratorFactory.createXml(version, bom); + File loadedFile = writeToFile(generator.toXmlString()); + + XmlParser parser = new XmlParser(); + assertTrue(parser.isValid(loadedFile, version)); + } + + @Test + public void testIssue408Regression_16To14() throws Exception { + Version version = Version.VERSION_14; + Bom bom = createCommonJsonBom("/regression/issue408.json"); + + BomXmlGenerator generator = BomGeneratorFactory.createXml(version, bom); + File loadedFile = writeToFile(generator.toXmlString()); + + XmlParser parser = new XmlParser(); + assertTrue(parser.isValid(loadedFile, version)); + } + @Test public void testIssue408Regression() throws Exception { Version version = Version.VERSION_16; diff --git a/src/test/java/org/cyclonedx/BomXmlGeneratorTest.java b/src/test/java/org/cyclonedx/BomXmlGeneratorTest.java index 12b643644..e30eb29f0 100644 --- a/src/test/java/org/cyclonedx/BomXmlGeneratorTest.java +++ b/src/test/java/org/cyclonedx/BomXmlGeneratorTest.java @@ -318,6 +318,30 @@ public void testIssue408Regression_1_5() throws Exception { assertTrue(parser.isValid(loadedFile, version)); } + @Test + public void testIssue408Regression_16To15() throws Exception { + Version version = Version.VERSION_15; + Bom bom = createCommonBomXml("/regression/issue408.xml"); + + BomXmlGenerator generator = BomGeneratorFactory.createXml(version, bom); + File loadedFile = writeToFile(generator.toXmlString()); + + XmlParser parser = new XmlParser(); + assertTrue(parser.isValid(loadedFile, version)); + } + + @Test + public void testIssue408Regression_16To14() throws Exception { + Version version = Version.VERSION_14; + Bom bom = createCommonBomXml("/regression/issue408.xml"); + + BomXmlGenerator generator = BomGeneratorFactory.createXml(version, bom); + File loadedFile = writeToFile(generator.toXmlString()); + + XmlParser parser = new XmlParser(); + assertTrue(parser.isValid(loadedFile, version)); + } + @Test public void testIssue408Regression() throws Exception { Version version = Version.VERSION_16; diff --git a/src/test/resources/regression/issue408.json b/src/test/resources/regression/issue408.json index c75ce3f1f..368590ef4 100644 --- a/src/test/resources/regression/issue408.json +++ b/src/test/resources/regression/issue408.json @@ -9,7 +9,11 @@ "id": "MIT" }, { - "name": "MIT" + "name": "MIT-test", + "properties": { + "name": "name", + "value": "value1" + } } ] }, diff --git a/src/test/resources/regression/issue408.xml b/src/test/resources/regression/issue408.xml index ad00569c5..b75b1d93c 100644 --- a/src/test/resources/regression/issue408.xml +++ b/src/test/resources/regression/issue408.xml @@ -6,7 +6,10 @@ MIT - MIT + MIT-test + + value1 +