Skip to content

Commit 307bd1b

Browse files
refactor: remove ancestorPath, use nested parent structure only
- Remove ancestorPath and AncestorPathElement from Project model - Replace with nested parent (parent.parent...) for hierarchy - Add ProjectParentInfo for AffectedProject parent chain - Update AffectedProject to use parent instead of ancestorPath - Rename buildAncestorPathFromMap to buildParentChainFromMap - Update tests to assert parent chain only Signed-off-by: Valentijn Scholten <valentijnscholten@gmail.com>
1 parent 609dd8d commit 307bd1b

7 files changed

Lines changed: 70 additions & 69 deletions

File tree

src/main/java/org/dependencytrack/model/Project.java

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -343,14 +343,6 @@ public enum FetchGroup {
343343
private transient ProjectMetrics metrics;
344344
private transient List<ProjectVersion> versions;
345345
private transient List<Component> dependencyGraph;
346-
private transient List<AncestorPathElement> ancestorPath;
347-
348-
/**
349-
* Represents an element in the ancestor path of a project.
350-
* Used to display the full hierarchy path without requiring nested parent objects.
351-
*/
352-
public record AncestorPathElement(UUID uuid, String name, String version) {
353-
}
354346

355347
public long getId() {
356348
return id;
@@ -636,14 +628,6 @@ public void setVersions(List<ProjectVersion> versions) {
636628
this.versions = versions;
637629
}
638630

639-
public List<AncestorPathElement> getAncestorPath() {
640-
return ancestorPath;
641-
}
642-
643-
public void setAncestorPath(List<AncestorPathElement> ancestorPath) {
644-
this.ancestorPath = ancestorPath;
645-
}
646-
647631
@JsonIgnore
648632
public List<Team> getAccessTeams() {
649633
return accessTeams;

src/main/java/org/dependencytrack/persistence/QueryManager.java

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1722,35 +1722,25 @@ protected void populateAncestorPaths(List<Project> projects) {
17221722
depth++;
17231723
}
17241724

1725-
// Build the ancestor path and wire the parent chain for each project
1725+
// Wire the parent chain for each project so serialization produces nested parent structure
17261726
projects.forEach(project -> {
1727-
final List<Project.AncestorPathElement> path = buildAncestorPathFromMap(project, projectMap);
1728-
project.setAncestorPath(path);
1729-
wireParentChain(project, path, projectMap);
1727+
final List<Project> path = buildParentChainFromMap(project, projectMap);
1728+
wireParentChain(path);
17301729
});
17311730
}
17321731

17331732
/**
17341733
* Wires the parent chain on the Project entities so that serialization produces a nested
1735-
* parent structure (parent containing parent containing ...) rather than a flat list.
1736-
* Path is ordered from root to immediate parent.
1734+
* parent structure (parent containing parent containing ...). Path is ordered from root to immediate parent.
17371735
*/
1738-
protected static void wireParentChain(Project project, List<Project.AncestorPathElement> path,
1739-
Map<UUID, Project> projectMap) {
1736+
protected static void wireParentChain(List<Project> path) {
17401737
if (path == null || path.size() < 2) {
17411738
return;
17421739
}
1743-
for (int i = path.size() - 1; i >= 1; i--) {
1744-
final Project ancestor = projectMap.get(path.get(i).uuid());
1745-
final Project ancestorParent = projectMap.get(path.get(i - 1).uuid());
1746-
if (ancestor != null && ancestorParent != null) {
1747-
ancestor.setParent(ancestorParent);
1748-
}
1749-
}
1750-
final Project root = projectMap.get(path.get(0).uuid());
1751-
if (root != null) {
1752-
root.setParent(null);
1740+
for (int i = 1; i < path.size(); i++) {
1741+
path.get(i).setParent(path.get(i - 1));
17531742
}
1743+
path.get(0).setParent(null);
17541744
}
17551745

17561746
/**
@@ -1771,18 +1761,19 @@ protected List<Project> fetchProjectsByUuids(Set<UUID> uuids) {
17711761
}
17721762

17731763
/**
1774-
* Builds the ancestor path for a project using a pre-populated map of projects.
1764+
* Builds the parent chain for a project using a pre-populated map of projects.
17751765
* The path is ordered from root to immediate parent (not including the project itself).
17761766
*/
1777-
protected static List<Project.AncestorPathElement> buildAncestorPathFromMap(Project project, Map<UUID, Project> projectMap) {
1778-
final List<Project.AncestorPathElement> path = new ArrayList<>();
1767+
protected static List<Project> buildParentChainFromMap(Project project, Map<UUID, Project> projectMap) {
1768+
final List<Project> path = new ArrayList<>();
17791769
Project current = project.getParent();
17801770
int depth = 0;
17811771

17821772
while (current != null && depth < MAX_ANCESTOR_DEPTH) {
1783-
// Insert at the beginning to maintain root-to-parent order
1784-
path.addFirst(new Project.AncestorPathElement(current.getUuid(), current.getName(), current.getVersion()));
1785-
// Look up the parent in our map to continue traversal
1773+
final Project resolved = projectMap.get(current.getUuid());
1774+
if (resolved != null) {
1775+
path.addFirst(resolved);
1776+
}
17861777
current = current.getParent() != null ? projectMap.get(current.getParent().getUuid()) : null;
17871778
depth++;
17881779
}

src/main/java/org/dependencytrack/persistence/VulnerabilityQueryManager.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -533,11 +533,11 @@ public List<AffectedProject> getAffectedProjects(Vulnerability vulnerability) {
533533
}
534534
}
535535

536-
// Populate ancestor paths for all affected projects
536+
// Wire parent chains for all affected projects
537537
final List<Project> projects = new ArrayList<>(projectMap.values());
538538
populateAncestorPaths(projects);
539539

540-
// Create AffectedProject objects with ancestor paths
540+
// Create AffectedProject objects with nested parent structure
541541
return projects.stream()
542542
.map(project -> new AffectedProject(
543543
project.getUuid(),
@@ -546,7 +546,7 @@ public List<AffectedProject> getAffectedProjects(Vulnerability vulnerability) {
546546
project.getVersion(),
547547
project.isActive(),
548548
affectedComponentUuidsMap.get(project.getUuid()),
549-
project.getAncestorPath()))
549+
AffectedProject.buildParentInfo(project.getParent())))
550550
.toList();
551551
}
552552

src/main/java/org/dependencytrack/resources/v1/vo/AffectedProject.java

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@
1818
*/
1919
package org.dependencytrack.resources.v1.vo;
2020

21-
import org.dependencytrack.model.Project.AncestorPathElement;
22-
2321
import java.util.ArrayList;
2422
import java.util.List;
2523
import java.util.UUID;
2624

25+
import org.dependencytrack.model.Project;
26+
2727
/**
2828
* Describes a project that is affected by a specific vulnerability, including a list of UUIDs of the components
2929
* affected by the vulnerability within this project.
@@ -44,16 +44,17 @@ public class AffectedProject {
4444

4545
private final List<UUID> affectedComponentUuids;
4646

47-
private final List<AncestorPathElement> ancestorPath;
47+
private final ProjectParentInfo parent;
4848

49-
public AffectedProject(UUID uuid, boolean dependencyGraphAvailable, String name, String version, boolean active, List<UUID> affectedComponentUuids, List<AncestorPathElement> ancestorPath) {
49+
public AffectedProject(UUID uuid, boolean dependencyGraphAvailable, String name, String version, boolean active,
50+
List<UUID> affectedComponentUuids, ProjectParentInfo parent) {
5051
this.uuid = uuid;
5152
this.dependencyGraphAvailable = dependencyGraphAvailable;
5253
this.name = name;
5354
this.version = version;
5455
this.active = active;
5556
this.affectedComponentUuids = affectedComponentUuids == null ? new ArrayList<>() : affectedComponentUuids;
56-
this.ancestorPath = ancestorPath;
57+
this.parent = parent;
5758
}
5859

5960
public UUID getUuid() {
@@ -63,6 +64,7 @@ public UUID getUuid() {
6364
public boolean isDependencyGraphAvailable() {
6465
return dependencyGraphAvailable;
6566
}
67+
6668
public String getName() {
6769
return name;
6870
}
@@ -79,7 +81,21 @@ public List<UUID> getAffectedComponentUuids() {
7981
return affectedComponentUuids;
8082
}
8183

82-
public List<AncestorPathElement> getAncestorPath() {
83-
return ancestorPath;
84+
public ProjectParentInfo getParent() {
85+
return parent;
86+
}
87+
88+
/**
89+
* Builds a nested ProjectParentInfo from a Project's parent chain (after it has been wired).
90+
*/
91+
public static ProjectParentInfo buildParentInfo(Project project) {
92+
if (project == null) {
93+
return null;
94+
}
95+
return new ProjectParentInfo(
96+
project.getUuid(),
97+
project.getName(),
98+
project.getVersion(),
99+
buildParentInfo(project.getParent()));
84100
}
85101
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* This file is part of Dependency-Track.
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.dependencytrack.resources.v1.vo;
20+
21+
import java.util.UUID;
22+
23+
/**
24+
* Represents a project's parent in a nested structure (parent containing parent containing ...).
25+
* Used for hierarchy display in API responses.
26+
*/
27+
public record ProjectParentInfo(UUID uuid, String name, String version, ProjectParentInfo parent) {
28+
}

src/test/java/org/dependencytrack/persistence/ProjectQueryManagerTest.java

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ public void testCloneProjectMetricUpdate() throws Exception {
133133
}
134134

135135
@Test
136-
void testGetProjectPopulatesAncestorPathAndNestedParentChain() {
136+
void testGetProjectPopulatesNestedParentChain() {
137137
final Project grandparent = qm.createProject("grandparent", null, "1.0", null, null, null, true, false);
138138
final Project parent = new Project();
139139
parent.setName("parent");
@@ -149,15 +149,6 @@ void testGetProjectPopulatesAncestorPathAndNestedParentChain() {
149149
final Project fetched = qm.getProject(project.getUuid().toString());
150150
Assertions.assertNotNull(fetched);
151151

152-
// Ancestor path is populated (root to immediate parent)
153-
final List<Project.AncestorPathElement> ancestorPath = fetched.getAncestorPath();
154-
Assertions.assertNotNull(ancestorPath);
155-
Assertions.assertEquals(2, ancestorPath.size());
156-
Assertions.assertEquals("grandparent", ancestorPath.get(0).name());
157-
Assertions.assertEquals("1.0", ancestorPath.get(0).version());
158-
Assertions.assertEquals("parent", ancestorPath.get(1).name());
159-
Assertions.assertEquals("1.0", ancestorPath.get(1).version());
160-
161152
// Nested parent chain is wired
162153
Assertions.assertNotNull(fetched.getParent());
163154
Assertions.assertEquals("parent", fetched.getParent().getName());

src/test/java/org/dependencytrack/resources/v1/ProjectResourceTest.java

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -458,15 +458,6 @@ void getProjectByUuidWithNestedParentChainTest() {
458458

459459
// Root has no parent (key omitted with NON_NULL or null)
460460
assertThat(grandparent.containsKey("parent")).isFalse();
461-
462-
// Assert ancestorPath is also populated (flat list for backwards compatibility)
463-
JsonArray ancestorPath = json.getJsonArray("ancestorPath");
464-
assertThat(ancestorPath).isNotNull();
465-
assertThat(ancestorPath.size()).isEqualTo(2); // grandparent, parent (root to immediate)
466-
assertThat(ancestorPath.getJsonObject(0).getString("name")).isEqualTo("acme-org");
467-
assertThat(ancestorPath.getJsonObject(0).getString("version")).isEqualTo("1.0");
468-
assertThat(ancestorPath.getJsonObject(1).getString("name")).isEqualTo("acme-app-parent");
469-
assertThat(ancestorPath.getJsonObject(1).getString("version")).isEqualTo("1.0.0");
470461
}
471462

472463
@Test

0 commit comments

Comments
 (0)