From e828fa5086fcb097a66211f43177d9545441c845 Mon Sep 17 00:00:00 2001 From: Alex Alzate Date: Thu, 28 Mar 2024 09:26:34 -0500 Subject: [PATCH] Update License Expression to have ACK Signed-off-by: Alex Alzate --- pom.xml | 1 - .../generators/AbstractBomGenerator.java | 7 +- .../generators/json/BomJsonGenerator.java | 7 +- .../org/cyclonedx/model/LicenseChoice.java | 8 +- .../cyclonedx/model/license/Expression.java | 56 ++++++++++ .../org/cyclonedx/util/LicenseResolver.java | 9 +- .../deserializer/LicenseDeserializer.java | 4 +- .../serializer/LicenseChoiceSerializer.java | 103 ++++++++++++++++-- .../org/cyclonedx/parsers/JsonParserTest.java | 2 +- .../org/cyclonedx/parsers/XmlParserTest.java | 2 +- 10 files changed, 171 insertions(+), 28 deletions(-) create mode 100644 src/main/java/org/cyclonedx/model/license/Expression.java diff --git a/pom.xml b/pom.xml index 9dbda9771..9c83f69ce 100644 --- a/pom.xml +++ b/pom.xml @@ -168,7 +168,6 @@ 4.4 - diff --git a/src/main/java/org/cyclonedx/generators/AbstractBomGenerator.java b/src/main/java/org/cyclonedx/generators/AbstractBomGenerator.java index bd655f800..6b672111e 100644 --- a/src/main/java/org/cyclonedx/generators/AbstractBomGenerator.java +++ b/src/main/java/org/cyclonedx/generators/AbstractBomGenerator.java @@ -7,6 +7,7 @@ import org.cyclonedx.model.Bom; import org.cyclonedx.util.serializer.EvidenceSerializer; import org.cyclonedx.util.serializer.InputTypeSerializer; +import org.cyclonedx.util.serializer.LicenseChoiceSerializer; import org.cyclonedx.util.serializer.LifecycleSerializer; import org.cyclonedx.util.serializer.MetadataSerializer; import org.cyclonedx.util.serializer.OutputTypeSerializer; @@ -17,7 +18,7 @@ public abstract class AbstractBomGenerator extends CycloneDxSchema protected final Version version; - protected final Bom bom; + protected Bom bom; public AbstractBomGenerator(final Version version, final Bom bom) { this.mapper = new ObjectMapper(); @@ -34,6 +35,10 @@ public Version getSchemaVersion() { } protected void setupObjectMapper(boolean isXml) { + SimpleModule licenseModule = new SimpleModule(); + licenseModule.addSerializer(new LicenseChoiceSerializer()); + mapper.registerModule(licenseModule); + SimpleModule lifecycleModule = new SimpleModule(); lifecycleModule.addSerializer(new LifecycleSerializer(isXml)); mapper.registerModule(lifecycleModule); diff --git a/src/main/java/org/cyclonedx/generators/json/BomJsonGenerator.java b/src/main/java/org/cyclonedx/generators/json/BomJsonGenerator.java index 28e24d94f..a92c63ff3 100644 --- a/src/main/java/org/cyclonedx/generators/json/BomJsonGenerator.java +++ b/src/main/java/org/cyclonedx/generators/json/BomJsonGenerator.java @@ -35,7 +35,6 @@ import org.cyclonedx.util.mixin.MixInBomReference; import org.cyclonedx.util.serializer.ComponentWrapperSerializer; import org.cyclonedx.util.serializer.DependencySerializer; -import org.cyclonedx.util.serializer.LicenseChoiceSerializer; import org.cyclonedx.util.serializer.TrimStringSerializer; public class BomJsonGenerator extends AbstractBomGenerator @@ -54,7 +53,7 @@ public BomJsonGenerator(Bom bom, final Version version) { } catch (GeneratorException e) { } - bom = modifiedBom != null ? modifiedBom : bom; + this.bom = modifiedBom != null ? modifiedBom : bom; this.prettyPrinter = new DefaultPrettyPrinter(); setupPrettyPrinter(this.prettyPrinter); @@ -68,7 +67,6 @@ private void setupObjectMapper() { super.setupObjectMapper(false); - SimpleModule licenseModule = new SimpleModule(); SimpleModule depModule = new SimpleModule(); SimpleModule componentWrapperModule = new SimpleModule(); @@ -76,9 +74,6 @@ private void setupObjectMapper() { stringModule.addSerializer(new TrimStringSerializer()); mapper.registerModule(stringModule); - licenseModule.addSerializer(new LicenseChoiceSerializer()); - mapper.registerModule(licenseModule); - depModule.addSerializer(new DependencySerializer(false, null)); mapper.registerModule(depModule); diff --git a/src/main/java/org/cyclonedx/model/LicenseChoice.java b/src/main/java/org/cyclonedx/model/LicenseChoice.java index 5f60fc1b5..9bda4b551 100644 --- a/src/main/java/org/cyclonedx/model/LicenseChoice.java +++ b/src/main/java/org/cyclonedx/model/LicenseChoice.java @@ -26,6 +26,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import org.cyclonedx.model.license.Expression; import org.cyclonedx.util.deserializer.LicenseDeserializer; @JsonIgnoreProperties(ignoreUnknown = true) @@ -34,7 +35,7 @@ public class LicenseChoice { private List license; - private String expression; + private Expression expression; @JacksonXmlProperty(localName = "license") @JacksonXmlElementWrapper(useWrapping = false) @@ -55,11 +56,12 @@ public void addLicense(License license) { this.expression = null; } - public String getExpression() { + @JacksonXmlProperty(localName = "expression") + public Expression getExpression() { return expression; } - public void setExpression(String expression) { + public void setExpression(Expression expression) { this.expression = expression; this.license = null; } diff --git a/src/main/java/org/cyclonedx/model/license/Expression.java b/src/main/java/org/cyclonedx/model/license/Expression.java new file mode 100644 index 000000000..97b692dea --- /dev/null +++ b/src/main/java/org/cyclonedx/model/license/Expression.java @@ -0,0 +1,56 @@ +package org.cyclonedx.model.license; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.annotation.JsonRootName; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({"id", "acknowledgement", "bom-ref"}) +@JsonRootName("expression") +public class Expression +{ + @JacksonXmlProperty(isAttribute = true, localName = "bom-ref") + @JsonProperty("bom-ref") + private String bomRef; + @JacksonXmlProperty(isAttribute = true, localName = "acknowledgement") + @JsonProperty("acknowledgement") + private String acknowledgement; + + public Expression() { + + } + + public Expression(String id) { + this.id = id; + } + + private String id; + + public String getBomRef() { + return bomRef; + } + + public void setBomRef(final String bomRef) { + this.bomRef = bomRef; + } + + public String getAcknowledgement() { + return acknowledgement; + } + + public void setAcknowledgement(final String acknowledgement) { + this.acknowledgement = acknowledgement; + } + + public String getId() { + return id; + } + + public void setId(final String id) { + this.id = id; + } +} diff --git a/src/main/java/org/cyclonedx/util/LicenseResolver.java b/src/main/java/org/cyclonedx/util/LicenseResolver.java index 4bae960b9..1ff24437f 100644 --- a/src/main/java/org/cyclonedx/util/LicenseResolver.java +++ b/src/main/java/org/cyclonedx/util/LicenseResolver.java @@ -23,6 +23,8 @@ import org.cyclonedx.model.License; import org.cyclonedx.model.LicenseChoice; import org.cyclonedx.model.AttachmentText; +import org.cyclonedx.model.license.Expression; + import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; @@ -159,15 +161,16 @@ private static LicenseChoice resolveFuzzyMatching(final String licenseString, fi final SpdxLicenseMapping[] mappings = mapper.readValue(is, SpdxLicenseMapping[].class); if (mappings != null) { - for(final SpdxLicenseMapping licenseMapping : mappings) { + for (final SpdxLicenseMapping licenseMapping : mappings) { if (licenseMapping.names != null && !licenseMapping.names.isEmpty()) { for (final String name : licenseMapping.names) { if (licenseString.equalsIgnoreCase(name)) { if (licenseMapping.exp.startsWith("(") && licenseMapping.exp.endsWith(")")) { final LicenseChoice lc = new LicenseChoice(); - lc.setExpression(licenseMapping.exp); + lc.setExpression(new Expression(licenseMapping.exp)); return lc; - } else { + } + else { return createLicenseChoice(licenseMapping.exp, null, false, licenseTextSettings); } } diff --git a/src/main/java/org/cyclonedx/util/deserializer/LicenseDeserializer.java b/src/main/java/org/cyclonedx/util/deserializer/LicenseDeserializer.java index 5cdddeb66..5c15d8e0b 100644 --- a/src/main/java/org/cyclonedx/util/deserializer/LicenseDeserializer.java +++ b/src/main/java/org/cyclonedx/util/deserializer/LicenseDeserializer.java @@ -27,6 +27,7 @@ import com.fasterxml.jackson.databind.node.ArrayNode; import org.cyclonedx.model.License; import org.cyclonedx.model.LicenseChoice; +import org.cyclonedx.model.license.Expression; public class LicenseDeserializer extends JsonDeserializer { @@ -44,7 +45,8 @@ public LicenseChoice deserialize( processLicenseNode(p, node.get("license"), licenseChoice); } else if (node.has("expression")) { - licenseChoice.setExpression(node.get("expression").asText()); + Expression expressionNode = p.getCodec().treeToValue(node.get("expression"), Expression.class); + licenseChoice.setExpression(expressionNode); return licenseChoice; } } diff --git a/src/main/java/org/cyclonedx/util/serializer/LicenseChoiceSerializer.java b/src/main/java/org/cyclonedx/util/serializer/LicenseChoiceSerializer.java index 1d47be8d3..b2c543323 100644 --- a/src/main/java/org/cyclonedx/util/serializer/LicenseChoiceSerializer.java +++ b/src/main/java/org/cyclonedx/util/serializer/LicenseChoiceSerializer.java @@ -23,36 +23,117 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; import org.cyclonedx.model.License; import org.cyclonedx.model.LicenseChoice; +import org.cyclonedx.model.license.Expression; -public class LicenseChoiceSerializer extends StdSerializer +public class LicenseChoiceSerializer + extends StdSerializer { public LicenseChoiceSerializer() { this(LicenseChoice.class); } - public LicenseChoiceSerializer(final Class t) { + public LicenseChoiceSerializer(final Class t) { super(t); } @Override public void serialize( - final LicenseChoice lc, final JsonGenerator gen, final SerializerProvider provider) + final LicenseChoice licenseChoice, final JsonGenerator gen, final SerializerProvider provider) throws IOException { - gen.writeStartArray(); - if (lc != null && lc.getLicenses() != null && !lc.getLicenses().isEmpty()) { - for (License l : lc.getLicenses()) { - gen.writeStartObject(); - provider.defaultSerializeField("license", l, gen); - gen.writeEndObject(); + if (licenseChoice == null) { + return; + } + + if (gen instanceof ToXmlGenerator) { + serializeXml(licenseChoice, gen, provider); + } + else { + serializeJson(licenseChoice, gen, provider); + } + } + + private void serializeXml( + final LicenseChoice licenseChoice, final JsonGenerator gen, final SerializerProvider provider) + throws IOException + { + if (CollectionUtils.isNotEmpty(licenseChoice.getLicenses())) { + serializeLicensesToJsonArray(licenseChoice, gen, provider); + } else if (licenseChoice.getExpression() != null && StringUtils.isNotBlank(licenseChoice.getExpression().getId())) { + final ToXmlGenerator toXmlGenerator = (ToXmlGenerator) gen; + serializeExpressionToXml(licenseChoice, toXmlGenerator); } - } else if (lc != null && lc.getExpression() != null) { + } + + private void serializeJson( + final LicenseChoice licenseChoice, final JsonGenerator gen, final SerializerProvider provider) + throws IOException + { + if (CollectionUtils.isNotEmpty(licenseChoice.getLicenses())) { + serializeLicensesToJsonArray(licenseChoice, gen, provider); + } else if (licenseChoice.getExpression() != null && StringUtils.isNotBlank(licenseChoice.getExpression().getId())) { + serializeExpressionToJson(licenseChoice, gen); + } + } + + private void serializeLicensesToJsonArray( + final LicenseChoice licenseChoice, final JsonGenerator gen, final SerializerProvider provider) + throws IOException { + gen.writeStartArray(); + for (License license : licenseChoice.getLicenses()) { gen.writeStartObject(); - gen.writeStringField("expression", lc.getExpression()); + provider.defaultSerializeField("license", license, gen); gen.writeEndObject(); } gen.writeEndArray(); } + + private void serializeExpressionToXml( + final LicenseChoice licenseChoice, final ToXmlGenerator toXmlGenerator) + throws IOException { + toXmlGenerator.writeStartObject(); + Expression expression = licenseChoice.getExpression(); + toXmlGenerator.writeFieldName("expression"); + + toXmlGenerator.writeStartObject(); + + if (StringUtils.isNotBlank(expression.getBomRef())) { + toXmlGenerator.setNextIsAttribute(true); + toXmlGenerator.writeFieldName("bom-ref"); + toXmlGenerator.writeString(expression.getBomRef()); + toXmlGenerator.setNextIsAttribute(false); + } + if (StringUtils.isNotBlank(expression.getAcknowledgement())) { + toXmlGenerator.setNextIsAttribute(true); + toXmlGenerator.writeFieldName("acknowledgement"); + toXmlGenerator.writeString(expression.getAcknowledgement()); + toXmlGenerator.setNextIsAttribute(false); + } + + toXmlGenerator.setNextIsUnwrapped(true); + toXmlGenerator.writeStringField("", expression.getId()); + toXmlGenerator.writeEndObject(); + toXmlGenerator.writeEndObject(); + } + + private void serializeExpressionToJson(final LicenseChoice licenseChoice, final JsonGenerator gen) + throws IOException { + Expression expression = licenseChoice.getExpression(); + gen.writeStartArray(); + gen.writeStartObject(); + gen.writeStringField("expression", expression.getId()); + if (StringUtils.isNotBlank(expression.getAcknowledgement())) { + gen.writeStringField("acknowledgement", expression.getAcknowledgement()); + } + if (StringUtils.isNotBlank(expression.getBomRef())) { + gen.writeStringField("bom-ref", expression.getBomRef()); + } + gen.writeEndObject(); + gen.writeEndArray(); + } } diff --git a/src/test/java/org/cyclonedx/parsers/JsonParserTest.java b/src/test/java/org/cyclonedx/parsers/JsonParserTest.java index 98183b09b..ba544cb21 100644 --- a/src/test/java/org/cyclonedx/parsers/JsonParserTest.java +++ b/src/test/java/org/cyclonedx/parsers/JsonParserTest.java @@ -64,7 +64,7 @@ public void testParsedObjects12Bom() throws Exception { assertEquals("org.glassfish.hk2", c3.getGroup()); assertEquals("osgi-resource-locator", c3.getName()); assertEquals("1.0.1", c3.getVersion()); - assertEquals("(CDDL-1.0 OR GPL-2.0-with-classpath-exception)", c3.getLicenseChoice().getExpression()); + assertEquals("(CDDL-1.0 OR GPL-2.0-with-classpath-exception)", c3.getLicenseChoice().getExpression().getId()); assertServices(bom); diff --git a/src/test/java/org/cyclonedx/parsers/XmlParserTest.java b/src/test/java/org/cyclonedx/parsers/XmlParserTest.java index 8c4ec774a..b2b439ec6 100644 --- a/src/test/java/org/cyclonedx/parsers/XmlParserTest.java +++ b/src/test/java/org/cyclonedx/parsers/XmlParserTest.java @@ -190,7 +190,7 @@ public void testParsedObjects11Bom() throws Exception { assertNotNull(c1.getPedigree().getCommits().get(0).getCommitter().getTimestamp()); assertEquals("Initial commit", c1.getPedigree().getCommits().get(0).getMessage()); assertEquals("Commentary here", c1.getPedigree().getNotes()); - assertEquals("EPL-2.0 OR GPL-2.0-with-classpath-exception", c2.getLicenseChoice().getExpression()); + assertEquals("EPL-2.0 OR GPL-2.0-with-classpath-exception", c2.getLicenseChoice().getExpression().getId()); } @Test