FindingCollectingConstraintValidationHandler.java
/*
* SPDX-FileCopyrightText: none
* SPDX-License-Identifier: CC0-1.0
*/
package gov.nist.secauto.metaschema.core.model.constraint;
import gov.nist.secauto.metaschema.core.datatype.IDataTypeAdapter;
import gov.nist.secauto.metaschema.core.metapath.DynamicContext;
import gov.nist.secauto.metaschema.core.metapath.ISequence;
import gov.nist.secauto.metaschema.core.metapath.MetapathException;
import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem;
import gov.nist.secauto.metaschema.core.model.constraint.IConstraint.Level;
import gov.nist.secauto.metaschema.core.model.validation.IValidationFinding.Kind;
import gov.nist.secauto.metaschema.core.model.validation.IValidationResult;
import gov.nist.secauto.metaschema.core.util.CollectionUtil;
import gov.nist.secauto.metaschema.core.util.ObjectUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Pattern;
import edu.umd.cs.findbugs.annotations.NonNull;
/**
* A validation result handler that collects the resulting findings for later
* retrieval using the {@link #getFindings()} method.
* <p>
* This class is not thread safe.
*/
@SuppressWarnings("PMD.CouplingBetweenObjects")
public class FindingCollectingConstraintValidationHandler
extends AbstractConstraintValidationHandler
implements IValidationResult {
private static final Logger LOGGER = LogManager.getLogger(FindingCollectingConstraintValidationHandler.class);
@NonNull
private final List<ConstraintValidationFinding> findings = new LinkedList<>();
@NonNull
private Level highestLevel = IConstraint.Level.INFORMATIONAL;
@Override
@NonNull
public List<ConstraintValidationFinding> getFindings() {
return CollectionUtil.unmodifiableList(findings);
}
@Override
@NonNull
public Level getHighestSeverity() {
return highestLevel;
}
/**
* Add a finding to the collection of findings maintained by this instance.
*
* @param finding
* the finding to add
*/
protected void addFinding(@NonNull ConstraintValidationFinding finding) {
findings.add(finding);
Level severity = finding.getSeverity();
if (severity.ordinal() > highestLevel.ordinal()) {
highestLevel = severity;
}
}
@NonNull
private static Kind toKind(@NonNull Level level) {
Kind retval;
switch (level) {
case CRITICAL:
case ERROR:
retval = Kind.FAIL;
break;
case INFORMATIONAL:
case DEBUG:
case NONE:
retval = Kind.INFORMATIONAL;
break;
case WARNING:
retval = Kind.PASS;
break;
default:
throw new IllegalArgumentException(String.format("Unsupported level '%s'.", level));
}
return retval;
}
@Override
public void handleCardinalityMinimumViolation(
@NonNull ICardinalityConstraint constraint,
@NonNull INodeItem target,
@NonNull ISequence<? extends INodeItem> testedItems,
@NonNull DynamicContext dynamicContext) {
addFinding(ConstraintValidationFinding.builder(constraint, target)
.severity(constraint.getLevel())
.kind(toKind(constraint.getLevel()))
.target(target)
.subjects(testedItems.getValue())
.message(newCardinalityMinimumViolationMessage(constraint, target, testedItems, dynamicContext))
.build());
}
@Override
public void handleCardinalityMaximumViolation(
@NonNull ICardinalityConstraint constraint,
@NonNull INodeItem target,
@NonNull ISequence<? extends INodeItem> testedItems,
@NonNull DynamicContext dynamicContext) {
addFinding(ConstraintValidationFinding.builder(constraint, target)
.severity(constraint.getLevel())
.kind(toKind(constraint.getLevel()))
.target(target)
.subjects(testedItems.getValue())
.message(newCardinalityMaximumViolationMessage(constraint, target, testedItems, dynamicContext))
.build());
}
@Override
public void handleIndexDuplicateKeyViolation(
@NonNull IIndexConstraint constraint,
@NonNull INodeItem node,
@NonNull INodeItem oldItem,
@NonNull INodeItem target,
@NonNull DynamicContext dynamicContext) {
addFinding(ConstraintValidationFinding.builder(constraint, node)
.severity(constraint.getLevel())
.kind(toKind(constraint.getLevel()))
.target(target)
.message(newIndexDuplicateKeyViolationMessage(constraint, node, oldItem, target, dynamicContext))
.build());
}
@Override
public void handleUniqueKeyViolation(
@NonNull IUniqueConstraint constraint,
@NonNull INodeItem node,
@NonNull INodeItem oldItem,
@NonNull INodeItem target,
@NonNull DynamicContext dynamicContext) {
addFinding(ConstraintValidationFinding.builder(constraint, node)
.severity(constraint.getLevel())
.kind(toKind(constraint.getLevel()))
.target(target)
.message(newUniqueKeyViolationMessage(constraint, node, oldItem, target, dynamicContext))
.build());
}
@SuppressWarnings("null")
@Override
public void handleKeyMatchError(
@NonNull IKeyConstraint constraint,
@NonNull INodeItem node,
@NonNull INodeItem target,
@NonNull MetapathException cause,
@NonNull DynamicContext dynamicContext) {
addFinding(ConstraintValidationFinding.builder(constraint, node)
.severity(constraint.getLevel())
.kind(toKind(constraint.getLevel()))
.target(target)
.message(cause.getLocalizedMessage())
.cause(cause)
.build());
}
@Override
public void handleMatchPatternViolation(
@NonNull IMatchesConstraint constraint,
@NonNull INodeItem node,
@NonNull INodeItem target,
@NonNull String value,
@NonNull Pattern pattern,
@NonNull DynamicContext dynamicContext) {
addFinding(ConstraintValidationFinding.builder(constraint, node)
.severity(constraint.getLevel())
.kind(toKind(constraint.getLevel()))
.target(target)
.message(newMatchPatternViolationMessage(constraint, node, target, value, pattern, dynamicContext))
.build());
}
@Override
public void handleMatchDatatypeViolation(
@NonNull IMatchesConstraint constraint,
@NonNull INodeItem node,
@NonNull INodeItem target,
@NonNull String value,
@NonNull IDataTypeAdapter<?> adapter,
@NonNull IllegalArgumentException cause,
@NonNull DynamicContext dynamicContext) {
addFinding(ConstraintValidationFinding.builder(constraint, node)
.severity(constraint.getLevel())
.kind(toKind(constraint.getLevel()))
.target(target)
.message(newMatchDatatypeViolationMessage(constraint, node, target, value, adapter, dynamicContext))
.cause(cause)
.build());
}
@Override
public void handleExpectViolation(
@NonNull IExpectConstraint constraint,
@NonNull INodeItem node,
@NonNull INodeItem target,
@NonNull DynamicContext dynamicContext) {
addFinding(ConstraintValidationFinding.builder(constraint, node)
.severity(constraint.getLevel())
.kind(toKind(constraint.getLevel()))
.target(target)
.message(newExpectViolationMessage(constraint, node, target, dynamicContext))
.build());
}
@Override
public void handleAllowedValuesViolation(
@NonNull List<IAllowedValuesConstraint> failedConstraints,
@NonNull INodeItem target,
@NonNull DynamicContext dynamicContext) {
Level maxLevel = ObjectUtils.notNull(failedConstraints.stream()
.map(IAllowedValuesConstraint::getLevel)
.reduce(Level.NONE, (l1, l2) -> l1.ordinal() >= l2.ordinal() ? l1 : l2));
addFinding(ConstraintValidationFinding.builder(failedConstraints, target)
.severity(maxLevel)
.kind(toKind(maxLevel))
.target(target)
.message(newAllowedValuesViolationMessage(failedConstraints, target))
.build());
}
@Override
public void handleIndexDuplicateViolation(
IIndexConstraint constraint,
INodeItem node,
@NonNull DynamicContext dynamicContext) {
addFinding(ConstraintValidationFinding.builder(constraint, node)
.kind(Kind.FAIL)
.severity(Level.CRITICAL)
.target(node)
.message(newIndexDuplicateViolationMessage(constraint, node))
.build());
}
@Override
public void handleIndexMiss(
@NonNull IIndexHasKeyConstraint constraint,
@NonNull INodeItem node,
@NonNull INodeItem target,
@NonNull List<String> key,
@NonNull DynamicContext dynamicContext) {
addFinding(ConstraintValidationFinding.builder(constraint, node)
.severity(constraint.getLevel())
.kind(toKind(constraint.getLevel()))
.target(target)
.message(newIndexMissMessage(constraint, node, target, key, dynamicContext))
.build());
}
@Override
public void handleMissingIndexViolation(
@NonNull IIndexHasKeyConstraint constraint,
@NonNull INodeItem node,
@NonNull INodeItem target,
@NonNull String message,
@NonNull DynamicContext dynamicContext) {
addFinding(ConstraintValidationFinding.builder(constraint, node)
.severity(constraint.getLevel())
.kind(toKind(constraint.getLevel()))
.target(target)
.message(newMissingIndexViolationMessage(constraint, node, target, message, dynamicContext))
.build());
}
@Override
public void handlePass(
@NonNull IConstraint constraint,
@NonNull INodeItem node,
@NonNull INodeItem target,
@NonNull DynamicContext dynamicContext) {
addFinding(ConstraintValidationFinding.builder(constraint, node)
.kind(Kind.PASS)
.severity(Level.NONE)
.target(target)
.build());
}
@Override
public void handleError(
@NonNull IConstraint constraint,
@NonNull INodeItem node,
@NonNull String message,
@NonNull Throwable exception,
@NonNull DynamicContext dynamicContext) {
LOGGER.atError().withThrowable(exception).log(message);
addFinding(ConstraintValidationFinding.builder(constraint, node)
.kind(Kind.FAIL)
.severity(Level.CRITICAL)
.target(node)
.message(message)
.cause(exception)
.build());
}
}