uoa-validator-engine2/src/main/java/eu/dnetlib/validator2/validation/guideline/GuidelineEvaluation.java

203 lines
8.5 KiB
Java

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<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);
logger.debug("Evaluating " + rules);
for ( SyntheticRule<Document> rule: rules ) {
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());
}
String id = rule.getContext().getIdProperty().getValue();
RequirementLevel requirementLevel = getRequirementLevelOf(id);
if ( status == Status.SUCCESS
&& (requirementLevel == 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 ( requirementLevel == RequirementLevel.MANDATORY
|| requirementLevel == RequirementLevel.MANDATORY_IF_APPLICABLE )
{
// 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.
if ( requirementLevel == RequirementLevel.MANDATORY )
logger.warn("Mandatory failure: " + rule);
else
logger.warn("Mandatory_If_Applicable 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.
if ( requirementLevel == RequirementLevel.RECOMMENDED )
logger.warn("Recommended failure: " + rule);
else if ( requirementLevel == RequirementLevel.OPTIONAL )
logger.warn("Optional failure: " + rule);
else // This should never happen, but better catch this case, than leave it be.
logger.error("UNKNOWN failure: " + rule); // It is not "Mandatory", in any form, though.
warnings.add(synthesizeFailureMessage(rule));
}
}
// Else it is just "SUCCESS" for an applicable rule, which means there is no error or warning to create.
}
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<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);
}
@Override
public String toString() {
return "GuidelineEvaluation{" +
"subjectId='" + subjectId + '\'' +
", doc=" + doc +
", weight=" + weight +
", warnings=" + warnings +
", errors=" + errors +
", ruleIdToRequirementLevel=" + ruleIdToRequirementLevel +
", ruleIdToNodeList=" + ruleIdToNodeList +
", diagnostics=" + diagnostics +
", reporter=" + reporter +
'}';
}
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);
}
@Override
public String toString() {
return "Diagnostics{" +
"statusByRuleId=" + statusByRuleId +
'}';
}
}
}