package eu.dnetlib.validator2.validation.guideline; import eu.dnetlib.validator2.engine.*; import eu.dnetlib.validator2.engine.builtins.StandardRuleDiagnostics; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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 { private static final Logger logger = LoggerFactory.getLogger(GuidelineEvaluation.class); // TODO: Report all rule diagnostics to System out/err (we should remove this or allow clients to disable it) private static final RuleDiagnostics> OUT = Helper.Diagnostics.systemOut(); private static final RuleDiagnostics> ERR = Helper.Diagnostics.systemErr(); private final String subjectId; private final Document doc; private final int weight; private final List warnings = new ArrayList<>(); private final List errors = new ArrayList<>(); private Map ruleIdToRequirementLevel = new HashMap<>(); private Map ruleIdToNodeList = new HashMap<>(); private final Diagnostics diagnostics = new Diagnostics(); private final Reporter> 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> rules = new ArrayList<>(); rules.add(result.rootNodeRule); rules.addAll(result.nodeRules); logger.debug("Evaluating " + rules); for (SyntheticRule rule: rules) { String id = rule.getContext().getIdProperty().getValue(); RuleEngine.applyAndReport(rule, doc, reporter); Status status = diagnostics.getLastReportedStatus(); if (status == Status.ERROR) { // fail fast in case of errors (no reason to proceed, since results may be rubbish) return StandardResult.forError(diagnostics.getLastReportedError().getMessage()); } if (status == Status.SUCCESS && getRequirementLevelOf(id) == RequirementLevel.NOT_APPLICABLE) { // Report the non-applicability of a rule as a warning // The check for both status and non-applicable requirement level is redundant // (non-applicable rules always succeed), yet it is more clear hopefully. logger.warn("Non-applicable: " + rule); warnings.add(synthesizeFailureMessage(rule)); } else if (status == Status.FAILURE) { if (getRequirementLevelOf(id) == RequirementLevel.MANDATORY) { // 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. logger.error("Fail fast for root failure: " + rule); errors.add(synthesizeFailureMessage(rule)); return StandardResult.forFailure(warnings, errors); } else { // 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. logger.warn("Mandatory failure: " + rule); warnings.add(synthesizeFailureMessage(rule)); } } else { // 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. logger.warn("Optional/Recommended failure: " + rule); warnings.add(synthesizeFailureMessage(rule)); } } } int returnedWeight = weight; if (rules.size() == warnings.size()) { // 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 rule) { return subjectId + ": rule " + Helper.stringify(rule) + " has failed"; } private String synthesizeNotApplicableMessage(Rule 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); } private static final class Diagnostics extends StandardRuleDiagnostics> { private final Map statusByRuleId = new HashMap<>(); @Override public void success(SyntheticRule rule, Document document) { OUT.success(rule, document); super.success(rule, document); statusByRuleId.put(rule.getContext().getIdProperty().getValue(), Status.SUCCESS); } @Override public void failure(SyntheticRule rule, Document document) { OUT.failure(rule, document); super.failure(rule, document); statusByRuleId.put(rule.getContext().getIdProperty().getValue(), Status.FAILURE); } @Override public void error(SyntheticRule 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); } } }