2022-09-16 12:56:52 +02:00
|
|
|
package eu.dnetlib.validator2.validation.guideline;
|
|
|
|
|
|
|
|
import eu.dnetlib.validator2.engine.*;
|
|
|
|
import eu.dnetlib.validator2.engine.builtins.StandardRuleDiagnostics;
|
2023-07-10 13:38:06 +02:00
|
|
|
import org.slf4j.Logger;
|
|
|
|
import org.slf4j.LoggerFactory;
|
2022-09-16 12:56:52 +02:00
|
|
|
import org.w3c.dom.Document;
|
|
|
|
import org.w3c.dom.NodeList;
|
|
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.Map;
|
|
|
|
|
|
|
|
class GuidelineEvaluation {
|
|
|
|
|
2023-07-10 13:38:06 +02:00
|
|
|
private static final Logger logger = LoggerFactory.getLogger(GuidelineEvaluation.class);
|
|
|
|
|
|
|
|
|
2022-09-16 12:56:52 +02:00
|
|
|
// TODO: Report all rule diagnostics to System out/err (we should remove this or allow clients to disable it)
|
|
|
|
private static final RuleDiagnostics<Document, Rule<Document>> OUT = Helper.Diagnostics.systemOut();
|
|
|
|
private static final RuleDiagnostics<Document, Rule<Document>> ERR = Helper.Diagnostics.systemErr();
|
|
|
|
|
|
|
|
private final String subjectId;
|
|
|
|
private final Document doc;
|
|
|
|
private final int weight;
|
|
|
|
private final List<String> warnings = new ArrayList<>();
|
|
|
|
private final List<String> errors = new ArrayList<>();
|
|
|
|
private Map<String, RequirementLevel> ruleIdToRequirementLevel = new HashMap<>();
|
|
|
|
private Map<String, NodeList> ruleIdToNodeList = new HashMap<>();
|
|
|
|
private final Diagnostics diagnostics = new Diagnostics();
|
|
|
|
private final Reporter<Document, SyntheticRule<Document>> reporter = new Reporter<>(diagnostics);
|
|
|
|
|
|
|
|
GuidelineEvaluation(String subjectId, Document doc, int weight) {
|
|
|
|
this.subjectId = subjectId;
|
|
|
|
this.doc = doc;
|
|
|
|
this.weight = weight;
|
|
|
|
}
|
|
|
|
|
|
|
|
Guideline.Result evaluate(CompilationResult result) {
|
|
|
|
|
|
|
|
ruleIdToRequirementLevel.putAll(result.ruleIdToRequirementLevel);
|
|
|
|
|
|
|
|
List<SyntheticRule<Document>> rules = new ArrayList<>();
|
|
|
|
rules.add(result.rootNodeRule);
|
|
|
|
rules.addAll(result.nodeRules);
|
|
|
|
|
2023-07-10 13:38:06 +02:00
|
|
|
logger.debug("Evaluating " + rules);
|
2022-09-16 12:56:52 +02:00
|
|
|
|
2023-07-27 13:48:10 +02:00
|
|
|
for ( SyntheticRule<Document> rule: rules ) {
|
2022-09-16 12:56:52 +02:00
|
|
|
|
|
|
|
String id = rule.getContext().getIdProperty().getValue();
|
|
|
|
|
|
|
|
RuleEngine.applyAndReport(rule, doc, reporter);
|
|
|
|
|
|
|
|
Status status = diagnostics.getLastReportedStatus();
|
2023-07-27 13:48:10 +02:00
|
|
|
if ( status == Status.ERROR ) {
|
2022-09-16 12:56:52 +02:00
|
|
|
// fail fast in case of errors (no reason to proceed, since results may be rubbish)
|
|
|
|
return StandardResult.forError(diagnostics.getLastReportedError().getMessage());
|
|
|
|
}
|
|
|
|
|
2023-07-27 13:48:10 +02:00
|
|
|
if ( status == Status.SUCCESS && getRequirementLevelOf(id) == RequirementLevel.NOT_APPLICABLE ) {
|
2022-09-16 12:56:52 +02:00
|
|
|
// Report the non-applicability of a rule as a warning
|
|
|
|
// The check for both status and non-applicable requirement level is redundant
|
2023-07-27 13:48:10 +02:00
|
|
|
// (non-applicable rules always succeed), yet it is more clear, hopefully.
|
2023-07-10 13:38:06 +02:00
|
|
|
logger.warn("Non-applicable: " + rule);
|
2022-09-16 12:56:52 +02:00
|
|
|
warnings.add(synthesizeFailureMessage(rule));
|
|
|
|
}
|
2023-07-27 13:48:10 +02:00
|
|
|
else if ( status == Status.FAILURE ) {
|
|
|
|
if ( getRequirementLevelOf(id) == RequirementLevel.MANDATORY ) {
|
2022-09-16 12:56:52 +02:00
|
|
|
// A mandatory rule has failed, yet we don't know whether we should report is as such.
|
|
|
|
|
|
|
|
// Let's check the parent of the rule
|
|
|
|
if (rule.parentRule() == null) {
|
|
|
|
// This is the root rule failing!
|
|
|
|
// Fail fast here, too (don't waste resources to evaluate other rules).
|
|
|
|
// We will "enable" it, if it is requested.
|
2023-07-10 13:38:06 +02:00
|
|
|
logger.error("Fail fast for root failure: " + rule);
|
2022-09-16 12:56:52 +02:00
|
|
|
errors.add(synthesizeFailureMessage(rule));
|
|
|
|
return StandardResult.forFailure(warnings, errors);
|
2023-07-27 13:48:10 +02:00
|
|
|
} else {
|
2022-09-16 12:56:52 +02:00
|
|
|
// The current rule has a parent/ancestor which:
|
|
|
|
// (a) is non-mandatory or
|
|
|
|
// (b) it was successful.
|
|
|
|
// Thus, here we need only to warn and not to err, allowing the evaluation loop to proceed.
|
2023-07-10 13:38:06 +02:00
|
|
|
logger.warn("Mandatory failure: " + rule);
|
2022-09-16 12:56:52 +02:00
|
|
|
warnings.add(synthesizeFailureMessage(rule));
|
|
|
|
}
|
2023-07-27 13:48:10 +02:00
|
|
|
} else {
|
2022-09-16 12:56:52 +02:00
|
|
|
// This is a warning: a non-mandatory rule has failed.
|
|
|
|
// Note that MA rules are treated as non-mandatory.
|
|
|
|
// We let the evaluation loop proceed.
|
2023-07-10 13:38:06 +02:00
|
|
|
logger.warn("Optional/Recommended failure: " + rule);
|
2022-09-16 12:56:52 +02:00
|
|
|
warnings.add(synthesizeFailureMessage(rule));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int returnedWeight = weight;
|
2023-07-27 13:48:10 +02:00
|
|
|
if ( rules.size() == warnings.size() ) {
|
2022-09-16 12:56:52 +02:00
|
|
|
// TODO: I think this is the desired behavior, but we need to get confirmation for this.
|
|
|
|
// All rules have failed, but with warnings (thus being either optional or recommended).
|
|
|
|
// This indicates a success with 0 weight.
|
|
|
|
returnedWeight = 0;
|
|
|
|
}
|
|
|
|
return StandardResult.forSuccess(returnedWeight, warnings);
|
|
|
|
}
|
|
|
|
|
|
|
|
private String synthesizeFailureMessage(Rule<Document> rule) {
|
|
|
|
return subjectId + ": rule " + Helper.stringify(rule) + " has failed";
|
|
|
|
}
|
|
|
|
|
|
|
|
private String synthesizeNotApplicableMessage(Rule<Document> rule) {
|
|
|
|
return subjectId + ": rule " + Helper.stringify(rule) + " is not applicable";
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void setNodesOf(String ruleId, NodeList nodes) {
|
|
|
|
ruleIdToNodeList.put(ruleId, nodes);
|
|
|
|
}
|
|
|
|
|
|
|
|
NodeList getNodesOf(String ruleId) {
|
|
|
|
return ruleIdToNodeList.get(ruleId);
|
|
|
|
}
|
|
|
|
|
|
|
|
void setRequirementLevelOf(String ruleId, RequirementLevel requirementLevel) {
|
|
|
|
ruleIdToRequirementLevel.put(ruleId, requirementLevel);
|
|
|
|
}
|
|
|
|
|
|
|
|
RequirementLevel getRequirementLevelOf(String ruleId) {
|
|
|
|
return ruleIdToRequirementLevel.get(ruleId);
|
|
|
|
}
|
|
|
|
|
2023-07-14 12:59:26 +02:00
|
|
|
@Override
|
|
|
|
public String toString() {
|
|
|
|
return "GuidelineEvaluation{" +
|
|
|
|
"subjectId='" + subjectId + '\'' +
|
|
|
|
", doc=" + doc +
|
|
|
|
", weight=" + weight +
|
|
|
|
", warnings=" + warnings +
|
|
|
|
", errors=" + errors +
|
|
|
|
", ruleIdToRequirementLevel=" + ruleIdToRequirementLevel +
|
|
|
|
", ruleIdToNodeList=" + ruleIdToNodeList +
|
|
|
|
", diagnostics=" + diagnostics +
|
|
|
|
", reporter=" + reporter +
|
|
|
|
'}';
|
|
|
|
}
|
|
|
|
|
2022-09-16 12:56:52 +02:00
|
|
|
|
|
|
|
private static final class Diagnostics extends StandardRuleDiagnostics<Document, SyntheticRule<Document>> {
|
|
|
|
|
|
|
|
private final Map<String, Status> statusByRuleId = new HashMap<>();
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void success(SyntheticRule<Document> rule, Document document) {
|
|
|
|
OUT.success(rule, document);
|
|
|
|
super.success(rule, document);
|
|
|
|
statusByRuleId.put(rule.getContext().getIdProperty().getValue(), Status.SUCCESS);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void failure(SyntheticRule<Document> rule, Document document) {
|
|
|
|
OUT.failure(rule, document);
|
|
|
|
super.failure(rule, document);
|
|
|
|
statusByRuleId.put(rule.getContext().getIdProperty().getValue(), Status.FAILURE);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void error(SyntheticRule<Document> rule, Document document, Throwable err) {
|
|
|
|
ERR.error(rule, document, err);
|
|
|
|
super.error(rule, document, err);
|
|
|
|
statusByRuleId.put(rule.getContext().getIdProperty().getValue(), Status.ERROR);
|
|
|
|
}
|
|
|
|
|
|
|
|
private Status statusFor(String ruleId) {
|
|
|
|
return statusByRuleId.get(ruleId);
|
|
|
|
}
|
2023-07-14 12:59:26 +02:00
|
|
|
|
|
|
|
@Override
|
|
|
|
public String toString() {
|
|
|
|
return "Diagnostics{" +
|
|
|
|
"statusByRuleId=" + statusByRuleId +
|
|
|
|
'}';
|
|
|
|
}
|
2022-09-16 12:56:52 +02:00
|
|
|
}
|
|
|
|
}
|