Skip to content

Commit 570dfb5

Browse files
committed
Implement hierarchical merge utility
Signed-off-by: Richard DiCroce <[email protected]>
1 parent 52819d4 commit 570dfb5

File tree

7 files changed

+534
-0
lines changed

7 files changed

+534
-0
lines changed

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,9 @@ target
33
.idea/
44
dependency-reduced-pom.xml
55
.DS_Store
6+
/bin/
7+
8+
# Eclipse
9+
.settings/
10+
.classpath
11+
.project

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,12 +183,26 @@ public void setCompositions(List<Composition> compositions) {
183183
this.compositions = compositions;
184184
}
185185

186+
public void addComposition(Composition composition) {
187+
if (compositions == null) {
188+
compositions = new ArrayList<>();
189+
}
190+
compositions.add(composition);
191+
}
192+
186193
@JacksonXmlElementWrapper(localName = "vulnerabilities")
187194
@JacksonXmlProperty(localName = "vulnerability")
188195
public List<Vulnerability> getVulnerabilities() { return vulnerabilities; }
189196

190197
public void setVulnerabilities(List<Vulnerability> vulnerabilities) { this.vulnerabilities = vulnerabilities; }
191198

199+
public void addVulnerability(Vulnerability vulnerability) {
200+
if (vulnerabilities == null) {
201+
vulnerabilities = new ArrayList<>();
202+
}
203+
vulnerabilities.add(vulnerability);
204+
}
205+
192206
@JacksonXmlElementWrapper(localName = "properties")
193207
@JacksonXmlProperty(localName = "property")
194208
public List<Property> getProperties() {

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ public String getRef() {
4141
return ref;
4242
}
4343

44+
public void setRef(String ref) {
45+
this.ref = ref;
46+
}
47+
4448
@Override
4549
public boolean equals(Object o) {
4650
if (this == o) return true;
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
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;
20+
21+
import java.util.ArrayDeque;
22+
import java.util.ArrayList;
23+
import java.util.Deque;
24+
import java.util.List;
25+
26+
import org.apache.commons.lang3.StringUtils;
27+
import org.cyclonedx.model.Bom;
28+
import org.cyclonedx.model.BomReference;
29+
import org.cyclonedx.model.Component;
30+
import org.cyclonedx.model.Composition;
31+
import org.cyclonedx.model.Dependency;
32+
import org.cyclonedx.model.ExternalReference;
33+
import org.cyclonedx.model.Metadata;
34+
import org.cyclonedx.model.Service;
35+
import org.cyclonedx.model.Tool;
36+
import org.cyclonedx.model.vulnerability.Vulnerability;
37+
import org.cyclonedx.model.vulnerability.Vulnerability.Affect;
38+
39+
public class BomMerger {
40+
41+
public Bom hierarchicalMerge(Iterable<Bom> boms, Component bomSubject) {
42+
Bom result = new Bom();
43+
Metadata resultMetadata = new Metadata();
44+
result.setMetadata(resultMetadata);
45+
46+
List<Dependency> bomSubjectDependencies = new ArrayList<>();
47+
48+
if (bomSubject != null) {
49+
resultMetadata.setComponent(bomSubject);
50+
if (bomSubject.getBomRef() == null) {
51+
bomSubject.setBomRef(componentBomRefNamespace(bomSubject));
52+
}
53+
54+
Dependency topLevelDependency = new Dependency(bomSubject.getBomRef());
55+
topLevelDependency.setDependencies(bomSubjectDependencies);
56+
result.addDependency(topLevelDependency);
57+
}
58+
59+
for (Bom bom : boms) {
60+
Metadata bomMetadata = bom.getMetadata();
61+
Component bomComponent = bomMetadata == null ? null : bomMetadata.getComponent();
62+
if (bomComponent == null) {
63+
throw new IllegalArgumentException("Required metadata (top level) component is missing from BOM.");
64+
}
65+
66+
List<Tool> bomTools = bomMetadata.getTools();
67+
if (bomTools != null) {
68+
bomTools.forEach(resultMetadata::addTool);
69+
}
70+
71+
List<Component> bomComponents = bom.getComponents();
72+
if (bomComponents != null) {
73+
bomComponents.forEach(bomComponent::addComponent);
74+
}
75+
76+
String bomRefNamespace = componentBomRefNamespace(bomComponent);
77+
78+
namespaceComponentBomRefs(bomRefNamespace, bomComponent);
79+
80+
if (bomComponent.getBomRef() == null) {
81+
bomComponent.setBomRef(bomRefNamespace);
82+
}
83+
84+
bomSubjectDependencies.add(new Dependency(bomComponent.getBomRef()));
85+
86+
result.addComponent(bomComponent);
87+
88+
List<Service> bomServices = bom.getServices();
89+
if (bomServices != null) {
90+
for (Service service : bomServices) {
91+
service.setBomRef(namespacedBomRef(bomRefNamespace, service.getBomRef()));
92+
result.addService(service);
93+
}
94+
}
95+
96+
List<ExternalReference> bomExternalRefs = bom.getExternalReferences();
97+
if (bomExternalRefs != null) {
98+
bomExternalRefs.forEach(result::addExternalReference);
99+
}
100+
101+
List<Dependency> bomDependencies = bom.getDependencies();
102+
if (bomDependencies != null) {
103+
namespaceDependencyBomRefs(bomRefNamespace, bomDependencies);
104+
bomDependencies.forEach(result::addDependency);
105+
}
106+
107+
List<Composition> bomCompositions = bom.getCompositions();
108+
if (bomCompositions != null) {
109+
namespaceCompositions(bomRefNamespace, bomCompositions);
110+
bomCompositions.forEach(result::addComposition);
111+
}
112+
113+
List<Vulnerability> bomVulnerabilities = bom.getVulnerabilities();
114+
if (bomVulnerabilities != null) {
115+
namespaceVulnerabilityRefs(bomSubject.getBomRef(), bomVulnerabilities);
116+
bomVulnerabilities.forEach(result::addVulnerability);
117+
}
118+
}
119+
120+
return result;
121+
}
122+
123+
private String componentBomRefNamespace(Component component) {
124+
StringBuilder builder = new StringBuilder(256);
125+
String group = component.getGroup();
126+
if (group != null) {
127+
builder.append(group).append('.');
128+
}
129+
builder.append(component.getName()).append('@').append(component.getVersion());
130+
return builder.toString();
131+
}
132+
133+
private String namespacedBomRef(String bomRefNamespace, String bomRef) {
134+
return StringUtils.isEmpty(bomRef) ? null : bomRefNamespace + ":" + bomRef;
135+
}
136+
137+
private void namespaceBomRefs(String bomRefNamespace, List<BomReference> bomRefs) {
138+
if (bomRefs != null) {
139+
for (BomReference bomRef : bomRefs) {
140+
bomRef.setRef(namespacedBomRef(bomRefNamespace, bomRef.getRef()));
141+
}
142+
}
143+
}
144+
145+
private void namespaceComponentBomRefs(String bomRefNamespace, Component topComponent) {
146+
Deque<Component> components = new ArrayDeque<>();
147+
components.push(topComponent);
148+
149+
while (!components.isEmpty()) {
150+
Component currentComponent = components.pop();
151+
currentComponent.setBomRef(namespacedBomRef(bomRefNamespace, currentComponent.getBomRef()));
152+
153+
List<Component> subComponents = currentComponent.getComponents();
154+
if (subComponents != null) {
155+
subComponents.forEach(components::push);
156+
}
157+
}
158+
}
159+
160+
private void namespaceDependencyBomRefs(String bomRefNamespace, List<Dependency> dependencies) {
161+
Deque<Dependency> pendingDependencies = new ArrayDeque<>(dependencies);
162+
while (!pendingDependencies.isEmpty()) {
163+
Dependency dependency = pendingDependencies.pop();
164+
dependency.setRef(namespacedBomRef(bomRefNamespace, dependency.getRef()));
165+
166+
List<Dependency> subDependencies = dependency.getDependencies();
167+
if (subDependencies != null) {
168+
subDependencies.forEach(pendingDependencies::push);
169+
}
170+
}
171+
}
172+
173+
private void namespaceCompositions(String bomRefNamespace, List<Composition> compositions) {
174+
for (Composition composition : compositions) {
175+
namespaceBomRefs(bomRefNamespace, composition.getAssemblies());
176+
namespaceBomRefs(bomRefNamespace, composition.getDependencies());
177+
}
178+
}
179+
180+
private void namespaceVulnerabilityRefs(String bomRefNamespace, List<Vulnerability> vulnerabilities) {
181+
for (Vulnerability vulnerability : vulnerabilities) {
182+
vulnerability.setBomRef(namespacedBomRef(bomRefNamespace, vulnerability.getBomRef()));
183+
184+
List<Affect> affects = vulnerability.getAffects();
185+
if (affects != null) {
186+
for (Affect affect : affects) {
187+
affect.setRef(bomRefNamespace);
188+
}
189+
}
190+
}
191+
}
192+
193+
}

0 commit comments

Comments
 (0)