ConstraintBindingSupport.java
/*
* SPDX-FileCopyrightText: none
* SPDX-License-Identifier: CC0-1.0
*/
package gov.nist.secauto.metaschema.databind.model.metaschema.impl;
import gov.nist.secauto.metaschema.core.datatype.IDataTypeAdapter;
import gov.nist.secauto.metaschema.core.datatype.markup.MarkupLine;
import gov.nist.secauto.metaschema.core.datatype.markup.MarkupMultiline;
import gov.nist.secauto.metaschema.core.model.ISource;
import gov.nist.secauto.metaschema.core.model.constraint.AbstractConfigurableMessageConstraintBuilder;
import gov.nist.secauto.metaschema.core.model.constraint.AbstractConstraintBuilder;
import gov.nist.secauto.metaschema.core.model.constraint.AbstractKeyConstraintBuilder;
import gov.nist.secauto.metaschema.core.model.constraint.IAllowedValuesConstraint;
import gov.nist.secauto.metaschema.core.model.constraint.ICardinalityConstraint;
import gov.nist.secauto.metaschema.core.model.constraint.IConstraint;
import gov.nist.secauto.metaschema.core.model.constraint.IExpectConstraint;
import gov.nist.secauto.metaschema.core.model.constraint.IIndexConstraint;
import gov.nist.secauto.metaschema.core.model.constraint.IIndexHasKeyConstraint;
import gov.nist.secauto.metaschema.core.model.constraint.IKeyField;
import gov.nist.secauto.metaschema.core.model.constraint.ILet;
import gov.nist.secauto.metaschema.core.model.constraint.IMatchesConstraint;
import gov.nist.secauto.metaschema.core.model.constraint.IModelConstrained;
import gov.nist.secauto.metaschema.core.model.constraint.IUniqueConstraint;
import gov.nist.secauto.metaschema.core.model.constraint.IValueConstrained;
import gov.nist.secauto.metaschema.core.util.ObjectUtils;
import gov.nist.secauto.metaschema.databind.model.metaschema.IConfigurableMessageConstraintBase;
import gov.nist.secauto.metaschema.databind.model.metaschema.IConstraintBase;
import gov.nist.secauto.metaschema.databind.model.metaschema.IModelConstraintsBase;
import gov.nist.secauto.metaschema.databind.model.metaschema.IValueConstraintsBase;
import gov.nist.secauto.metaschema.databind.model.metaschema.IValueTargetedConstraintsBase;
import gov.nist.secauto.metaschema.databind.model.metaschema.binding.ConstraintValueEnum;
import gov.nist.secauto.metaschema.databind.model.metaschema.binding.FlagAllowedValues;
import gov.nist.secauto.metaschema.databind.model.metaschema.binding.FlagExpect;
import gov.nist.secauto.metaschema.databind.model.metaschema.binding.FlagIndexHasKey;
import gov.nist.secauto.metaschema.databind.model.metaschema.binding.FlagMatches;
import gov.nist.secauto.metaschema.databind.model.metaschema.binding.KeyConstraintField;
import gov.nist.secauto.metaschema.databind.model.metaschema.binding.Property;
import gov.nist.secauto.metaschema.databind.model.metaschema.binding.Remarks;
import gov.nist.secauto.metaschema.databind.model.metaschema.binding.TargetedAllowedValuesConstraint;
import gov.nist.secauto.metaschema.databind.model.metaschema.binding.TargetedExpectConstraint;
import gov.nist.secauto.metaschema.databind.model.metaschema.binding.TargetedHasCardinalityConstraint;
import gov.nist.secauto.metaschema.databind.model.metaschema.binding.TargetedIndexConstraint;
import gov.nist.secauto.metaschema.databind.model.metaschema.binding.TargetedIndexHasKeyConstraint;
import gov.nist.secauto.metaschema.databind.model.metaschema.binding.TargetedIsUniqueConstraint;
import gov.nist.secauto.metaschema.databind.model.metaschema.binding.TargetedMatchesConstraint;
import java.math.BigInteger;
import java.util.List;
import java.util.regex.Pattern;
import javax.xml.namespace.QName;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
/**
* Supports parsing constraints declared within a bound object.
*/
@SuppressWarnings("PMD.CouplingBetweenObjects")
public final class ConstraintBindingSupport {
private ConstraintBindingSupport() {
// disable construction
}
/**
* Parse a constraint set.
*
* @param constraintSet
* the parsed constraint set
* @param constraints
* the constraint definitions to parse
* @param source
* the source of the constraints
*/
public static void parse(
@NonNull IValueConstrained constraintSet,
@NonNull IValueConstraintsBase constraints,
@NonNull ISource source) {
parseLet(constraintSet, constraints, source);
// parse rules
for (IConstraintBase ruleObj : constraints.getRules()) {
if (ruleObj instanceof FlagAllowedValues) {
IAllowedValuesConstraint constraint = newAllowedValues((FlagAllowedValues) ruleObj, source);
constraintSet.addConstraint(constraint);
} else if (ruleObj instanceof FlagExpect) {
IExpectConstraint constraint = newExpect((FlagExpect) ruleObj, source);
constraintSet.addConstraint(constraint);
} else if (ruleObj instanceof FlagIndexHasKey) {
IIndexHasKeyConstraint constraint = newIndexHasKey((FlagIndexHasKey) ruleObj, source);
constraintSet.addConstraint(constraint);
} else if (ruleObj instanceof FlagMatches) {
IMatchesConstraint constraint = newMatches((FlagMatches) ruleObj, source);
constraintSet.addConstraint(constraint);
}
}
}
/**
* Parse a constraint set.
*
* @param constraintSet
* the parsed constraint set
* @param constraints
* the constraint definitions to parse
* @param source
* the source of the constraints
*/
public static void parse(
@NonNull IValueConstrained constraintSet,
@NonNull IValueTargetedConstraintsBase constraints,
@NonNull ISource source) {
parseLet(constraintSet, constraints, source);
// parse rules
for (IConstraintBase ruleObj : constraints.getRules()) {
if (ruleObj instanceof TargetedAllowedValuesConstraint) {
IAllowedValuesConstraint constraint = newAllowedValues((TargetedAllowedValuesConstraint) ruleObj, source);
constraintSet.addConstraint(constraint);
} else if (ruleObj instanceof TargetedExpectConstraint) {
IExpectConstraint constraint = newExpect((TargetedExpectConstraint) ruleObj, source);
constraintSet.addConstraint(constraint);
} else if (ruleObj instanceof TargetedIndexHasKeyConstraint) {
IIndexHasKeyConstraint constraint = newIndexHasKey((TargetedIndexHasKeyConstraint) ruleObj, source);
constraintSet.addConstraint(constraint);
} else if (ruleObj instanceof TargetedMatchesConstraint) {
IMatchesConstraint constraint = newMatches((TargetedMatchesConstraint) ruleObj, source);
constraintSet.addConstraint(constraint);
}
}
}
/**
* Parse a constraint set.
*
* @param constraintSet
* the parsed constraint set
* @param constraints
* the constraint definitions to parse
* @param source
* the source of the constraints
*/
public static void parse(
@NonNull IModelConstrained constraintSet,
@NonNull IModelConstraintsBase constraints,
@NonNull ISource source) {
parseLet(constraintSet, constraints, source);
// parse rules
for (IConstraintBase ruleObj : constraints.getRules()) {
if (ruleObj instanceof TargetedAllowedValuesConstraint) {
IAllowedValuesConstraint constraint = newAllowedValues((TargetedAllowedValuesConstraint) ruleObj, source);
constraintSet.addConstraint(constraint);
} else if (ruleObj instanceof TargetedExpectConstraint) {
IExpectConstraint constraint = newExpect((TargetedExpectConstraint) ruleObj, source);
constraintSet.addConstraint(constraint);
} else if (ruleObj instanceof TargetedIndexHasKeyConstraint) {
IIndexHasKeyConstraint constraint = newIndexHasKey((TargetedIndexHasKeyConstraint) ruleObj, source);
constraintSet.addConstraint(constraint);
} else if (ruleObj instanceof TargetedMatchesConstraint) {
IMatchesConstraint constraint = newMatches((TargetedMatchesConstraint) ruleObj, source);
constraintSet.addConstraint(constraint);
} else if (ruleObj instanceof TargetedIndexConstraint) {
IIndexConstraint constraint = newIndex((TargetedIndexConstraint) ruleObj, source);
constraintSet.addConstraint(constraint);
} else if (ruleObj instanceof TargetedHasCardinalityConstraint) {
ICardinalityConstraint constraint = newHasCardinality((TargetedHasCardinalityConstraint) ruleObj, source);
constraintSet.addConstraint(constraint);
} else if (ruleObj instanceof TargetedIsUniqueConstraint) {
IUniqueConstraint constraint = newUnique((TargetedIsUniqueConstraint) ruleObj, source);
constraintSet.addConstraint(constraint);
}
}
}
/**
* Parse the let clause in a constraint set.
*
* @param constraintSet
* the parsed constraint set
* @param constraints
* the constraint definitions to parse
* @param source
* the source of the constraint
*/
public static void parseLet(
@NonNull IValueConstrained constraintSet,
@NonNull IValueConstraintsBase constraints,
@NonNull ISource source) {
// parse let expressions
constraints.getLets().stream()
.map(letObj -> {
MarkupMultiline remarks = null;
Remarks remarkObj = letObj.getRemarks();
if (remarkObj != null) {
remarks = remarkObj.getRemark();
}
return ILet.of(
ObjectUtils.requireNonNull(new QName(letObj.getVar())),
ObjectUtils.requireNonNull(letObj.getExpression()),
source,
remarks);
})
.forEachOrdered(constraintSet::addLetExpression);
}
@NonNull
private static IAllowedValuesConstraint newAllowedValues(
@NonNull FlagAllowedValues obj,
@NonNull ISource source) {
IAllowedValuesConstraint.Builder builder = IAllowedValuesConstraint.builder()
.allowsOther(ModelSupport.yesOrNo(obj.getAllowOther()))
.extensible(extensible(obj.getExtensible()));
applyCommonValues(obj, null, source, builder);
for (ConstraintValueEnum value : ObjectUtils.requireNonNull(obj.getEnums())) {
builder.allowedValue(ObjectUtils.requireNonNull(value));
}
return builder.build();
}
@NonNull
private static IAllowedValuesConstraint newAllowedValues(
@NonNull TargetedAllowedValuesConstraint obj,
@NonNull ISource source) {
IAllowedValuesConstraint.Builder builder = IAllowedValuesConstraint.builder()
.allowsOther(ModelSupport.yesOrNo(obj.getAllowOther()))
.extensible(extensible(ObjectUtils.requireNonNull(obj.getExtensible())));
applyCommonValues(obj, obj.getTarget(), source, builder);
for (ConstraintValueEnum value : ObjectUtils.requireNonNull(obj.getEnums())) {
builder.allowedValue(ObjectUtils.requireNonNull(value));
}
return builder.build();
}
@NonNull
private static IExpectConstraint newExpect(
@NonNull FlagExpect obj,
@NonNull ISource source) {
IExpectConstraint.Builder builder = IExpectConstraint.builder()
.test(target(ObjectUtils.requireNonNull(obj.getTest())));
applyConfigurableCommonValues(obj, null, source, builder);
String message = obj.getMessage();
if (message != null) {
builder.message(message);
}
return builder.build();
}
@NonNull
private static IExpectConstraint newExpect(
@NonNull TargetedExpectConstraint obj,
@NonNull ISource source) {
IExpectConstraint.Builder builder = IExpectConstraint.builder()
.test(target(ObjectUtils.requireNonNull(obj.getTest())));
applyConfigurableCommonValues(obj, obj.getTarget(), source, builder);
return builder.build();
}
@NonNull
private static <T extends AbstractKeyConstraintBuilder<T, ?>> T handleKeyConstraints(
@NonNull List<KeyConstraintField> keys,
@NonNull T builder,
@NonNull ISource source) {
for (KeyConstraintField value : keys) {
assert value != null;
IKeyField keyField = IKeyField.of(
target(ObjectUtils.requireNonNull(value.getTarget())),
pattern(value.getPattern()),
ModelSupport.remarks(value.getRemarks()),
source);
builder.keyField(keyField);
}
return builder;
}
@NonNull
private static IIndexHasKeyConstraint newIndexHasKey(
@NonNull FlagIndexHasKey obj,
@NonNull ISource source) {
IIndexHasKeyConstraint.Builder builder = IIndexHasKeyConstraint.builder(ObjectUtils.requireNonNull(obj.getName()));
applyConfigurableCommonValues(obj, null, source, builder);
handleKeyConstraints(ObjectUtils.requireNonNull(obj.getKeyFields()), builder, source);
return builder.build();
}
@NonNull
private static IIndexHasKeyConstraint newIndexHasKey(
@NonNull TargetedIndexHasKeyConstraint obj,
@NonNull ISource source) {
IIndexHasKeyConstraint.Builder builder = IIndexHasKeyConstraint.builder(ObjectUtils.requireNonNull(obj.getName()));
applyConfigurableCommonValues(obj, obj.getTarget(), source, builder);
handleKeyConstraints(ObjectUtils.requireNonNull(obj.getKeyFields()), builder, source);
return builder.build();
}
@NonNull
private static IMatchesConstraint newMatches(
@NonNull FlagMatches obj,
@NonNull ISource source) {
IMatchesConstraint.Builder builder = IMatchesConstraint.builder();
applyConfigurableCommonValues(obj, null, source, builder);
Pattern regex = pattern(obj.getRegex());
if (regex != null) {
builder.regex(regex);
}
String dataType = obj.getDatatype();
if (dataType != null) {
IDataTypeAdapter<?> javaTypeAdapter = ModelSupport.dataType(obj.getDatatype());
builder.datatype(javaTypeAdapter);
}
return builder.build();
}
@NonNull
private static IMatchesConstraint newMatches(
@NonNull TargetedMatchesConstraint obj,
@NonNull ISource source) {
IMatchesConstraint.Builder builder = IMatchesConstraint.builder();
applyConfigurableCommonValues(obj, obj.getTarget(), source, builder);
Pattern regex = pattern(obj.getRegex());
if (regex != null) {
builder.regex(regex);
}
String dataType = obj.getDatatype();
if (dataType != null) {
IDataTypeAdapter<?> javaTypeAdapter = ModelSupport.dataType(obj.getDatatype());
builder.datatype(javaTypeAdapter);
}
return builder.build();
}
@NonNull
private static IIndexConstraint newIndex(
@NonNull TargetedIndexConstraint obj,
@NonNull ISource source) {
IIndexConstraint.Builder builder = IIndexConstraint.builder(ObjectUtils.requireNonNull(obj.getName()));
applyConfigurableCommonValues(obj, obj.getTarget(), source, builder);
handleKeyConstraints(ObjectUtils.requireNonNull(obj.getKeyFields()), builder, source);
return builder.build();
}
@NonNull
private static ICardinalityConstraint newHasCardinality(
@NonNull TargetedHasCardinalityConstraint obj,
@NonNull ISource source) {
ICardinalityConstraint.Builder builder = ICardinalityConstraint.builder();
applyConfigurableCommonValues(obj, obj.getTarget(), source, builder);
BigInteger minOccurs = obj.getMinOccurs();
if (minOccurs != null) {
builder.minOccurs(minOccurs.intValueExact());
}
String maxOccurs = obj.getMaxOccurs();
if (maxOccurs != null) {
int occurance = ModelSupport.maxOccurs(maxOccurs);
builder.maxOccurs(occurance);
}
return builder.build();
}
@NonNull
private static IUniqueConstraint newUnique(
@NonNull TargetedIsUniqueConstraint obj,
@NonNull ISource source) {
IUniqueConstraint.Builder builder = IUniqueConstraint.builder();
applyConfigurableCommonValues(obj, obj.getTarget(), source, builder);
handleKeyConstraints(ObjectUtils.requireNonNull(obj.getKeyFields()), builder, source);
return builder.build();
}
@NonNull
private static <T extends AbstractConfigurableMessageConstraintBuilder<T, ?>> T applyConfigurableCommonValues(
@NonNull IConfigurableMessageConstraintBase constraint,
@Nullable String target,
@NonNull ISource source,
@NonNull T builder) {
applyCommonValues(constraint, target, source, builder);
String message = constraint.getMessage();
if (message != null) {
builder.message(message);
}
return builder;
}
@NonNull
private static <T extends AbstractConstraintBuilder<T, ?>> T applyCommonValues(
@NonNull IConstraintBase constraint,
@Nullable String target,
@NonNull ISource source,
@NonNull T builder) {
String id = constraint.getId();
if (id != null) {
builder.identifier(id);
}
String formalName = constraint.getFormalName();
if (formalName != null) {
builder.formalName(formalName);
}
MarkupLine description = constraint.getDescription();
if (description != null) {
builder.description(description);
}
List<Property> props = ObjectUtils.requireNonNull(constraint.getProps());
builder.properties(ModelSupport.parseProperties(props));
Remarks remarks = constraint.getRemarks();
if (remarks != null) {
builder.remarks(ObjectUtils.notNull(remarks.getRemark()));
}
builder.target(target(target));
builder.level(level(constraint.getLevel()));
builder.source(source);
return builder;
}
@NonNull
private static String target(@Nullable String target) {
return target == null
? IConstraint.DEFAULT_TARGET_METAPATH
: target;
}
@NonNull
private static IConstraint.Level level(@Nullable String level) {
IConstraint.Level retval = IConstraint.DEFAULT_LEVEL;
if (level != null) {
switch (level) {
case "CRITICAL":
retval = IConstraint.Level.CRITICAL;
break;
case "ERROR":
retval = IConstraint.Level.ERROR;
break;
case "WARNING":
retval = IConstraint.Level.WARNING;
break;
case "INFORMATIONAL":
retval = IConstraint.Level.INFORMATIONAL;
break;
case "DEBUG":
retval = IConstraint.Level.DEBUG;
break;
default:
throw new UnsupportedOperationException(level);
}
}
return retval;
}
@NonNull
private static IAllowedValuesConstraint.Extensible extensible(@Nullable String extensible) {
IAllowedValuesConstraint.Extensible retval = IAllowedValuesConstraint.EXTENSIBLE_DEFAULT;
if (extensible != null) {
switch (extensible) {
case "model":
retval = IAllowedValuesConstraint.Extensible.MODEL;
break;
case "external":
retval = IAllowedValuesConstraint.Extensible.EXTERNAL;
break;
case "none":
retval = IAllowedValuesConstraint.Extensible.NONE;
break;
default:
throw new UnsupportedOperationException(extensible);
}
}
return retval;
}
@Nullable
private static Pattern pattern(@Nullable String pattern) {
return pattern == null ? null : Pattern.compile(pattern);
}
}