1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 package gov.nist.secauto.decima.core.assessment.result;
28
29 import gov.nist.secauto.decima.core.assessment.Assessment;
30 import gov.nist.secauto.decima.core.assessment.util.LoggingHandler;
31 import gov.nist.secauto.decima.core.assessment.util.NoOpLoggingHandler;
32 import gov.nist.secauto.decima.core.document.Document;
33 import gov.nist.secauto.decima.core.requirement.BaseRequirement;
34 import gov.nist.secauto.decima.core.requirement.DerivedRequirement;
35 import gov.nist.secauto.decima.core.requirement.RequirementsManager;
36 import gov.nist.secauto.decima.core.util.ObjectUtil;
37
38 import org.apache.logging.log4j.LogManager;
39 import org.apache.logging.log4j.Logger;
40
41 import java.time.Clock;
42 import java.time.ZonedDateTime;
43 import java.util.Collection;
44 import java.util.Collections;
45 import java.util.HashMap;
46 import java.util.LinkedHashMap;
47 import java.util.LinkedList;
48 import java.util.List;
49 import java.util.Map;
50 import java.util.Objects;
51
52 public class DefaultAssessmentResultBuilder implements AssessmentResultBuilder {
53 private static final Logger log = LogManager.getLogger(DefaultAssessmentResultBuilder.class);
54 private static final ResultStatusBehavior DEFAULT_RESULT_STATUS_BEHAVIOR = new DefaultResultStatusBehavior();
55 private final ResultStatusBehavior resultStatusBehavior;
56 private final Map<String, Document> systemIdToAssessedDocumentMap;
57 private final Map<String, List<TestResult>> derivedRequirementToTestResultsMap;
58 private final Map<String, TestState> derivedRequirementsTestStatusMap;
59 private final Map<String, String> assessmentProperties;
60
61 private ZonedDateTime startDateTime;
62 private ZonedDateTime endDateTime;
63 private LoggingHandler loggingHandler = NoOpLoggingHandler.instance();
64
65 public DefaultAssessmentResultBuilder() {
66 this(DEFAULT_RESULT_STATUS_BEHAVIOR);
67 }
68
69
70
71
72
73
74
75
76 public DefaultAssessmentResultBuilder(ResultStatusBehavior resultStatusBehavior) {
77 Objects.requireNonNull(resultStatusBehavior, "resultStatusBehavior");
78 this.resultStatusBehavior = resultStatusBehavior;
79 this.systemIdToAssessedDocumentMap = new HashMap<>();
80 this.derivedRequirementToTestResultsMap = new HashMap<>(50);
81 this.derivedRequirementsTestStatusMap = new HashMap<>(50);
82 this.assessmentProperties = new LinkedHashMap<>();
83 }
84
85
86
87
88
89
90 public synchronized ZonedDateTime getStartDateTime() {
91 return startDateTime;
92 }
93
94
95
96
97
98
99
100 protected synchronized void setStartDateTime(ZonedDateTime startDateTime) {
101 this.startDateTime = startDateTime;
102 }
103
104
105
106
107
108
109 public synchronized ZonedDateTime getEndDateTime() {
110 return endDateTime;
111 }
112
113
114
115
116
117
118
119 protected synchronized void setEndDateTime(ZonedDateTime endDateTime) {
120 this.endDateTime = endDateTime;
121 }
122
123 @Override
124 public synchronized Map<String, TestState> getTestStateByDerivedRequirementId() {
125 return Collections.unmodifiableMap(derivedRequirementsTestStatusMap);
126 }
127
128 public LoggingHandler getLoggingHandler() {
129 return loggingHandler;
130 }
131
132 public void setLoggingHandler(LoggingHandler loggingHandler) {
133 Objects.requireNonNull(loggingHandler, "loggingHandler");
134 this.loggingHandler = loggingHandler;
135 }
136
137 @Override
138 public synchronized AssessmentResultBuilder start() {
139 synchronized (this) {
140 if (getStartDateTime() == null) {
141 setStartDateTime(ZonedDateTime.now(Clock.systemDefaultZone()));
142 getLoggingHandler().validationStarted();
143 }
144 }
145 return this;
146 }
147
148 @Override
149 public synchronized AssessmentResultBuilder end() {
150 synchronized (this) {
151 if (getStartDateTime() == null) {
152 throw new IllegalStateException("The builder was not started. Please call start() first.");
153 }
154 if (getEndDateTime() == null) {
155 setEndDateTime(ZonedDateTime.now(Clock.systemDefaultZone()));
156 getLoggingHandler().validationEnded(this);
157 }
158 }
159 return this;
160 }
161
162 @Override
163 public AssessmentResultBuilder addAssessmentTarget(Document document) {
164 String systemId = document.getSystemId();
165 if (!systemIdToAssessedDocumentMap.containsKey(systemId)) {
166 systemIdToAssessedDocumentMap.put(systemId, document);
167 } else {
168 Document other = systemIdToAssessedDocumentMap.get(systemId);
169 if (!other.equals(document) && log.isDebugEnabled()) {
170 log.debug("Duplicate systemId {} found for documents {} and {}", systemId, document.toString(),
171 other.toString());
172 }
173 }
174 return this;
175 }
176
177 @Override
178 public <DOC extends Document> AssessmentResultBuilder addTestResult(Assessment<? extends DOC> assessment,
179 DOC document, String derivedRequirementId, TestResult result) {
180 ObjectUtil.requireNonEmpty(derivedRequirementId);
181 Objects.requireNonNull(result);
182
183 synchronized (this) {
184 start();
185 List<TestResult> resultList = derivedRequirementToTestResultsMap.get(derivedRequirementId);
186 if (resultList == null) {
187 resultList = new LinkedList<>();
188 derivedRequirementToTestResultsMap.put(derivedRequirementId, resultList);
189 }
190 resultList.add(result);
191 assignTestStatus(assessment, document, derivedRequirementId, TestState.TESTED);
192 }
193
194 LoggingHandler loggingHandler = getLoggingHandler();
195 if (loggingHandler != null) {
196 loggingHandler.addTestResult(assessment, document, derivedRequirementId, result);
197 }
198 return this;
199 }
200
201 @Override
202 public AssessmentResults build(RequirementsManager requirementsManager) {
203 getLoggingHandler().producingResults(this, requirementsManager);
204
205 log.info("Compiling assessment results");
206 DefaultAssessmentResults retval;
207
208 synchronized (this) {
209 if (startDateTime == null) {
210 throw new IllegalStateException("The builder was not started. Please call start() first.");
211 }
212
213 if (endDateTime == null) {
214 throw new IllegalStateException("The builder has not been stopped. Please call end() first.");
215 }
216
217 retval = new DefaultAssessmentResults(requirementsManager, getStartDateTime(), getEndDateTime());
218
219 for (Map.Entry<String, String> entry : assessmentProperties.entrySet()) {
220 retval.setProperty(entry.getKey(), entry.getValue());
221 }
222
223 for (Map.Entry<String, Document> entry : systemIdToAssessedDocumentMap.entrySet()) {
224 retval.addAssessmentSubject(entry.getValue());
225 }
226 for (BaseRequirement base : requirementsManager.getBaseRequirements()) {
227 boolean inScope = resultStatusBehavior.isInScope(base);
228 DefaultBaseRequirementResult baseResult;
229 if (inScope) {
230 baseResult = buildBaseRequirementResult(base);
231 } else {
232 baseResult = new DefaultBaseRequirementResult(base, ResultStatus.NOT_IN_SCOPE);
233 for (DerivedRequirement derived : base.getDerivedRequirements()) {
234 baseResult
235 .addDerivedRequirementResult(new DefaultDerivedRequirementResult(derived, ResultStatus.NOT_IN_SCOPE));
236 }
237 }
238 retval.addValidationResult(baseResult);
239 }
240 }
241
242 getLoggingHandler().completedResults(this, requirementsManager, retval);
243 return retval;
244 }
245
246 private DefaultBaseRequirementResult buildBaseRequirementResult(BaseRequirement base) {
247 DefaultBaseRequirementResult retval;
248
249 Collection<DerivedRequirement> derivedRequirements = base.getDerivedRequirements();
250 retval = new DefaultBaseRequirementResult(base, ResultStatus.NOT_TESTED);
251 if (!derivedRequirements.isEmpty()) {
252 for (DerivedRequirement derived : base.getDerivedRequirements()) {
253 DefaultDerivedRequirementResult result = buildDerivedRequirementResult(derived);
254 retval.addDerivedRequirementResult(result);
255 }
256 }
257 return retval;
258 }
259
260 private DefaultDerivedRequirementResult buildDerivedRequirementResult(DerivedRequirement derived) {
261
262 DefaultDerivedRequirementResult derivedResult;
263
264 boolean inScope = resultStatusBehavior.isInScope(derived);
265 if (!inScope) {
266 derivedResult = new DefaultDerivedRequirementResult(derived, ResultStatus.NOT_IN_SCOPE);
267 } else {
268 derivedResult = new DefaultDerivedRequirementResult(derived, ResultStatus.NOT_TESTED);
269
270
271
272 List<TestResult> assertionResults = getAssertionResultsByDerivedRequirementId(derived.getId());
273 if (!assertionResults.isEmpty()) {
274 derivedResult.addTestResults(assertionResults);
275 } else {
276
277
278 TestState testStatus = derivedRequirementsTestStatusMap.get(derived.getId());
279 if (testStatus == null) {
280 testStatus = TestState.NOT_TESTED;
281 }
282
283 switch (testStatus) {
284 case NOT_APPLICABLE:
285 derivedResult.setStatus(ResultStatus.NOT_APPLICABLE);
286 break;
287 case TESTED:
288
289 if (Severity.INFO.equals(derived.getType().getSeverity())) {
290 derivedResult.setStatus(ResultStatus.INFORMATIONAL);
291
292
293 } else {
294 derivedResult.setStatus(ResultStatus.PASS);
295 }
296 break;
297 case NOT_TESTED:
298
299 break;
300 default:
301 throw new UnsupportedOperationException(testStatus.toString());
302 }
303 }
304 }
305 return derivedResult;
306 }
307
308 @Override
309 public <DOC extends Document> AssessmentResultBuilder assignTestStatus(Assessment<? extends DOC> assessment,
310 DOC document, String derivedRequirementId, TestState state) {
311 ObjectUtil.requireNonEmpty(derivedRequirementId, "derivedRequirementId");
312 Objects.requireNonNull(state, "state");
313
314 synchronized (this) {
315 start();
316 TestState oldStatus = derivedRequirementsTestStatusMap.get(derivedRequirementId);
317 if (oldStatus == null || oldStatus.ordinal() < state.ordinal()) {
318 derivedRequirementsTestStatusMap.put(derivedRequirementId, state);
319 }
320 }
321
322 LoggingHandler loggingHandler = getLoggingHandler();
323 if (loggingHandler != null) {
324 loggingHandler.assignTestStatus(assessment, document, derivedRequirementId, state);
325 }
326 return this;
327 }
328
329
330
331
332
333
334
335
336 public List<TestResult> getAssertionResultsByDerivedRequirementId(String derivedRequirementId) {
337 ObjectUtil.requireNonEmpty(derivedRequirementId);
338
339 List<TestResult> retval;
340
341 synchronized (this) {
342 retval = derivedRequirementToTestResultsMap.get(derivedRequirementId);
343 }
344
345 if (retval == null) {
346 retval = Collections.emptyList();
347 } else {
348 retval = Collections.unmodifiableList(retval);
349 }
350 return retval;
351 }
352
353
354
355
356
357
358
359
360
361
362
363 public AssessmentResultBuilder assignProperty(String key, String value) {
364 Objects.requireNonNull(key, "key");
365 Objects.requireNonNull(value, "value");
366 this.assessmentProperties.put(key, value);
367 return this;
368 }
369 }