Skip to content

Commit 7fceac4

Browse files
authored
feat: Add missing provides mapping to Dependency to comply with CycloneDX 1.6 spec (#599)
* feat: Add missing provides mapping to Dependency to comply with CycloneDX 1.6 spec Signed-off-by: Andrea Vibelli <avibelli@redhat.com> * Fixed mapping to have a list of BomReference for provides; added tests Signed-off-by: Andrea Vibelli <avibelli@redhat.com> * fix: correct XML serialization of provides elements in DependencySerializer - Replace broken writeXMLProvides (used writeFieldName+writeStartArray causing 'Can not write a field name, expecting a value') with the same processNamespace pattern used for sub-dependency elements - Extend the writeStartArray/writeEndArray guard in writeXMLDependency to cover provides children, not only sub-dependency children; without this, a dependency with only provides (no sub-deps) would fail with 'Can not start an object, expecting field name' - Replace CollectionUtils.isNotEmpty() calls (removed upstream dependency) with inline null+isEmpty checks Signed-off-by: Andrea Vibelli <avibelli@redhat.com> --------- Signed-off-by: Andrea Vibelli <avibelli@redhat.com>
1 parent cf5d168 commit 7fceac4

10 files changed

Lines changed: 317 additions & 7 deletions

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919
package org.cyclonedx.model;
2020

21+
import org.cyclonedx.Version;
2122
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
2223
import com.fasterxml.jackson.annotation.JsonInclude;
2324
import com.fasterxml.jackson.annotation.JsonProperty;
@@ -36,6 +37,12 @@ public class Dependency extends BomReference {
3637
@JacksonXmlProperty(localName = "dependency")
3738
private List<Dependency> dependencies;
3839

40+
@VersionFilter(Version.VERSION_16)
41+
@JsonProperty("provides")
42+
@JacksonXmlElementWrapper(useWrapping = false)
43+
@JacksonXmlProperty(localName = "provides")
44+
private List<BomReference> provides;
45+
3946
public Dependency(final String ref) {
4047
super(ref);
4148
}
@@ -60,6 +67,23 @@ public void addDependency(final Dependency dependency) {
6067
}
6168
}
6269

70+
public List<BomReference> getProvides() {
71+
return provides;
72+
}
73+
74+
public void setProvides(final List<BomReference> provides) {
75+
this.provides = provides;
76+
}
77+
78+
public void addProvides(final BomReference dependency) {
79+
if (provides == null) {
80+
provides = new ArrayList<>();
81+
}
82+
boolean found = provides.stream().anyMatch(d -> d.getRef().equals(dependency.getRef()));
83+
if (!found) {
84+
provides.add(dependency);
85+
}
86+
}
6387
@Override
6488
public boolean equals(Object o) {
6589
if (this == o) return true;

‎src/main/java/org/cyclonedx/util/serializer/DependencySerializer.java‎

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
2727
import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator;
2828
import org.cyclonedx.CycloneDxSchema;
29+
import org.cyclonedx.model.BomReference;
2930
import org.cyclonedx.model.Dependency;
3031
import org.cyclonedx.model.DependencyList;
3132

@@ -94,6 +95,13 @@ private void writeJSONDependenciesWithGenerator(final JsonGenerator generator, f
9495
}
9596
}
9697
generator.writeEndArray();
98+
if (dependency.getProvides() != null && !dependency.getProvides().isEmpty()) {
99+
generator.writeArrayFieldStart("provides");
100+
for (BomReference subDependency : dependency.getProvides()) {
101+
generator.writeString(subDependency.getRef());
102+
}
103+
generator.writeEndArray();
104+
}
97105
generator.writeEndObject();
98106
}
99107
}
@@ -122,7 +130,10 @@ private void writeXMLDependency(final Dependency dependency, final ToXmlGenerato
122130
{
123131
processNamespace(generator, "dependency");
124132

125-
if (dependency.getDependencies() != null && !dependency.getDependencies().isEmpty()) {
133+
boolean hasChildren = (dependency.getDependencies() != null && !dependency.getDependencies().isEmpty())
134+
|| (dependency.getProvides() != null && !dependency.getProvides().isEmpty());
135+
136+
if (hasChildren) {
126137
generator.writeStartArray();
127138
}
128139

@@ -131,20 +142,38 @@ private void writeXMLDependency(final Dependency dependency, final ToXmlGenerato
131142
generator.writeString(dependency.getRef());
132143
generator.setNextIsAttribute(false);
133144

145+
// Write provides
146+
if (dependency.getProvides() != null && !dependency.getProvides().isEmpty()) {
147+
writeXMLProvides(dependency, generator);
148+
}
149+
134150
if (dependency.getDependencies() != null && !dependency.getDependencies().isEmpty()) {
135151
for (Dependency subDependency : dependency.getDependencies()) {
136152
// You got Shay'd
137153
writeXMLDependency(subDependency, generator);
138154
}
139155
}
140156

141-
if (dependency.getDependencies() != null && !dependency.getDependencies().isEmpty()) {
142-
generator.writeEndArray();
143-
}
157+
if (hasChildren) {
158+
generator.writeEndArray();
159+
}
144160

145161
generator.writeEndObject();
146162
}
147163

164+
private void writeXMLProvides(final Dependency dependency, final ToXmlGenerator generator)
165+
throws IOException, XMLStreamException
166+
{
167+
for (BomReference ref : dependency.getProvides()) {
168+
processNamespace(generator, "provides");
169+
generator.setNextIsAttribute(true);
170+
generator.setNextName(new QName(REF));
171+
generator.writeString(ref.getRef());
172+
generator.setNextIsAttribute(false);
173+
generator.writeEndObject();
174+
}
175+
}
176+
148177
private void processNamespace(final ToXmlGenerator toXmlGenerator, final String dependencies)
149178
throws XMLStreamException, IOException
150179
{

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

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.cyclonedx.generators.xml.BomXmlGenerator;
2525
import org.cyclonedx.model.Bom;
2626
import org.cyclonedx.model.Component;
27+
import org.cyclonedx.model.Dependency;
2728
import org.cyclonedx.model.Component.Type;
2829
import org.cyclonedx.model.License;
2930
import org.cyclonedx.model.LicenseChoice;
@@ -336,6 +337,72 @@ public void schema16_testAttestations_json() throws Exception {
336337
assertTrue(parser.isValid(loadedFile, version));
337338
}
338339

340+
@Test
341+
public void schema16_testDependencyProvides_json() throws Exception {
342+
Version version = Version.VERSION_16;
343+
Bom bom = createCommonJsonBom("/1.6/valid-dependency-provides-1.6.json");
344+
345+
BomJsonGenerator generator = BomGeneratorFactory.createJson(version, bom);
346+
File loadedFile = writeToFile(generator.toJsonString());
347+
348+
JsonParser parser = new JsonParser();
349+
assertTrue(parser.isValid(loadedFile, version));
350+
351+
Bom parsedBom = parser.parse(loadedFile);
352+
assertNotNull(parsedBom.getDependencies());
353+
assertEquals(3, parsedBom.getDependencies().size());
354+
// Test dependency library-a
355+
Dependency libA = parsedBom.getDependencies().get(0);
356+
assertEquals("library-a", libA.getRef());
357+
assertNotNull(libA.getDependencies());
358+
assertEquals(0, libA.getDependencies().size());
359+
assertNull(libA.getProvides());
360+
// Test dependency library-b
361+
Dependency libB = parsedBom.getDependencies().get(1);
362+
assertEquals("library-b", libB.getRef());
363+
assertEquals(1, libB.getDependencies().size());
364+
assertEquals("library-c", libB.getDependencies().get(0).getRef());
365+
// Test dependency library-c
366+
Dependency libC = parsedBom.getDependencies().get(2);
367+
assertEquals("library-c", libC.getRef());
368+
assertNotNull(libC.getDependencies());
369+
assertNotNull(libC.getProvides());
370+
assertEquals("library-d", libC.getProvides().get(0).getRef());
371+
}
372+
373+
@Test
374+
public void schema16_testDependencyProvides() throws Exception {
375+
Version version = Version.VERSION_16;
376+
Bom bom = createCommonXmlBom("/1.6/valid-dependency-provides-1.6.xml");
377+
378+
BomJsonGenerator generator = BomGeneratorFactory.createJson(version, bom);
379+
File loadedFile = writeToFile(generator.toJsonString());
380+
381+
JsonParser parser = new JsonParser();
382+
assertTrue(parser.isValid(loadedFile, version));
383+
384+
Bom parsedBom = parser.parse(loadedFile);
385+
assertNotNull(parsedBom.getDependencies());
386+
assertEquals(3, parsedBom.getDependencies().size());
387+
// Test dependency library-a
388+
Dependency libA = parsedBom.getDependencies().get(0);
389+
assertEquals("library-a", libA.getRef());
390+
assertNotNull(libA.getDependencies());
391+
assertEquals(0, libA.getDependencies().size());
392+
assertNull(libA.getProvides());
393+
// Test dependency library-b
394+
Dependency libB = parsedBom.getDependencies().get(1);
395+
assertEquals("library-b", libB.getRef());
396+
assertEquals(1, libB.getDependencies().size());
397+
assertEquals("library-c", libB.getDependencies().get(0).getRef());
398+
// Test dependency library-c
399+
Dependency libC = parsedBom.getDependencies().get(2);
400+
assertEquals("library-c", libC.getRef());
401+
assertNotNull(libC.getDependencies());
402+
assertNotNull(libC.getProvides());
403+
assertEquals("library-d", libC.getProvides().get(0).getRef());
404+
}
405+
339406
@Test
340407
public void schema16_testCompositions() throws Exception {
341408
Version version = Version.VERSION_16;

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

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.cyclonedx.model.Bom;
2626
import org.cyclonedx.model.Component;
2727
import org.cyclonedx.model.Component.Type;
28+
import org.cyclonedx.model.Dependency;
2829
import org.cyclonedx.model.ExtensibleType;
2930
import org.cyclonedx.model.ExternalReference;
3031
import org.cyclonedx.model.License;
@@ -540,6 +541,70 @@ public void schema16_testAttestations_xml() throws Exception {
540541
assertTrue(parser.isValid(loadedFile, version));
541542
}
542543

544+
@Test
545+
public void schema16_testDependencyProvides() throws Exception {
546+
Version version = Version.VERSION_16;
547+
Bom bom = createCommonJsonBom("/1.6/valid-dependency-provides-1.6.json");
548+
549+
BomXmlGenerator generator = BomGeneratorFactory.createXml(version, bom);
550+
File loadedFile = writeToFile(generator.toXmlString());
551+
552+
XmlParser parser = new XmlParser();
553+
assertTrue(parser.isValid(loadedFile, version));
554+
555+
Bom parsedBom = parser.parse(loadedFile);
556+
assertNotNull(parsedBom.getDependencies());
557+
assertEquals(3, parsedBom.getDependencies().size());
558+
// Test dependency library-a
559+
Dependency libA = parsedBom.getDependencies().get(0);
560+
assertEquals("library-a", libA.getRef());
561+
assertNull(libA.getDependencies());
562+
assertNull(libA.getProvides());
563+
// Test dependency library-b
564+
Dependency libB = parsedBom.getDependencies().get(1);
565+
assertEquals("library-b", libB.getRef());
566+
assertEquals(1, libB.getDependencies().size());
567+
assertEquals("library-c", libB.getDependencies().get(0).getRef());
568+
// Test dependency library-c
569+
Dependency libC = parsedBom.getDependencies().get(2);
570+
assertEquals("library-c", libC.getRef());
571+
assertNull(libC.getDependencies());
572+
assertNotNull(libC.getProvides());
573+
assertEquals("library-d", libC.getProvides().get(0).getRef());
574+
}
575+
576+
@Test
577+
public void schema16_testDependencyProvides_xml() throws Exception {
578+
Version version = Version.VERSION_16;
579+
Bom bom = createCommonBomXml("/1.6/valid-dependency-provides-1.6.xml");
580+
581+
BomXmlGenerator generator = BomGeneratorFactory.createXml(version, bom);
582+
File loadedFile = writeToFile(generator.toXmlString());
583+
584+
XmlParser parser = new XmlParser();
585+
assertTrue(parser.isValid(loadedFile, version));
586+
587+
Bom parsedBom = parser.parse(loadedFile);
588+
assertNotNull(parsedBom.getDependencies());
589+
assertEquals(3, parsedBom.getDependencies().size());
590+
// Test dependency library-a
591+
Dependency libA = parsedBom.getDependencies().get(0);
592+
assertEquals("library-a", libA.getRef());
593+
assertNull(libA.getDependencies());
594+
assertNull(libA.getProvides());
595+
// Test dependency library-b
596+
Dependency libB = parsedBom.getDependencies().get(1);
597+
assertEquals("library-b", libB.getRef());
598+
assertEquals(1, libB.getDependencies().size());
599+
assertEquals("library-c", libB.getDependencies().get(0).getRef());
600+
// Test dependency library-c
601+
Dependency libC = parsedBom.getDependencies().get(2);
602+
assertEquals("library-c", libC.getRef());
603+
assertNull(libC.getDependencies());
604+
assertNotNull(libC.getProvides());
605+
assertEquals("library-d", libC.getProvides().get(0).getRef());
606+
}
607+
543608
private void addSignature(Bom bom) {
544609
List<Attribute> attributes = new ArrayList<>();
545610
attributes.add(new Attribute("xmlns", "http://www.w3.org/2000/09/xmldsig#"));

‎src/test/resources/1.6/valid-dependency-1.6.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,4 @@
3636
]
3737
}
3838
]
39-
}
39+
}

‎src/test/resources/1.6/valid-dependency-1.6.textproto‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,4 @@ dependencies {
3030
dependencies {
3131
ref: "library-c"
3232
}
33-
}
33+
}

‎src/test/resources/1.6/valid-dependency-1.6.xml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,4 @@
2020
<dependency ref="library-c"/>
2121
</dependency>
2222
</dependencies>
23-
</bom>
23+
</bom>
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
{
2+
"$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json",
3+
"bomFormat": "CycloneDX",
4+
"specVersion": "1.6",
5+
"serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79",
6+
"version": 1,
7+
"components": [
8+
{
9+
"bom-ref": "library-a",
10+
"type": "library",
11+
"name": "library-a",
12+
"version": "1.0.0"
13+
},
14+
{
15+
"bom-ref": "library-b",
16+
"type": "library",
17+
"name": "library-b",
18+
"version": "1.0.0"
19+
},
20+
{
21+
"bom-ref": "library-c",
22+
"type": "library",
23+
"name": "library-c",
24+
"version": "1.0.0"
25+
},
26+
{
27+
"bom-ref": "library-d",
28+
"type": "library",
29+
"name": "library-d",
30+
"version": "1.0.0"
31+
}
32+
],
33+
"dependencies": [
34+
{
35+
"ref": "library-a",
36+
"dependsOn": []
37+
},
38+
{
39+
"ref": "library-b",
40+
"dependsOn": [
41+
"library-c"
42+
]
43+
},
44+
{
45+
"ref": "library-c",
46+
"provides": [
47+
"library-d"
48+
]
49+
}
50+
]
51+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# proto-file: schema/bom-1.6.proto
2+
# proto-message: Bom
3+
4+
spec_version: "1.6"
5+
version: 1
6+
serial_number: "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"
7+
components {
8+
type: CLASSIFICATION_LIBRARY
9+
bom_ref: "library-a"
10+
name: "library-a"
11+
version: "1.0.0"
12+
}
13+
components {
14+
type: CLASSIFICATION_LIBRARY
15+
bom_ref: "library-b"
16+
name: "library-b"
17+
version: "1.0.0"
18+
}
19+
components {
20+
type: CLASSIFICATION_LIBRARY
21+
bom_ref: "library-c"
22+
name: "library-c"
23+
version: "1.0.0"
24+
}
25+
components {
26+
type: CLASSIFICATION_LIBRARY
27+
bom_ref: "library-d"
28+
name: "library-d"
29+
version: "1.0.0"
30+
}
31+
dependencies {
32+
ref: "library-a"
33+
}
34+
dependencies {
35+
ref: "library-b"
36+
dependencies {
37+
ref: "library-c"
38+
}
39+
}
40+
dependencies {
41+
ref: "library-c"
42+
provides: ["library-d"]
43+
}
44+
}

0 commit comments

Comments
 (0)