34
34
import org .dependencytrack .model .Project ;
35
35
import org .dependencytrack .persistence .QueryManager ;
36
36
37
+ import javax .jdo .FetchPlan ;
38
+ import javax .jdo .PersistenceManager ;
37
39
import javax .ws .rs .GET ;
38
40
import javax .ws .rs .Path ;
39
41
import javax .ws .rs .PathParam ;
40
42
import javax .ws .rs .Produces ;
41
43
import javax .ws .rs .QueryParam ;
42
44
import javax .ws .rs .core .MediaType ;
43
45
import javax .ws .rs .core .Response ;
46
+ import java .util .Collection ;
44
47
45
48
/**
46
49
* JAX-RS resources for processing policy violations.
@@ -68,7 +71,9 @@ public Response getViolations(@ApiParam(value = "Optionally includes suppressed
68
71
@ QueryParam ("suppressed" ) boolean suppressed ) {
69
72
try (QueryManager qm = new QueryManager (getAlpineRequest ())) {
70
73
final PaginatedResult result = qm .getPolicyViolations (suppressed );
71
- return Response .ok (result .getObjects ()).header (TOTAL_COUNT_HEADER , result .getTotal ()).build ();
74
+ return Response .ok (detachViolations (qm , result .getList (PolicyViolation .class )))
75
+ .header (TOTAL_COUNT_HEADER , result .getTotal ())
76
+ .build ();
72
77
}
73
78
}
74
79
@@ -95,7 +100,9 @@ public Response getViolationsByProject(@PathParam("uuid") String uuid,
95
100
if (project != null ) {
96
101
if (qm .hasAccess (super .getPrincipal (), project )) {
97
102
final PaginatedResult result = qm .getPolicyViolations (project , suppressed );
98
- return Response .ok (result .getObjects ()).header (TOTAL_COUNT_HEADER , result .getTotal ()).build ();
103
+ return Response .ok (detachViolations (qm , result .getList (PolicyViolation .class )))
104
+ .header (TOTAL_COUNT_HEADER , result .getTotal ())
105
+ .build ();
99
106
} else {
100
107
return Response .status (Response .Status .FORBIDDEN ).entity ("Access to the specified project is forbidden" ).build ();
101
108
}
@@ -128,7 +135,9 @@ public Response getViolationsByComponent(@PathParam("uuid") String uuid,
128
135
if (component != null ) {
129
136
if (qm .hasAccess (super .getPrincipal (), component .getProject ())) {
130
137
final PaginatedResult result = qm .getPolicyViolations (component , suppressed );
131
- return Response .ok (result .getObjects ()).header (TOTAL_COUNT_HEADER , result .getTotal ()).build ();
138
+ return Response .ok (detachViolations (qm , result .getList (PolicyViolation .class )))
139
+ .header (TOTAL_COUNT_HEADER , result .getTotal ())
140
+ .build ();
132
141
} else {
133
142
return Response .status (Response .Status .FORBIDDEN ).entity ("Access to the specified component is forbidden" ).build ();
134
143
}
@@ -137,4 +146,23 @@ public Response getViolationsByComponent(@PathParam("uuid") String uuid,
137
146
}
138
147
}
139
148
}
149
+
150
+ /**
151
+ * Detach a given {@link Collection} of {@link PolicyViolation} suitable for use in API responses.
152
+ * <p>
153
+ * This ensures that responses include not only the violations themselves, but also the associated
154
+ * {@link org.dependencytrack.model.Policy}, which is required to tell the policy name and violation state.
155
+ *
156
+ * @param qm The {@link QueryManager} to use
157
+ * @param violations The {@link PolicyViolation}s to detach
158
+ * @return A detached {@link Collection} of {@link PolicyViolation}s
159
+ * @see <a href="https://github.com/DependencyTrack/dependency-track/issues/2043">GitHub issue</a>
160
+ */
161
+ private Collection <PolicyViolation > detachViolations (final QueryManager qm , final Collection <PolicyViolation > violations ) {
162
+ final PersistenceManager pm = qm .getPersistenceManager ();
163
+ pm .getFetchPlan ().setMaxFetchDepth (2 ); // Ensure policy is included
164
+ pm .getFetchPlan ().setDetachmentOptions (FetchPlan .DETACH_LOAD_FIELDS );
165
+ return qm .getPersistenceManager ().detachCopyAll (violations );
166
+ }
167
+
140
168
}
0 commit comments