Skip to content

Commit 5bcd2b3

Browse files
authored
fix: spdx relationships (#179)
Signed-off-by: Ruben Romero Montes <[email protected]>
1 parent 84820c9 commit 5bcd2b3

File tree

6 files changed

+8895
-102
lines changed

6 files changed

+8895
-102
lines changed

src/main/java/com/redhat/exhort/integration/backend/ExhortIntegration.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.util.Arrays;
2727

2828
import org.apache.camel.Exchange;
29+
import org.apache.camel.LoggingLevel;
2930
import org.apache.camel.Message;
3031
import org.apache.camel.builder.AggregationStrategies;
3132
import org.apache.camel.builder.endpoint.EndpointRouteBuilder;
@@ -51,6 +52,7 @@
5152
import jakarta.ws.rs.ClientErrorException;
5253
import jakarta.ws.rs.core.MediaType;
5354
import jakarta.ws.rs.core.Response;
55+
import jakarta.ws.rs.core.Response.Status;
5456

5557
@ApplicationScoped
5658
public class ExhortIntegration extends EndpointRouteBuilder {
@@ -78,7 +80,7 @@ public void configure() {
7880
restConfiguration().contextPath("/api/")
7981
.clientRequestValidation(true);
8082

81-
errorHandler(deadLetterChannel("seda:processFailedRequests"));
83+
errorHandler(deadLetterChannel("direct:processInternalError"));
8284

8385
onException(IllegalArgumentException.class)
8486
.routeId("onExhortIllegalArgumentException")
@@ -174,7 +176,17 @@ public void configure() {
174176
.process(analytics::trackAnalysis);
175177

176178
from(seda("processFailedRequests"))
179+
.routeId("processFailedRequests")
177180
.process(monitoringProcessor::processServerError);
181+
182+
from(direct("processInternalError"))
183+
.routeId("processInternalError")
184+
.log(LoggingLevel.ERROR, "${exception.stacktrace}")
185+
.to(seda("processFailedRequests"))
186+
.setBody().simple("${exception.message}")
187+
.setHeader(Exchange.HTTP_RESPONSE_CODE, constant(Status.INTERNAL_SERVER_ERROR.getStatusCode()))
188+
.setHeader(Exchange.CONTENT_TYPE, constant(MediaType.TEXT_PLAIN))
189+
;
178190
//fmt:on
179191
}
180192

src/main/java/com/redhat/exhort/integration/backend/sbom/SbomParser.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,15 @@ protected void validate(DependencyTree tree) {
4646
types.add(d.ref().purl().getType());
4747
d.transitive().forEach(t -> types.add(t.purl().getType()));
4848
});
49-
if (types.size() > 1) {
50-
throw new IllegalArgumentException(
51-
"It is not supported to submit mixed Package Manager types. Found: " + types);
52-
}
49+
5350
List<String> invalidTypes =
5451
types.stream().filter(Predicate.not(Constants.PKG_MANAGERS::contains)).toList();
5552
if (!invalidTypes.isEmpty()) {
5653
throw new IllegalArgumentException("Unsupported package types received: " + invalidTypes);
5754
}
55+
if (types.size() > 1) {
56+
throw new IllegalArgumentException(
57+
"It is not supported to submit mixed Package Manager types. Found: " + types);
58+
}
5859
}
5960
}

src/main/java/com/redhat/exhort/integration/backend/sbom/spdx/SpdxParser.java

Lines changed: 91 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -25,22 +25,19 @@
2525
import java.util.HashMap;
2626
import java.util.HashSet;
2727
import java.util.Map;
28-
import java.util.Optional;
2928
import java.util.Set;
30-
import java.util.function.Predicate;
3129
import java.util.stream.Collectors;
3230

3331
import org.slf4j.Logger;
3432
import org.slf4j.LoggerFactory;
3533
import org.spdx.jacksonstore.MultiFormatStore;
3634
import org.spdx.jacksonstore.MultiFormatStore.Format;
3735
import org.spdx.library.InvalidSPDXAnalysisException;
38-
import org.spdx.library.model.Relationship;
3936
import org.spdx.library.model.SpdxPackage;
37+
import org.spdx.library.model.enumerations.RelationshipType;
4038
import org.spdx.storage.simple.InMemSpdxStore;
4139

4240
import com.redhat.exhort.api.PackageRef;
43-
import com.redhat.exhort.integration.Constants;
4441
import com.redhat.exhort.integration.backend.sbom.SbomParser;
4542
import com.redhat.exhort.model.DependencyTree;
4643
import com.redhat.exhort.model.DirectDependency;
@@ -57,16 +54,7 @@ protected DependencyTree buildTree(InputStream input) {
5754
try {
5855
MultiFormatStore inputStore = new MultiFormatStore(new InMemSpdxStore(), Format.JSON_PRETTY);
5956
SpdxWrapper wrapper = new SpdxWrapper(inputStore, input);
60-
PackageRef root = wrapper.getRootRef();
6157
Map<PackageRef, DirectDependency> deps = buildDeps(wrapper);
62-
if (root == null) {
63-
Optional<PackageRef> first = deps.keySet().stream().findFirst();
64-
if (first.isEmpty()) {
65-
root = DependencyTree.getDefaultRoot(Constants.MAVEN_PKG_MANAGER);
66-
} else {
67-
root = DependencyTree.getDefaultRoot(first.get().purl().getType());
68-
}
69-
}
7058
DependencyTree tree = new DependencyTree(deps);
7159
return tree;
7260
} catch (SpdxProcessingException | InvalidSPDXAnalysisException | IOException e) {
@@ -80,21 +68,8 @@ protected DependencyTree buildTree(InputStream input) {
8068
private Map<PackageRef, DirectDependency> buildDeps(SpdxWrapper wrapper) {
8169
Collection<SpdxPackage> packages = wrapper.getPackages();
8270
Map<String, Set<String>> links = new HashMap<>();
83-
packages.stream()
84-
.filter(Predicate.not(wrapper::hasRootName))
85-
.forEach(
86-
p -> {
87-
try {
88-
String id = p.getId();
89-
Set<String> rels =
90-
p.getRelationships().stream()
91-
.map(this::getRelationshipId)
92-
.collect(Collectors.toSet());
93-
links.put(id, rels);
94-
} catch (InvalidSPDXAnalysisException e) {
95-
throw new SpdxProcessingException("Unable to retrieve relationsips", e);
96-
}
97-
});
71+
packages.stream().forEach(p -> createPackageLinks(p, packages, links));
72+
9873
Set<String> directDeps =
9974
links.keySet().stream()
10075
.filter(
@@ -131,6 +106,94 @@ private Map<PackageRef, DirectDependency> buildDeps(SpdxWrapper wrapper) {
131106
return deps;
132107
}
133108

109+
private void createPackageLinks(
110+
SpdxPackage p, Collection<SpdxPackage> packages, Map<String, Set<String>> links) {
111+
try {
112+
String pkgId = p.getId();
113+
if (packages.stream().noneMatch(pkg -> pkg.getId().equals(pkgId))) {
114+
return;
115+
}
116+
if (p.getRelationships() == null || p.getRelationships().isEmpty()) {
117+
addLink(links, pkgId, null);
118+
}
119+
p.getRelationships().stream()
120+
.forEach(
121+
rel -> {
122+
try {
123+
String relatedId;
124+
if (rel.getRelatedSpdxElement().isPresent()) {
125+
relatedId = rel.getRelatedSpdxElement().get().getId();
126+
} else {
127+
relatedId = null;
128+
}
129+
boolean shouldIndexRelated =
130+
packages.stream().anyMatch(pkg -> pkg.getId().equals(relatedId));
131+
132+
switch (RelationshipDirection.fromRelationshipType(rel.getRelationshipType())) {
133+
case FORWARD:
134+
if (shouldIndexRelated) {
135+
addLink(links, pkgId, relatedId);
136+
} else {
137+
addLink(links, pkgId, null);
138+
}
139+
break;
140+
case BACKWARDS:
141+
if (shouldIndexRelated) {
142+
addLink(links, relatedId, pkgId);
143+
}
144+
break;
145+
case IGNORED:
146+
}
147+
} catch (InvalidSPDXAnalysisException e) {
148+
throw new SpdxProcessingException(
149+
"Unable to determine relationship for " + p.getId(), e);
150+
}
151+
});
152+
} catch (InvalidSPDXAnalysisException e) {
153+
throw new SpdxProcessingException("Unable to build package relationships", e);
154+
}
155+
}
156+
157+
private enum RelationshipDirection {
158+
FORWARD,
159+
BACKWARDS,
160+
IGNORED;
161+
162+
static RelationshipDirection fromRelationshipType(RelationshipType type) {
163+
switch (type) {
164+
case DEPENDS_ON:
165+
case CONTAINS:
166+
case BUILD_DEPENDENCY_OF:
167+
case OPTIONAL_COMPONENT_OF:
168+
case OPTIONAL_DEPENDENCY_OF:
169+
case PROVIDED_DEPENDENCY_OF:
170+
case TEST_DEPENDENCY_OF:
171+
case RUNTIME_DEPENDENCY_OF:
172+
case DEV_DEPENDENCY_OF:
173+
case ANCESTOR_OF:
174+
return FORWARD;
175+
case DEPENDENCY_OF:
176+
case DESCENDANT_OF:
177+
case PACKAGE_OF:
178+
case CONTAINED_BY:
179+
return BACKWARDS;
180+
default:
181+
return IGNORED;
182+
}
183+
}
184+
}
185+
186+
private void addLink(Map<String, Set<String>> links, String fromId, String toId) {
187+
Set<String> toRefs = links.get(fromId);
188+
if (toRefs == null) {
189+
toRefs = new HashSet<>();
190+
links.put(fromId, toRefs);
191+
}
192+
if (toId != null) {
193+
toRefs.add(toId);
194+
}
195+
}
196+
134197
private Set<String> addAllTransitive(String depKey, Map<String, Set<String>> links) {
135198
Set<String> deps = links.get(depKey);
136199
if (deps == null) {
@@ -144,12 +207,4 @@ private Set<String> addAllTransitive(String depKey, Map<String, Set<String>> lin
144207
});
145208
return result;
146209
}
147-
148-
private String getRelationshipId(Relationship r) {
149-
try {
150-
return r.getRelatedSpdxElement().get().getId();
151-
} catch (InvalidSPDXAnalysisException e) {
152-
throw new SpdxProcessingException("Unable to retrieve related Spdx element", e);
153-
}
154-
}
155210
}

src/main/java/com/redhat/exhort/integration/backend/sbom/spdx/SpdxWrapper.java

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ public class SpdxWrapper {
4444
private MultiFormatStore inputStore;
4545
private SpdxDocument doc;
4646
private String uri;
47-
private SpdxPackage root;
4847
private Collection<SpdxPackage> packages;
4948

5049
public SpdxWrapper(MultiFormatStore inputStore, InputStream input)
@@ -58,29 +57,6 @@ public SpdxWrapper(MultiFormatStore inputStore, InputStream input)
5857
throw new SpdxProcessingException("Invalid " + SUPPORTED_VERSION + " document received");
5958
}
6059
this.packages = parsePackages();
61-
this.root = findRoot();
62-
}
63-
64-
public PackageRef getRootRef() {
65-
if (root != null) {
66-
return toPackageRef(root);
67-
}
68-
return null;
69-
}
70-
71-
private SpdxPackage findRoot() throws InvalidSPDXAnalysisException {
72-
if (doc.getName().isEmpty()) {
73-
return null;
74-
}
75-
return packages.stream().filter(p -> hasRootName(p)).findFirst().orElse(null);
76-
}
77-
78-
public boolean hasRootName(SpdxPackage p) {
79-
try {
80-
return p.getName().isPresent() && p.getName().get().equals(doc.getName().get());
81-
} catch (InvalidSPDXAnalysisException e) {
82-
throw new SpdxProcessingException("Unable to retrieve name for package", e);
83-
}
8460
}
8561

8662
public PackageRef toPackageRef(SpdxPackage spdxPackage) {
@@ -107,6 +83,25 @@ public PackageRef toPackageRef(SpdxPackage spdxPackage) {
10783
}
10884
}
10985

86+
public boolean hasPurl(SpdxPackage pkg) {
87+
try {
88+
if (pkg.getExternalRefs() == null || pkg.getExternalRefs().isEmpty()) {
89+
return false;
90+
}
91+
return pkg.getExternalRefs().stream()
92+
.anyMatch(
93+
ref -> {
94+
try {
95+
return PURL_REFERENCE.equals(ref.getReferenceType().getIndividualURI());
96+
} catch (InvalidSPDXAnalysisException e) {
97+
return false;
98+
}
99+
});
100+
} catch (InvalidSPDXAnalysisException e) {
101+
return false;
102+
}
103+
}
104+
110105
public Collection<SpdxPackage> getPackages() {
111106
return this.packages;
112107
}
@@ -120,11 +115,26 @@ public SpdxPackage getPackageById(String id) {
120115
}
121116

122117
private Collection<SpdxPackage> parsePackages() throws InvalidSPDXAnalysisException {
118+
Optional<String> docName = doc.getName();
123119
return inputStore
124120
.getAllItems(uri, SpdxConstants.CLASS_SPDX_PACKAGE)
125-
.filter(p -> root == null || !p.getId().equals(root.getId()))
126121
.map(TypedValue::getId)
127122
.map(this::getPackageById)
123+
.filter(this::hasPurl)
124+
.filter(p -> !packageHasName(p, docName))
128125
.collect(Collectors.toList());
129126
}
127+
128+
private boolean packageHasName(SpdxPackage pkg, Optional<String> expected) {
129+
Optional<String> name;
130+
try {
131+
name = pkg.getName();
132+
if (name.isPresent()) {
133+
return name.get().equals(expected.orElse(null));
134+
}
135+
return expected.isEmpty();
136+
} catch (InvalidSPDXAnalysisException e) {
137+
throw new SpdxProcessingException("Unable to retrieve package name", e);
138+
}
139+
}
130140
}

0 commit comments

Comments
 (0)