From c782f23aff2a6f4ca049fd9023f69e16d6e4835b Mon Sep 17 00:00:00 2001 From: David Waltermire Date: Sun, 27 Oct 2024 17:22:14 -0400 Subject: [PATCH] Added support for custom constraint messages in all but allowed values constraints (see metaschema-framework/metaschema#43). Resolves #215. --- core/metaschema | 2 +- ...tConfigurableMessageConstraintBuilder.java | 52 +++++ .../constraint/AbstractConstraintBuilder.java | 25 --- .../AbstractConstraintValidationHandler.java | 184 +++++++++++------- .../AbstractKeyConstraintBuilder.java | 2 +- .../DefaultConstraintValidator.java | 108 +++++----- ...CollectingConstraintValidationHandler.java | 98 ++++++---- .../constraint/IAllowedValuesConstraint.java | 1 - .../constraint/ICardinalityConstraint.java | 4 +- .../IConfigurableMessageConstraint.java | 45 +++++ .../core/model/constraint/IConstraint.java | 26 --- .../IConstraintValidationHandler.java | 107 +++++++--- .../model/constraint/IExpectConstraint.java | 4 +- .../core/model/constraint/IKeyConstraint.java | 2 +- .../model/constraint/IMatchesConstraint.java | 4 +- .../LoggingConstraintValidationHandler.java | 177 ++++++++++++++--- ...AbstractConfigurableMessageConstraint.java | 97 +++++++++ .../constraint/impl/AbstractConstraint.java | 31 --- .../impl/AbstractKeyConstraint.java | 2 +- .../impl/DefaultAllowedValuesConstraint.java | 5 +- .../impl/DefaultCardinalityConstraint.java | 2 +- .../impl/DefaultExpectConstraint.java | 2 +- .../impl/DefaultMatchesConstraint.java | 2 +- .../model/xml/impl/ConstraintXmlSupport.java | 3 + .../codegen/impl/AnnotationGenerator.java | 5 - .../io/json/MetaschemaJsonReader.java | 6 +- .../model/annotations/AllowedValues.java | 8 - .../model/impl/ConstraintFactory.java | 5 +- .../metaschema/BindingConstraintLoader.java | 3 +- .../IConfigurableMessageConstraintBase.java | 24 +++ .../model/metaschema/IConstraintBase.java | 11 -- .../metaschema/binding/FlagAllowedValues.java | 14 -- .../model/metaschema/binding/FlagExpect.java | 4 +- .../metaschema/binding/FlagIndexHasKey.java | 4 +- .../model/metaschema/binding/FlagMatches.java | 4 +- .../TargetedAllowedValuesConstraint.java | 14 -- .../binding/TargetedExpectConstraint.java | 4 +- .../TargetedHasCardinalityConstraint.java | 4 +- .../binding/TargetedIndexConstraint.java | 4 +- .../TargetedIndexHasKeyConstraint.java | 4 +- .../binding/TargetedIsUniqueConstraint.java | 4 +- .../binding/TargetedMatchesConstraint.java | 4 +- .../impl/ConstraintBindingSupport.java | 40 ++-- 43 files changed, 755 insertions(+), 396 deletions(-) create mode 100644 core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/AbstractConfigurableMessageConstraintBuilder.java create mode 100644 core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IConfigurableMessageConstraint.java create mode 100644 core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/impl/AbstractConfigurableMessageConstraint.java create mode 100644 databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/IConfigurableMessageConstraintBase.java diff --git a/core/metaschema b/core/metaschema index fbd6dd7c7..20c107794 160000 --- a/core/metaschema +++ b/core/metaschema @@ -1 +1 @@ -Subproject commit fbd6dd7c78ef7a73c47a17675e2cc6afc91683f5 +Subproject commit 20c107794327ff3c2cc39ce98f462c8c0791d916 diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/AbstractConfigurableMessageConstraintBuilder.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/AbstractConfigurableMessageConstraintBuilder.java new file mode 100644 index 000000000..0d1867d81 --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/AbstractConfigurableMessageConstraintBuilder.java @@ -0,0 +1,52 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.model.constraint; + +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; + +/** + * Provides builder methods for the core data elements of an + * {@link IConstraint}. + *

+ * The base class of all constraint builders. + * + * @param + * the Java type of the implementing builder + * @param + * the Java type of the resulting built object + * @since 2.0.0 + */ +public abstract class AbstractConfigurableMessageConstraintBuilder< + T extends AbstractConfigurableMessageConstraintBuilder, + R extends IConfigurableMessageConstraint> + extends AbstractConstraintBuilder { + private String message; + + /** + * A message to emit when the constraint is violated. Allows embedded Metapath + * expressions using the syntax {@code \{ metapath \}}. + * + * @param message + * the message if defined or {@code null} otherwise + * @return this builder + */ + @NonNull + public T message(@NonNull String message) { + this.message = message; + return getThis(); + } + + /** + * Get the constraint message provided to the builder. + * + * @return the message or {@code null} if no message is set + */ + @Nullable + protected String getMessage() { + return message; + } +} diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/AbstractConstraintBuilder.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/AbstractConstraintBuilder.java index 069ee502a..d2f49bc4e 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/AbstractConstraintBuilder.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/AbstractConstraintBuilder.java @@ -44,7 +44,6 @@ public abstract class AbstractConstraintBuilder< private String target = IConstraint.DEFAULT_TARGET_METAPATH; @NonNull private Map> properties = new LinkedHashMap<>(); // NOPMD not thread safe - private String message; private MarkupMultiline remarks; /** @@ -180,20 +179,6 @@ public T property(@NonNull IAttributable.Key key, @NonNull Set values) { return getThis(); } - /** - * A message to emit when the constraint is violated. Allows embedded Metapath - * expressions using the syntax {@code \{ metapath \}}. - * - * @param message - * the message if defined or {@code null} otherwise - * @return this builder - */ - @NonNull - public T message(@NonNull String message) { - this.message = message; - return getThis(); - } - /** * Set the provided {@code remarks}. * @@ -309,16 +294,6 @@ protected Map> getProperties() { return properties; } - /** - * Get the constraint message provided to the builder. - * - * @return the message or {@code null} if no message is set - */ - @Nullable - protected String getMessage() { - return message; - } - /** * Get the remarks provided to the builder. * diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/AbstractConstraintValidationHandler.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/AbstractConstraintValidationHandler.java index 0ac063366..3e718ee63 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/AbstractConstraintValidationHandler.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/AbstractConstraintValidationHandler.java @@ -12,6 +12,7 @@ import gov.nist.secauto.metaschema.core.metapath.function.library.FnData; import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem; import gov.nist.secauto.metaschema.core.util.CustomCollectors; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; import java.util.List; import java.util.Objects; @@ -67,23 +68,28 @@ protected String toPath(@NonNull INodeItem item) { * * @param constraint * the constraint the requested message pertains to - * @param node + * @param target * the item the constraint targeted - * @param targets - * the targets matching the constraint + * @param testedItems + * the items tested by the constraint + * @param dynamicContext + * the Metapath dynamic execution context to use for Metapath + * evaluation * @return the new message */ - @SuppressWarnings("null") @NonNull protected String newCardinalityMinimumViolationMessage( @NonNull ICardinalityConstraint constraint, - @NonNull INodeItem node, - @NonNull ISequence targets) { - return String.format( - "The cardinality '%d' is below the required minimum '%d' for items matching '%s'.", - targets.size(), - constraint.getMinOccurs(), - constraint.getTarget()); + @NonNull INodeItem target, + @NonNull ISequence testedItems, + @NonNull DynamicContext dynamicContext) { + return constraint.getMessage() == null + ? ObjectUtils.notNull(String.format( + "The cardinality '%d' is below the required minimum '%d' for items matching '%s'.", + testedItems.size(), + constraint.getMinOccurs(), + constraint.getTarget())) + : constraint.generateMessage(target, dynamicContext); } /** @@ -92,29 +98,34 @@ protected String newCardinalityMinimumViolationMessage( * * @param constraint * the constraint the requested message pertains to - * @param node + * @param target * the item the constraint targeted - * @param targets - * the targets matching the constraint + * @param testedItems + * the items tested by the constraint + * @param dynamicContext + * the Metapath dynamic execution context to use for Metapath + * evaluation * @return the new message */ - @SuppressWarnings("null") @NonNull protected String newCardinalityMaximumViolationMessage( @NonNull ICardinalityConstraint constraint, - @NonNull INodeItem node, - @NonNull ISequence targets) { - return String.format( - "The cardinality '%d' is greater than the required maximum '%d' at: %s.", - targets.size(), - constraint.getMinOccurs(), - targets.safeStream() - .map(item -> new StringBuilder(12) - .append('\'') - .append(toPath(item)) - .append('\'') - .toString()) - .collect(CustomCollectors.joiningWithOxfordComma("and"))); + @NonNull INodeItem target, + @NonNull ISequence testedItems, + @NonNull DynamicContext dynamicContext) { + return constraint.getMessage() == null + ? ObjectUtils.notNull(String.format( + "The cardinality '%d' is greater than the required maximum '%d' at: %s.", + testedItems.size(), + constraint.getMinOccurs(), + testedItems.safeStream() + .map(item -> new StringBuilder(12) + .append('\'') + .append(toPath(ObjectUtils.notNull(item))) + .append('\'') + .toString()) + .collect(CustomCollectors.joiningWithOxfordComma("and")))) + : constraint.generateMessage(target, dynamicContext); } /** @@ -127,22 +138,27 @@ protected String newCardinalityMaximumViolationMessage( * the item the constraint targeted * @param oldItem * the original item matching the constraint - * @param newItem + * @param target * the new item matching the constraint + * @param dynamicContext + * the Metapath dynamic execution context to use for Metapath + * evaluation * @return the new message */ - @SuppressWarnings("null") @NonNull protected String newIndexDuplicateKeyViolationMessage( @NonNull IIndexConstraint constraint, @NonNull INodeItem node, @NonNull INodeItem oldItem, - @NonNull INodeItem newItem) { + @NonNull INodeItem target, + @NonNull DynamicContext dynamicContext) { // TODO: render the key paths - return String.format("Index '%s' has duplicate key for items at paths '%s' and '%s'", - constraint.getName(), - toPath(oldItem), - toPath(newItem)); + return constraint.getMessage() == null + ? ObjectUtils.notNull(String.format("Index '%s' has duplicate key for items at paths '%s' and '%s'", + constraint.getName(), + toPath(oldItem), + toPath(target))) + : constraint.generateMessage(target, dynamicContext); } /** @@ -155,20 +171,25 @@ protected String newIndexDuplicateKeyViolationMessage( * the item the constraint targeted * @param oldItem * the original item matching the constraint - * @param newItem + * @param target * the new item matching the constraint + * @param dynamicContext + * the Metapath dynamic execution context to use for Metapath + * evaluation * @return the new message */ - @SuppressWarnings("null") @NonNull protected String newUniqueKeyViolationMessage( @NonNull IUniqueConstraint constraint, @NonNull INodeItem node, @NonNull INodeItem oldItem, - @NonNull INodeItem newItem) { - return String.format("Unique constraint violation at paths '%s' and '%s'", - toPath(oldItem), - toPath(newItem)); + @NonNull INodeItem target, + @NonNull DynamicContext dynamicContext) { + return constraint.getMessage() == null + ? ObjectUtils.notNull(String.format("Unique constraint violation at paths '%s' and '%s'", + toPath(oldItem), + toPath(target))) + : constraint.generateMessage(target, dynamicContext); } /** @@ -185,20 +206,25 @@ protected String newUniqueKeyViolationMessage( * the target's value * @param pattern * the expected pattern + * @param dynamicContext + * the Metapath dynamic execution context to use for Metapath + * evaluation * @return the new message */ - @SuppressWarnings("null") @NonNull protected String newMatchPatternViolationMessage( @NonNull IMatchesConstraint constraint, @NonNull INodeItem node, @NonNull INodeItem target, @NonNull String value, - @NonNull Pattern pattern) { - return String.format("Value '%s' did not match the pattern '%s' at path '%s'", - value, - pattern.pattern(), - toPath(target)); + @NonNull Pattern pattern, + @NonNull DynamicContext dynamicContext) { + return constraint.getMessage() == null + ? ObjectUtils.notNull(String.format("Value '%s' did not match the pattern '%s' at path '%s'", + value, + pattern.pattern(), + toPath(target))) + : constraint.generateMessage(target, dynamicContext); } /** @@ -215,18 +241,25 @@ protected String newMatchPatternViolationMessage( * the target's value * @param adapter * the expected data type adapter + * @param dynamicContext + * the Metapath dynamic execution context to use for Metapath + * evaluation * @return the new message */ - @SuppressWarnings("null") @NonNull protected String newMatchDatatypeViolationMessage( @NonNull IMatchesConstraint constraint, @NonNull INodeItem node, @NonNull INodeItem target, @NonNull String value, - @NonNull IDataTypeAdapter adapter) { - return String.format("Value '%s' did not conform to the data type '%s' at path '%s'", value, - adapter.getPreferredName(), toPath(target)); + @NonNull IDataTypeAdapter adapter, + @NonNull DynamicContext dynamicContext) { + return constraint.getMessage() == null + ? ObjectUtils.notNull(String.format("Value '%s' did not conform to the data type '%s' at path '%s'", + value, + adapter.getPreferredName(), + toPath(target))) + : constraint.generateMessage(target, dynamicContext); } /** @@ -244,22 +277,17 @@ protected String newMatchDatatypeViolationMessage( * evaluation * @return the new message */ - @SuppressWarnings("null") @NonNull protected String newExpectViolationMessage( @NonNull IExpectConstraint constraint, @NonNull INodeItem node, @NonNull INodeItem target, @NonNull DynamicContext dynamicContext) { - String message; - if (constraint.getMessage() != null) { - message = constraint.generateMessage(target, dynamicContext); - } else { - message = String.format("Expect constraint '%s' did not match the data at path '%s'", - constraint.getTest(), - toPath(target)); - } - return message; + return constraint.getMessage() == null + ? ObjectUtils.notNull(String.format("Expect constraint '%s' did not match the data at path '%s'", + constraint.getTest(), + toPath(target))) + : constraint.generateMessage(target, dynamicContext); } /** @@ -272,12 +300,10 @@ protected String newExpectViolationMessage( * the target matching the constraint * @return the new message */ - @SuppressWarnings("null") @NonNull protected String newAllowedValuesViolationMessage( @NonNull List constraints, @NonNull INodeItem target) { - String allowedValues = constraints.stream() .flatMap(constraint -> constraint.getAllowedValues().values().stream()) .map(IAllowedValue::getValue) @@ -285,10 +311,10 @@ protected String newAllowedValuesViolationMessage( .distinct() .collect(CustomCollectors.joiningWithOxfordComma("or")); - return String.format("Value '%s' doesn't match one of '%s' at path '%s'", + return ObjectUtils.notNull(String.format("Value '%s' doesn't match one of '%s' at path '%s'", FnData.fnDataItem(target).asString(), allowedValues, - toPath(target)); + toPath(target))); } /** @@ -301,14 +327,13 @@ protected String newAllowedValuesViolationMessage( * the item the constraint targeted * @return the new message */ - @SuppressWarnings("null") @NonNull protected String newIndexDuplicateViolationMessage( @NonNull IIndexConstraint constraint, @NonNull INodeItem node) { - return String.format("Duplicate index named '%s' found at path '%s'", + return ObjectUtils.notNull(String.format("Duplicate index named '%s' found at path '%s'", constraint.getName(), - node.getMetapath()); + node.getMetapath())); } /** @@ -323,22 +348,27 @@ protected String newIndexDuplicateViolationMessage( * the target matching the constraint * @param key * the key derived from the target that failed to be found in the index + * @param dynamicContext + * the Metapath dynamic execution context to use for Metapath + * evaluation * @return the new message */ - @SuppressWarnings("null") @NonNull protected String newIndexMissMessage( @NonNull IIndexHasKeyConstraint constraint, @NonNull INodeItem node, @NonNull INodeItem target, - @NonNull List key) { + @NonNull List key, + @NonNull DynamicContext dynamicContext) { String keyValues = key.stream() .collect(Collectors.joining(",")); - return String.format("Key reference [%s] not found in index '%s' for item at path '%s'", - keyValues, - constraint.getIndexName(), - target.getMetapath()); + return constraint.getMessage() == null + ? ObjectUtils.notNull(String.format("Key reference [%s] not found in index '%s' for item at path '%s'", + keyValues, + constraint.getIndexName(), + target.getMetapath())) + : constraint.generateMessage(target, dynamicContext); } /** @@ -353,6 +383,9 @@ protected String newIndexMissMessage( * the target matching the constraint * @param message * the message to be added before information about the target path + * @param dynamicContext + * the Metapath dynamic execution context to use for Metapath + * evaluation * @return the new message */ @SuppressWarnings("null") @@ -361,7 +394,8 @@ protected String newMissingIndexViolationMessage( @NonNull IIndexHasKeyConstraint constraint, @NonNull INodeItem node, @NonNull INodeItem target, - @NonNull String message) { + @NonNull String message, + @NonNull DynamicContext dynamicContext) { return String.format("%s for constraint '%s' for item at path '%s'", message, Objects.requireNonNullElse(constraint.getId(), "?"), diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/AbstractKeyConstraintBuilder.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/AbstractKeyConstraintBuilder.java index 2004439d8..ff3d35e59 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/AbstractKeyConstraintBuilder.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/AbstractKeyConstraintBuilder.java @@ -15,7 +15,7 @@ public abstract class AbstractKeyConstraintBuilder< T extends AbstractKeyConstraintBuilder, R extends IKeyConstraint> - extends AbstractConstraintBuilder { + extends AbstractConfigurableMessageConstraintBuilder { @NonNull private final List keyFields = new LinkedList<>(); diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/DefaultConstraintValidator.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/DefaultConstraintValidator.java index 6cbcadf31..71d57aef3 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/DefaultConstraintValidator.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/DefaultConstraintValidator.java @@ -233,9 +233,9 @@ private void validateHasCardinality( // NOPMD false positive for (ICardinalityConstraint constraint : constraints) { ISequence> targets = constraint.matchTargets(item, dynamicContext); try { - validateHasCardinality(constraint, item, targets); + validateHasCardinality(constraint, item, targets, dynamicContext); } catch (MetapathException ex) { - handleError(constraint, item, ex); + handleError(constraint, item, ex, dynamicContext); } } } @@ -256,7 +256,8 @@ private void validateHasCardinality( // NOPMD false positive private void validateHasCardinality( @NonNull ICardinalityConstraint constraint, @NonNull IAssemblyNodeItem node, - @NonNull ISequence targets) { + @NonNull ISequence targets, + @NonNull DynamicContext dynamicContext) { int itemCount = targets.size(); IConstraintValidationHandler handler = getConstraintValidationHandler(); @@ -264,18 +265,18 @@ private void validateHasCardinality( boolean violation = false; Integer minOccurs = constraint.getMinOccurs(); if (minOccurs != null && itemCount < minOccurs) { - handler.handleCardinalityMinimumViolation(constraint, node, targets); + handler.handleCardinalityMinimumViolation(constraint, node, targets, dynamicContext); violation = true; } Integer maxOccurs = constraint.getMaxOccurs(); if (maxOccurs != null && itemCount > maxOccurs) { - handler.handleCardinalityMaximumViolation(constraint, node, targets); + handler.handleCardinalityMaximumViolation(constraint, node, targets, dynamicContext); violation = true; } if (!violation) { - handlePass(constraint, node, node); + handlePass(constraint, node, node, dynamicContext); } } @@ -300,7 +301,7 @@ private void validateIndex( try { validateIndex(constraint, item, targets, dynamicContext); } catch (MetapathException ex) { - handleError(constraint, item, ex); + handleError(constraint, item, ex, dynamicContext); } } } @@ -330,7 +331,7 @@ private void validateIndex( IConstraintValidationHandler handler = getConstraintValidationHandler(); if (indexNameToIndexMap.containsKey(indexName)) { - handler.handleIndexDuplicateViolation(constraint, node); + handler.handleIndexDuplicateViolation(constraint, node, dynamicContext); } else { IIndex index = IIndex.newInstance(constraint.getKeyFields()); targets.stream() @@ -340,12 +341,12 @@ private void validateIndex( try { INodeItem oldItem = index.put(item, dynamicContext); if (oldItem == null) { - handlePass(constraint, node, item); + handlePass(constraint, node, item, dynamicContext); } else { - handler.handleIndexDuplicateKeyViolation(constraint, node, oldItem, item); + handler.handleIndexDuplicateKeyViolation(constraint, node, oldItem, item, dynamicContext); } } catch (MetapathException ex) { - handler.handleKeyMatchError(constraint, node, item, ex); + handler.handleKeyMatchError(constraint, node, item, ex, dynamicContext); } } }); @@ -356,17 +357,20 @@ private void validateIndex( private void handlePass( @NonNull IConstraint constraint, @NonNull INodeItem node, - @NonNull INodeItem item) { + @NonNull INodeItem item, + @NonNull DynamicContext dynamicContext) { if (isFeatureEnabled(ValidationFeature.VALIDATE_GENERATE_PASS_FINDINGS)) { - getConstraintValidationHandler().handlePass(constraint, node, item); + getConstraintValidationHandler().handlePass(constraint, node, item, dynamicContext); } } private void handleError( @NonNull IConstraint constraint, @NonNull INodeItem node, - @NonNull MetapathException ex) { - getConstraintValidationHandler().handleError(constraint, node, toErrorMessage(constraint, node, ex), ex); + @NonNull MetapathException ex, + @NonNull DynamicContext dynamicContext) { + getConstraintValidationHandler() + .handleError(constraint, node, toErrorMessage(constraint, node, ex), ex, dynamicContext); } @NonNull @@ -418,7 +422,7 @@ private void validateUnique( try { validateUnique(constraint, item, targets, dynamicContext); } catch (MetapathException ex) { - handleError(constraint, item, ex); + handleError(constraint, item, ex, dynamicContext); } } } @@ -454,12 +458,12 @@ private void validateUnique( try { INodeItem oldItem = index.put(item, dynamicContext); if (oldItem == null) { - handlePass(constraint, node, item); + handlePass(constraint, node, item, dynamicContext); } else { - handler.handleUniqueKeyViolation(constraint, node, oldItem, item); + handler.handleUniqueKeyViolation(constraint, node, oldItem, item, dynamicContext); } } catch (MetapathException ex) { - handler.handleKeyMatchError(constraint, node, item, ex); + handler.handleKeyMatchError(constraint, node, item, ex, dynamicContext); throw ex; } } @@ -486,9 +490,9 @@ private void validateMatches( // NOPMD false positive for (IMatchesConstraint constraint : constraints) { ISequence> targets = constraint.matchTargets(item, dynamicContext); try { - validateMatches(constraint, item, targets); + validateMatches(constraint, item, targets, dynamicContext); } catch (MetapathException ex) { - handleError(constraint, item, ex); + handleError(constraint, item, ex, dynamicContext); } } } @@ -509,12 +513,13 @@ private void validateMatches( // NOPMD false positive private void validateMatches( @NonNull IMatchesConstraint constraint, @NonNull INodeItem node, - @NonNull ISequence targets) { + @NonNull ISequence targets, + @NonNull DynamicContext dynamicContext) { targets.stream() .forEachOrdered(item -> { assert item != null; if (item.hasValue()) { - validateMatchesItem(constraint, node, item); + validateMatchesItem(constraint, node, item, dynamicContext); } }); } @@ -522,7 +527,8 @@ private void validateMatches( private void validateMatchesItem( @NonNull IMatchesConstraint constraint, @NonNull INodeItem node, - @NonNull INodeItem item) { + @NonNull INodeItem item, + @NonNull DynamicContext dynamicContext) { String value = FnData.fnDataItem(item).asString(); IConstraintValidationHandler handler = getConstraintValidationHandler(); @@ -530,7 +536,7 @@ private void validateMatchesItem( Pattern pattern = constraint.getPattern(); if (pattern != null && !pattern.asMatchPredicate().test(value)) { // failed pattern match - handler.handleMatchPatternViolation(constraint, node, item, value, pattern); + handler.handleMatchPatternViolation(constraint, node, item, value, pattern, dynamicContext); valid = false; } @@ -539,13 +545,13 @@ private void validateMatchesItem( try { adapter.parse(value); } catch (IllegalArgumentException ex) { - handler.handleMatchDatatypeViolation(constraint, node, item, value, adapter, ex); + handler.handleMatchDatatypeViolation(constraint, node, item, value, adapter, ex, dynamicContext); valid = false; } } if (valid) { - handlePass(constraint, node, item); + handlePass(constraint, node, item, dynamicContext); } } @@ -657,12 +663,12 @@ private void validateExpect( try { ISequence result = metapath.evaluate(item, dynamicContext); if (FnBoolean.fnBoolean(result).toBoolean()) { - handlePass(constraint, node, item); + handlePass(constraint, node, item, dynamicContext); } else { handler.handleExpectViolation(constraint, node, item, dynamicContext); } } catch (MetapathException ex) { - handleError(constraint, item, ex); + handleError(constraint, item, ex, dynamicContext); } } }); @@ -686,7 +692,7 @@ private void validateAllowedValues( @NonNull DynamicContext dynamicContext) { for (IAllowedValuesConstraint constraint : constraints) { ISequence> targets = constraint.matchTargets(item, dynamicContext); - validateAllowedValues(constraint, item, targets); + validateAllowedValues(constraint, item, targets, dynamicContext); } } @@ -702,18 +708,22 @@ private void validateAllowedValues( * @param targets * the focus of Metapath evaluation for evaluating any constraint * Metapath clauses + * @param dynamicContext + * the Metapath dynamic execution context to use for Metapath + * evaluation */ private void validateAllowedValues( @NonNull IAllowedValuesConstraint constraint, @NonNull IDefinitionNodeItem node, - @NonNull ISequence> targets) { + @NonNull ISequence> targets, + @NonNull DynamicContext dynamicContext) { targets.stream().forEachOrdered(item -> { assert item != null; if (item.hasValue()) { try { updateValueStatus(item, constraint, node); } catch (MetapathException ex) { - handleError(constraint, item, ex); + handleError(constraint, item, ex, dynamicContext); } } }); @@ -751,11 +761,16 @@ protected void updateValueStatus( * * @param targetItem * the item whose value will be validated + * @param dynamicContext + * the Metapath dynamic execution context to use for Metapath + * evaluation */ - protected void handleAllowedValues(@NonNull INodeItem targetItem) { + protected void handleAllowedValues( + @NonNull INodeItem targetItem, + @NonNull DynamicContext dynamicContext) { ValueStatus valueStatus = valueMap.remove(targetItem); if (valueStatus != null) { - valueStatus.validate(); + valueStatus.validate(dynamicContext); } } @@ -794,19 +809,24 @@ private void validateKeyRef( List key = IIndex.toKey(item, constraint.getKeyFields(), dynamicContext); if (index == null) { - handler.handleMissingIndexViolation(constraint, contextNode, item, ObjectUtils.notNull( - String.format("Key reference to undefined index with name '%s'", indexName))); + handler.handleMissingIndexViolation( + constraint, + contextNode, + item, + ObjectUtils.notNull(String.format("Key reference to undefined index with name '%s'", + indexName)), + dynamicContext); } else { INodeItem referencedItem = index.get(key); if (referencedItem == null) { - handler.handleIndexMiss(constraint, contextNode, item, key); + handler.handleIndexMiss(constraint, contextNode, item, key, dynamicContext); } else { - handlePass(constraint, contextNode, item); + handlePass(constraint, contextNode, item, dynamicContext); } } } catch (MetapathException ex) { - handler.handleKeyMatchError(constraint, contextNode, item, ex); + handler.handleKeyMatchError(constraint, contextNode, item, ex, dynamicContext); } } @@ -854,7 +874,7 @@ public void registerAllowedValue( } } - public void validate() { + public void validate(@NonNull DynamicContext dynamicContext) { if (!constraints.isEmpty()) { boolean match = false; List failedConstraints = new LinkedList<>(); @@ -865,7 +885,7 @@ public void validate() { IAllowedValue matchingValue = allowedValues.getAllowedValue(value); if (matchingValue != null) { match = true; - handlePass(allowedValues, node, item); + handlePass(allowedValues, node, item, dynamicContext); } else if (IAllowedValuesConstraint.Extensible.NONE.equals(allowedValues.getExtensible())) { // hard failure, since no other values can satisfy this constraint failedConstraints = CollectionUtil.singletonList(allowedValues); @@ -878,7 +898,7 @@ public void validate() { // it's not a failure if allow others is true if (!match && !allowOthers) { - handler.handleAllowedValuesViolation(failedConstraints, item); + handler.handleAllowedValuesViolation(failedConstraints, item, dynamicContext); } } } @@ -923,7 +943,7 @@ public Void visitFlag(@NonNull IFlagNodeItem item, DynamicContext context) { validateFlag(item, effectiveContext); super.visitFlag(item, effectiveContext); - handleAllowedValues(item); + handleAllowedValues(item, context); return null; } @@ -936,7 +956,7 @@ public Void visitField(@NonNull IFieldNodeItem item, DynamicContext context) { validateField(item, effectiveContext); super.visitField(item, effectiveContext); - handleAllowedValues(item); + handleAllowedValues(item, context); return null; } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/FindingCollectingConstraintValidationHandler.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/FindingCollectingConstraintValidationHandler.java index bb06900e1..29130e7c7 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/FindingCollectingConstraintValidationHandler.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/FindingCollectingConstraintValidationHandler.java @@ -25,6 +25,12 @@ import edu.umd.cs.findbugs.annotations.NonNull; +/** + * A validation result handler that collects the resulting findings for later + * retrieval using the {@link #getFindings()} method. + *

+ * This class is not thread safe. + */ @SuppressWarnings("PMD.CouplingBetweenObjects") public class FindingCollectingConstraintValidationHandler extends AbstractConstraintValidationHandler @@ -88,28 +94,30 @@ private static Kind toKind(@NonNull Level level) { @Override public void handleCardinalityMinimumViolation( @NonNull ICardinalityConstraint constraint, - @NonNull INodeItem node, - @NonNull ISequence targets) { - addFinding(ConstraintValidationFinding.builder(constraint, node) + @NonNull INodeItem target, + @NonNull ISequence testedItems, + @NonNull DynamicContext dynamicContext) { + addFinding(ConstraintValidationFinding.builder(constraint, target) .severity(constraint.getLevel()) .kind(toKind(constraint.getLevel())) - .target(node) - .subjects(targets.getValue()) - .message(newCardinalityMinimumViolationMessage(constraint, node, targets)) + .target(target) + .subjects(testedItems.getValue()) + .message(newCardinalityMinimumViolationMessage(constraint, target, testedItems, dynamicContext)) .build()); } @Override public void handleCardinalityMaximumViolation( @NonNull ICardinalityConstraint constraint, - @NonNull INodeItem node, - @NonNull ISequence targets) { - addFinding(ConstraintValidationFinding.builder(constraint, node) + @NonNull INodeItem target, + @NonNull ISequence testedItems, + @NonNull DynamicContext dynamicContext) { + addFinding(ConstraintValidationFinding.builder(constraint, target) .severity(constraint.getLevel()) .kind(toKind(constraint.getLevel())) - .target(node) - .subjects(targets.getValue()) - .message(newCardinalityMaximumViolationMessage(constraint, node, targets)) + .target(target) + .subjects(testedItems.getValue()) + .message(newCardinalityMaximumViolationMessage(constraint, target, testedItems, dynamicContext)) .build()); } @@ -118,12 +126,13 @@ public void handleIndexDuplicateKeyViolation( @NonNull IIndexConstraint constraint, @NonNull INodeItem node, @NonNull INodeItem oldItem, - @NonNull INodeItem target) { + @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)) + .message(newIndexDuplicateKeyViolationMessage(constraint, node, oldItem, target, dynamicContext)) .build()); } @@ -132,12 +141,13 @@ public void handleUniqueKeyViolation( @NonNull IUniqueConstraint constraint, @NonNull INodeItem node, @NonNull INodeItem oldItem, - @NonNull INodeItem target) { + @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)) + .message(newUniqueKeyViolationMessage(constraint, node, oldItem, target, dynamicContext)) .build()); } @@ -147,7 +157,8 @@ public void handleKeyMatchError( @NonNull IKeyConstraint constraint, @NonNull INodeItem node, @NonNull INodeItem target, - @NonNull MetapathException cause) { + @NonNull MetapathException cause, + @NonNull DynamicContext dynamicContext) { addFinding(ConstraintValidationFinding.builder(constraint, node) .severity(constraint.getLevel()) .kind(toKind(constraint.getLevel())) @@ -163,12 +174,13 @@ public void handleMatchPatternViolation( @NonNull INodeItem node, @NonNull INodeItem target, @NonNull String value, - @NonNull Pattern pattern) { + @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)) + .message(newMatchPatternViolationMessage(constraint, node, target, value, pattern, dynamicContext)) .build()); } @@ -179,12 +191,13 @@ public void handleMatchDatatypeViolation( @NonNull INodeItem target, @NonNull String value, @NonNull IDataTypeAdapter adapter, - @NonNull IllegalArgumentException cause) { + @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)) + .message(newMatchDatatypeViolationMessage(constraint, node, target, value, adapter, dynamicContext)) .cause(cause) .build()); } @@ -206,7 +219,8 @@ public void handleExpectViolation( @Override public void handleAllowedValuesViolation( @NonNull List failedConstraints, - @NonNull INodeItem target) { + @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)); @@ -220,7 +234,10 @@ public void handleAllowedValuesViolation( } @Override - public void handleIndexDuplicateViolation(IIndexConstraint constraint, INodeItem node) { + public void handleIndexDuplicateViolation( + IIndexConstraint constraint, + INodeItem node, + @NonNull DynamicContext dynamicContext) { addFinding(ConstraintValidationFinding.builder(constraint, node) .kind(Kind.FAIL) .severity(Level.CRITICAL) @@ -230,31 +247,41 @@ public void handleIndexDuplicateViolation(IIndexConstraint constraint, INodeItem } @Override - public void handleIndexMiss(IIndexHasKeyConstraint constraint, INodeItem node, INodeItem target, List key) { + public void handleIndexMiss( + @NonNull IIndexHasKeyConstraint constraint, + @NonNull INodeItem node, + @NonNull INodeItem target, + @NonNull List key, + @NonNull DynamicContext dynamicContext) { addFinding(ConstraintValidationFinding.builder(constraint, node) .severity(constraint.getLevel()) .kind(toKind(constraint.getLevel())) .target(target) - .message(newIndexMissMessage(constraint, node, target, key)) + .message(newIndexMissMessage(constraint, node, target, key, dynamicContext)) .build()); } @Override public void handleMissingIndexViolation( - IIndexHasKeyConstraint constraint, - INodeItem node, - INodeItem target, - String message) { + @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)) + .message(newMissingIndexViolationMessage(constraint, node, target, message, dynamicContext)) .build()); } @Override - public void handlePass(IConstraint constraint, INodeItem node, INodeItem target) { + 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) @@ -264,10 +291,11 @@ public void handlePass(IConstraint constraint, INodeItem node, INodeItem target) @Override public void handleError( - IConstraint constraint, - INodeItem node, - String message, - Throwable exception) { + @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) diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IAllowedValuesConstraint.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IAllowedValuesConstraint.java index 3ff683191..5bc44b3c9 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IAllowedValuesConstraint.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IAllowedValuesConstraint.java @@ -204,7 +204,6 @@ protected IAllowedValuesConstraint newInstance() { getAllowedValues(), isAllowedOther(), getExtensible(), - getMessage(), getRemarks()); } } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/ICardinalityConstraint.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/ICardinalityConstraint.java index 1c60897ab..4d78547eb 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/ICardinalityConstraint.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/ICardinalityConstraint.java @@ -16,7 +16,7 @@ * Represents a rule requiring a Metaschema assembly data instance to have * elements with a minimum and/or maximum occurrence. */ -public interface ICardinalityConstraint extends IConstraint { +public interface ICardinalityConstraint extends IConfigurableMessageConstraint { /** * Retrieve the required minimum occurrence of the target instance. If * specified, this value must be less than or equal to the value of @@ -59,7 +59,7 @@ static Builder builder() { * {@link ICardinalityConstraint}. */ final class Builder - extends AbstractConstraintBuilder { + extends AbstractConfigurableMessageConstraintBuilder { private Integer minOccurs; private Integer maxOccurs; diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IConfigurableMessageConstraint.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IConfigurableMessageConstraint.java new file mode 100644 index 000000000..c5548fa81 --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IConfigurableMessageConstraint.java @@ -0,0 +1,45 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.model.constraint; + +import gov.nist.secauto.metaschema.core.metapath.DynamicContext; +import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem; + +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; + +/** + * Represents a constraint that allows a configurable message. + * + * @since 2.0.0 + */ +public interface IConfigurableMessageConstraint extends IConstraint { + + /** + * A message to emit when the constraint is violated. Allows embedded Metapath + * expressions using the syntax {@code \{ metapath \}}. + * + * @return the message if defined or {@code null} otherwise + */ + @Nullable + String getMessage(); + + /** + * Generate a violation message using the provide item and dynamic context for + * inline Metapath value insertion. + * + * @param item + * the target Metapath item to use as the focus for Metapath evaluation + * @param context + * the dynamic context for Metapath evaluation + * @return the message + * @throws IllegalStateException + * if a custom message is not defined, which will occur if this method + * is called while {@link #getMessage()} returns {@code null} + */ + @NonNull + String generateMessage(@NonNull INodeItem item, @NonNull DynamicContext context); +} diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IConstraint.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IConstraint.java index 5824c13ac..0060ac9c4 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IConstraint.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IConstraint.java @@ -9,7 +9,6 @@ import gov.nist.secauto.metaschema.core.metapath.DynamicContext; import gov.nist.secauto.metaschema.core.metapath.ISequence; import gov.nist.secauto.metaschema.core.metapath.item.node.IDefinitionNodeItem; -import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem; import gov.nist.secauto.metaschema.core.model.IAttributable; import gov.nist.secauto.metaschema.core.model.IDescribable; @@ -117,31 +116,6 @@ enum Level { @NonNull IDefinitionNodeItem item, @NonNull DynamicContext dynamicContext); - /** - * A message to emit when the constraint is violated. Allows embedded Metapath - * expressions using the syntax {@code \{ metapath \}}. - * - * @return the message if defined or {@code null} otherwise - */ - @Nullable - String getMessage(); - - /** - * Generate a violation message using the provide item and dynamic context for - * inline Metapath value insertion. - * - * @param item - * the target Metapath item to use as the focus for Metapath evaluation - * @param context - * the dynamic context for Metapath evaluation - * @return the message - * @throws IllegalStateException - * if a custom message is not defined, which will occur if this method - * is called while {@link #getMessage()} returns {@code null} - */ - @NonNull - String generateMessage(@NonNull INodeItem item, @NonNull DynamicContext context); - /** * Retrieve the remarks associated with the constraint. * diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IConstraintValidationHandler.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IConstraintValidationHandler.java index 4c23c0181..5bc3a09b0 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IConstraintValidationHandler.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IConstraintValidationHandler.java @@ -27,32 +27,38 @@ public interface IConstraintValidationHandler { * * @param constraint * the constraint that was evaluated - * @param node - * the node used as the evaluation focus to determine constraint - * targets - * @param targets - * the targets of evaluation + * @param target + * the node used as the evaluation focus to determine the items to test + * @param testedItems + * the items tested + * @param dynamicContext + * the Metapath dynamic execution context to use for Metapath + * evaluation */ void handleCardinalityMinimumViolation( @NonNull ICardinalityConstraint constraint, - @NonNull INodeItem node, - @NonNull ISequence targets); + @NonNull INodeItem target, + @NonNull ISequence testedItems, + @NonNull DynamicContext dynamicContext); /** * Handle a cardinality constraint maximum violation. * * @param constraint * the constraint that was evaluated - * @param node - * the node used as the evaluation focus to determine constraint - * targets - * @param targets - * the targets of evaluation + * @param target + * the node used as the evaluation focus to determine the items to test + * @param testedItems + * the items tested + * @param dynamicContext + * the Metapath dynamic execution context to use for Metapath + * evaluation */ void handleCardinalityMaximumViolation( @NonNull ICardinalityConstraint constraint, - @NonNull INodeItem node, - @NonNull ISequence targets); + @NonNull INodeItem target, + @NonNull ISequence testedItems, + @NonNull DynamicContext dynamicContext); /** * Handle a duplicate index violation. @@ -62,10 +68,14 @@ void handleCardinalityMaximumViolation( * @param node * the node used as the evaluation focus to determine constraint * targets + * @param dynamicContext + * the Metapath dynamic execution context to use for Metapath + * evaluation */ void handleIndexDuplicateViolation( @NonNull IIndexConstraint constraint, - @NonNull INodeItem node); + @NonNull INodeItem node, + @NonNull DynamicContext dynamicContext); /** * Handle an index duplicate key violation. @@ -81,12 +91,16 @@ void handleIndexDuplicateViolation( * the node that exists in the index for the related key * @param target * the target of evaluation + * @param dynamicContext + * the Metapath dynamic execution context to use for Metapath + * evaluation */ void handleIndexDuplicateKeyViolation( @NonNull IIndexConstraint constraint, @NonNull INodeItem node, @NonNull INodeItem oldItem, - @NonNull INodeItem target); + @NonNull INodeItem target, + @NonNull DynamicContext dynamicContext); /** * Handle an unique key violation. @@ -102,12 +116,16 @@ void handleIndexDuplicateKeyViolation( * the other node with the same key * @param target * the target of evaluation + * @param dynamicContext + * the Metapath dynamic execution context to use for Metapath + * evaluation */ void handleUniqueKeyViolation( @NonNull IUniqueConstraint constraint, @NonNull INodeItem node, @NonNull INodeItem oldItem, - @NonNull INodeItem target); + @NonNull INodeItem target, + @NonNull DynamicContext dynamicContext); /** * Handle an error that occurred while generating a key. @@ -121,12 +139,16 @@ void handleUniqueKeyViolation( * the target of evaluation * @param exception * the resulting Metapath exception + * @param dynamicContext + * the Metapath dynamic execution context to use for Metapath + * evaluation */ void handleKeyMatchError( @NonNull IKeyConstraint constraint, @NonNull INodeItem node, @NonNull INodeItem target, - @NonNull MetapathException exception); + @NonNull MetapathException exception, + @NonNull DynamicContext dynamicContext); /** * Handle a missing index violation. @@ -142,12 +164,16 @@ void handleKeyMatchError( * the target of evaluation * @param message * the error message + * @param dynamicContext + * the Metapath dynamic execution context to use for Metapath + * evaluation */ void handleMissingIndexViolation( @NonNull IIndexHasKeyConstraint constraint, @NonNull INodeItem node, @NonNull INodeItem target, - @NonNull String message); + @NonNull String message, + @NonNull DynamicContext dynamicContext); /** * Handle an index lookup key miss violation. @@ -164,12 +190,16 @@ void handleMissingIndexViolation( * the target of evaluation * @param key * the key that was used to lookup the index entry + * @param dynamicContext + * the Metapath dynamic execution context to use for Metapath + * evaluation */ void handleIndexMiss( @NonNull IIndexHasKeyConstraint constraint, @NonNull INodeItem node, @NonNull INodeItem target, - @NonNull List key); + @NonNull List key, + @NonNull DynamicContext dynamicContext); /** * Handle a match pattern violation. @@ -187,13 +217,17 @@ void handleIndexMiss( * the value used for pattern matching * @param pattern * the pattern used for pattern matching + * @param dynamicContext + * the Metapath dynamic execution context to use for Metapath + * evaluation */ void handleMatchPatternViolation( @NonNull IMatchesConstraint constraint, @NonNull INodeItem node, @NonNull INodeItem target, @NonNull String value, - @NonNull Pattern pattern); + @NonNull Pattern pattern, + @NonNull DynamicContext dynamicContext); /** * Handle a match data type violation. @@ -214,6 +248,9 @@ void handleMatchPatternViolation( * the data type used for data type matching * @param cause * the data type exception related to this violation + * @param dynamicContext + * the Metapath dynamic execution context to use for Metapath + * evaluation */ void handleMatchDatatypeViolation( @NonNull IMatchesConstraint constraint, @@ -221,7 +258,8 @@ void handleMatchDatatypeViolation( @NonNull INodeItem target, @NonNull String value, @NonNull IDataTypeAdapter adapter, - @NonNull IllegalArgumentException cause); + @NonNull IllegalArgumentException cause, + @NonNull DynamicContext dynamicContext); /** * Handle an expect test violation. @@ -235,14 +273,15 @@ void handleMatchDatatypeViolation( * targets * @param target * the target of evaluation - * @param metapathContext - * the Metapath evaluation context + * @param dynamicContext + * the Metapath dynamic execution context to use for Metapath + * evaluation */ void handleExpectViolation( @NonNull IExpectConstraint constraint, @NonNull INodeItem node, @NonNull INodeItem target, - @NonNull DynamicContext metapathContext); + @NonNull DynamicContext dynamicContext); /** * Handle an allowed values constraint violation. @@ -251,10 +290,14 @@ void handleExpectViolation( * the allowed values constraints that did not match. * @param target * the target of evaluation + * @param dynamicContext + * the Metapath dynamic execution context to use for Metapath + * evaluation */ void handleAllowedValuesViolation( @NonNull List failedConstraints, - @NonNull INodeItem target); + @NonNull INodeItem target, + @NonNull DynamicContext dynamicContext); /** * Handle a constraint that has passed validation. @@ -266,11 +309,15 @@ void handleAllowedValuesViolation( * targets * @param target * the target of evaluation + * @param dynamicContext + * the Metapath dynamic execution context to use for Metapath + * evaluation */ void handlePass( @NonNull IConstraint constraint, @NonNull INodeItem node, - @NonNull INodeItem target); + @NonNull INodeItem target, + @NonNull DynamicContext dynamicContext); /** * Handle a constraint that whose evaluation resulted in an unexpected error @@ -285,10 +332,14 @@ void handlePass( * the error message * @param exception * the causing exception + * @param dynamicContext + * the Metapath dynamic execution context to use for Metapath + * evaluation */ void handleError( @NonNull IConstraint constraint, @NonNull INodeItem node, @NonNull String message, - @NonNull Throwable exception); + @NonNull Throwable exception, + @NonNull DynamicContext dynamicContext); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IExpectConstraint.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IExpectConstraint.java index f63904fb1..d1bf60ffb 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IExpectConstraint.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IExpectConstraint.java @@ -16,7 +16,7 @@ *

* A custom message can be used to indicate what a test failure signifies. */ -public interface IExpectConstraint extends IConstraint { +public interface IExpectConstraint extends IConfigurableMessageConstraint { /** * Get the test to use to validate selected nodes. * @@ -44,7 +44,7 @@ static Builder builder() { * Provides a builder pattern for constructing a new {@link IExpectConstraint}. */ final class Builder - extends AbstractConstraintBuilder { + extends AbstractConfigurableMessageConstraintBuilder { private String test; private Builder() { diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IKeyConstraint.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IKeyConstraint.java index 2901b176d..fcc5328b0 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IKeyConstraint.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IKeyConstraint.java @@ -12,7 +12,7 @@ /** * A common interface used for constraints oriented around key-based indexes. */ -public interface IKeyConstraint extends IConstraint { +public interface IKeyConstraint extends IConfigurableMessageConstraint { /** * Retrieve the list of keys to use in creating and looking up an entry in a * given index. diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IMatchesConstraint.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IMatchesConstraint.java index 130a2c477..d2a6a6c0b 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IMatchesConstraint.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IMatchesConstraint.java @@ -18,7 +18,7 @@ * Represents a rule requiring the value of a field or flag to match a pattern * and/or conform to an identified data type. */ -public interface IMatchesConstraint extends IConstraint { +public interface IMatchesConstraint extends IConfigurableMessageConstraint { /** * Get the expected pattern. * @@ -55,7 +55,7 @@ static Builder builder() { * Provides a builder pattern for constructing a new {@link IMatchesConstraint}. */ final class Builder - extends AbstractConstraintBuilder { + extends AbstractConfigurableMessageConstraintBuilder { private Pattern pattern; private IDataTypeAdapter datatype; diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/LoggingConstraintValidationHandler.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/LoggingConstraintValidationHandler.java index ea837667b..f872edacd 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/LoggingConstraintValidationHandler.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/LoggingConstraintValidationHandler.java @@ -101,10 +101,19 @@ private void logConstraint( public void handleCardinalityMinimumViolation( @NonNull ICardinalityConstraint constraint, @NonNull INodeItem node, - @NonNull ISequence targets) { + @NonNull ISequence targets, + @NonNull DynamicContext dynamicContext) { Level level = constraint.getLevel(); if (isLogged(level)) { - logConstraint(level, node, newCardinalityMinimumViolationMessage(constraint, node, targets), null); + logConstraint( + level, + node, + newCardinalityMinimumViolationMessage( + constraint, + node, + targets, + dynamicContext), + null); } } @@ -112,10 +121,19 @@ public void handleCardinalityMinimumViolation( public void handleCardinalityMaximumViolation( @NonNull ICardinalityConstraint constraint, @NonNull INodeItem node, - @NonNull ISequence targets) { + @NonNull ISequence targets, + @NonNull DynamicContext dynamicContext) { Level level = constraint.getLevel(); if (isLogged(level)) { - logConstraint(level, node, newCardinalityMaximumViolationMessage(constraint, node, targets), null); + logConstraint( + level, + node, + newCardinalityMaximumViolationMessage( + constraint, + node, + targets, + dynamicContext), + null); } } @@ -124,10 +142,20 @@ public void handleIndexDuplicateKeyViolation( @NonNull IIndexConstraint constraint, @NonNull INodeItem node, @NonNull INodeItem oldItem, - @NonNull INodeItem target) { + @NonNull INodeItem target, + @NonNull DynamicContext dynamicContext) { Level level = constraint.getLevel(); if (isLogged(level)) { - logConstraint(level, target, newIndexDuplicateKeyViolationMessage(constraint, node, oldItem, target), null); + logConstraint( + level, + target, + newIndexDuplicateKeyViolationMessage( + constraint, + node, + oldItem, + target, + dynamicContext), + null); } } @@ -136,10 +164,20 @@ public void handleUniqueKeyViolation( @NonNull IUniqueConstraint constraint, @NonNull INodeItem node, @NonNull INodeItem oldItem, - @NonNull INodeItem target) { + @NonNull INodeItem target, + @NonNull DynamicContext dynamicContext) { Level level = constraint.getLevel(); if (isLogged(level)) { - logConstraint(level, target, newUniqueKeyViolationMessage(constraint, node, oldItem, target), null); + logConstraint( + level, + target, + newUniqueKeyViolationMessage( + constraint, + node, + oldItem, + target, + dynamicContext), + null); } } @@ -149,7 +187,8 @@ public void handleKeyMatchError( @NonNull IKeyConstraint constraint, @NonNull INodeItem node, @NonNull INodeItem target, - @NonNull MetapathException cause) { + @NonNull MetapathException cause, + @NonNull DynamicContext dynamicContext) { Level level = constraint.getLevel(); if (isLogged(level)) { logConstraint(level, target, cause.getLocalizedMessage(), cause); @@ -162,10 +201,21 @@ public void handleMatchPatternViolation( @NonNull INodeItem node, @NonNull INodeItem target, @NonNull String value, - @NonNull Pattern pattern) { + @NonNull Pattern pattern, + @NonNull DynamicContext dynamicContext) { Level level = constraint.getLevel(); if (isLogged(level)) { - logConstraint(level, target, newMatchPatternViolationMessage(constraint, node, target, value, pattern), null); + logConstraint( + level, + target, + newMatchPatternViolationMessage( + constraint, + node, + target, + value, + pattern, + dynamicContext), + null); } } @@ -176,10 +226,21 @@ public void handleMatchDatatypeViolation( @NonNull INodeItem target, @NonNull String value, @NonNull IDataTypeAdapter adapter, - @NonNull IllegalArgumentException cause) { + @NonNull IllegalArgumentException cause, + @NonNull DynamicContext dynamicContext) { Level level = constraint.getLevel(); if (isLogged(level)) { - logConstraint(level, target, newMatchDatatypeViolationMessage(constraint, node, target, value, adapter), cause); + logConstraint( + level, + target, + newMatchDatatypeViolationMessage( + constraint, + node, + target, + value, + adapter, + dynamicContext), + cause); } } @@ -191,59 +252,117 @@ public void handleExpectViolation( @NonNull DynamicContext dynamicContext) { Level level = constraint.getLevel(); if (isLogged(level)) { - logConstraint(level, target, newExpectViolationMessage(constraint, node, target, dynamicContext), null); + logConstraint( + level, + target, + newExpectViolationMessage( + constraint, + node, + target, + dynamicContext), + null); } } @Override - public void handleAllowedValuesViolation(@NonNull List failedConstraints, - @NonNull INodeItem target) { + public void handleAllowedValuesViolation( + List failedConstraints, + INodeItem target, + @NonNull DynamicContext dynamicContext) { Level level = ObjectUtils.notNull(failedConstraints.stream() .map(IConstraint::getLevel) .max(Comparator.comparing(Level::ordinal)) .get()); if (isLogged(level)) { - logConstraint(level, target, newAllowedValuesViolationMessage(failedConstraints, target), null); + logConstraint( + level, + target, + newAllowedValuesViolationMessage( + failedConstraints, + target), + null); } } @Override - public void handleIndexDuplicateViolation(IIndexConstraint constraint, INodeItem node) { + public void handleIndexDuplicateViolation( + @NonNull IIndexConstraint constraint, + @NonNull INodeItem node, + @NonNull DynamicContext dynamicContext) { + // always log at level critical Level level = Level.CRITICAL; if (isLogged(level)) { - logConstraint(level, node, newIndexDuplicateViolationMessage(constraint, node), null); + logConstraint( + level, + node, + newIndexDuplicateViolationMessage( + constraint, + node), + null); } } @Override - public void handleIndexMiss(IIndexHasKeyConstraint constraint, INodeItem node, INodeItem target, List key) { + public void handleIndexMiss( + @NonNull IIndexHasKeyConstraint constraint, + @NonNull INodeItem node, + @NonNull INodeItem target, + @NonNull List key, + @NonNull DynamicContext dynamicContext) { Level level = constraint.getLevel(); if (isLogged(level)) { - logConstraint(level, node, newIndexMissMessage(constraint, node, target, key), null); + logConstraint( + level, + node, + newIndexMissMessage( + constraint, + node, + target, + key, + dynamicContext), + null); } } @Override - public void handleMissingIndexViolation(IIndexHasKeyConstraint constraint, INodeItem node, INodeItem target, - String message) { + public void handleMissingIndexViolation( + @NonNull IIndexHasKeyConstraint constraint, + @NonNull INodeItem node, + @NonNull INodeItem target, + @NonNull String message, + @NonNull DynamicContext dynamicContext) { Level level = constraint.getLevel(); if (isLogged(level)) { - logConstraint(level, node, newMissingIndexViolationMessage(constraint, node, target, message), null); + logConstraint( + level, + node, + newMissingIndexViolationMessage( + constraint, + node, + target, + message, + dynamicContext), + null); } } @Override - public void handlePass(IConstraint constraint, INodeItem node, INodeItem target) { + public void handlePass( + @NonNull IConstraint constraint, + @NonNull INodeItem node, + @NonNull INodeItem target, + @NonNull DynamicContext dynamicContext) { // do nothing } @Override public void handleError( - IConstraint constraint, - INodeItem node, - String message, - Throwable exception) { + @NonNull IConstraint constraint, + @NonNull INodeItem node, + @NonNull String message, + @NonNull Throwable exception, + @NonNull DynamicContext dynamicContext) { Level level = Level.CRITICAL; if (isLogged(level)) { logConstraint(level, node, message, exception); diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/impl/AbstractConfigurableMessageConstraint.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/impl/AbstractConfigurableMessageConstraint.java new file mode 100644 index 000000000..1b3b4b937 --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/impl/AbstractConfigurableMessageConstraint.java @@ -0,0 +1,97 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.model.constraint.impl; + +import gov.nist.secauto.metaschema.core.datatype.markup.MarkupLine; +import gov.nist.secauto.metaschema.core.datatype.markup.MarkupMultiline; +import gov.nist.secauto.metaschema.core.metapath.DynamicContext; +import gov.nist.secauto.metaschema.core.metapath.MetapathExpression; +import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem; +import gov.nist.secauto.metaschema.core.model.IAttributable; +import gov.nist.secauto.metaschema.core.model.constraint.IConfigurableMessageConstraint; +import gov.nist.secauto.metaschema.core.model.constraint.ISource; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; +import gov.nist.secauto.metaschema.core.util.ReplacementScanner; + +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; + +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; + +/** + * The base class for all constraint implementations that allow a configurable + * message. + * + * @since 2.0.0 + */ +public abstract class AbstractConfigurableMessageConstraint + extends AbstractConstraint + implements IConfigurableMessageConstraint { + @NonNull + private static final Pattern METAPATH_VALUE_TEMPLATE_PATTERN + = ObjectUtils.notNull(Pattern.compile("(?> properties, + @Nullable String message, + @Nullable MarkupMultiline remarks) { + super(id, formalName, description, source, level, target, properties, remarks); + this.message = message; + } + + @Override + public String getMessage() { + return message; + } + + @Override + public String generateMessage(@NonNull INodeItem item, @NonNull DynamicContext context) { + String message = getMessage(); + if (message == null) { + throw new IllegalStateException("A custom message is not defined."); + } + + return ObjectUtils.notNull(ReplacementScanner.replaceTokens(message, METAPATH_VALUE_TEMPLATE_PATTERN, match -> { + String metapath = ObjectUtils.notNull(match.group(2)); + MetapathExpression expr = MetapathExpression.compile(metapath, context.getStaticContext()); + return expr.evaluateAs(item, MetapathExpression.ResultType.STRING, context); + }).toString()); + } +} diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/impl/AbstractConstraint.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/impl/AbstractConstraint.java index cc8a3f3aa..64fb4f7d5 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/impl/AbstractConstraint.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/impl/AbstractConstraint.java @@ -11,18 +11,15 @@ import gov.nist.secauto.metaschema.core.metapath.ISequence; import gov.nist.secauto.metaschema.core.metapath.MetapathExpression; import gov.nist.secauto.metaschema.core.metapath.item.node.IDefinitionNodeItem; -import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem; import gov.nist.secauto.metaschema.core.model.IAttributable; import gov.nist.secauto.metaschema.core.model.constraint.IConstraint; import gov.nist.secauto.metaschema.core.model.constraint.ISource; import gov.nist.secauto.metaschema.core.util.CollectionUtil; import gov.nist.secauto.metaschema.core.util.ObjectUtils; -import gov.nist.secauto.metaschema.core.util.ReplacementScanner; import java.util.Map; import java.util.Objects; import java.util.Set; -import java.util.regex.Pattern; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -32,9 +29,6 @@ * The base class for all constraint implementations. */ public abstract class AbstractConstraint implements IConstraint { // NOPMD - intentional data class - @NonNull - private static final Pattern METAPATH_VALUE_TEMPLATE_PATTERN - = ObjectUtils.notNull(Pattern.compile("(?> properties; @@ -72,8 +64,6 @@ public abstract class AbstractConstraint implements IConstraint { // NOPMD - int * the Metapath expression identifying the nodes the constraint targets * @param properties * a collection of associated properties - * @param message - * an optional message to emit when the constraint is violated * @param remarks * optional remarks describing the intent of the constraint */ @@ -85,7 +75,6 @@ protected AbstractConstraint( @NonNull Level level, @NonNull String target, @NonNull Map> properties, - @Nullable String message, @Nullable MarkupMultiline remarks) { Objects.requireNonNull(target); this.id = id; @@ -94,7 +83,6 @@ protected AbstractConstraint( this.source = source; this.level = ObjectUtils.requireNonNull(level, "level"); this.properties = properties; - this.message = message; this.remarks = remarks; this.targetMetapath = ObjectUtils.notNull( Lazy.lazy(() -> MetapathExpression.compile( @@ -153,25 +141,6 @@ public MetapathExpression getTargetMetapath() { return ObjectUtils.notNull(targetMetapath.get()); } - @Override - public String getMessage() { - return message; - } - - @Override - public String generateMessage(@NonNull INodeItem item, @NonNull DynamicContext context) { - String message = getMessage(); - if (message == null) { - throw new IllegalStateException("A custom message is not defined."); - } - - return ObjectUtils.notNull(ReplacementScanner.replaceTokens(message, METAPATH_VALUE_TEMPLATE_PATTERN, match -> { - String metapath = ObjectUtils.notNull(match.group(2)); - MetapathExpression expr = MetapathExpression.compile(metapath, context.getStaticContext()); - return expr.evaluateAs(item, MetapathExpression.ResultType.STRING, context); - }).toString()); - } - @Override @NonNull public ISequence> matchTargets( diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/impl/AbstractKeyConstraint.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/impl/AbstractKeyConstraint.java index bd9469c52..a38cf834f 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/impl/AbstractKeyConstraint.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/impl/AbstractKeyConstraint.java @@ -21,7 +21,7 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; abstract class AbstractKeyConstraint - extends AbstractConstraint + extends AbstractConfigurableMessageConstraint implements IKeyConstraint { @NonNull private final List keyFields; diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/impl/DefaultAllowedValuesConstraint.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/impl/DefaultAllowedValuesConstraint.java index 6d2303558..6f9dce6af 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/impl/DefaultAllowedValuesConstraint.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/impl/DefaultAllowedValuesConstraint.java @@ -61,8 +61,6 @@ public final class DefaultAllowedValuesConstraint * {@code allowedValues} are allowed, or disallowed if {@code false} * @param extensible * indicates the degree to which extended values should be allowed - * @param message - * an optional message to emit when the constraint is violated * @param remarks * optional remarks describing the intent of the constraint */ @@ -77,9 +75,8 @@ public DefaultAllowedValuesConstraint( // NOPMD necessary @NonNull Map allowedValues, boolean allowedOther, @NonNull Extensible extensible, - @Nullable String message, @Nullable MarkupMultiline remarks) { - super(id, formalName, description, source, level, target, properties, message, remarks); + super(id, formalName, description, source, level, target, properties, remarks); this.allowedValues = allowedValues; this.allowedOther = allowedOther; this.extensible = extensible; diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/impl/DefaultCardinalityConstraint.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/impl/DefaultCardinalityConstraint.java index f48727887..86d34b426 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/impl/DefaultCardinalityConstraint.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/impl/DefaultCardinalityConstraint.java @@ -25,7 +25,7 @@ * values. */ public final class DefaultCardinalityConstraint - extends AbstractConstraint + extends AbstractConfigurableMessageConstraint implements ICardinalityConstraint { @Nullable private final Integer minOccurs; diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/impl/DefaultExpectConstraint.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/impl/DefaultExpectConstraint.java index 61ffee36f..0f36cb8a4 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/impl/DefaultExpectConstraint.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/impl/DefaultExpectConstraint.java @@ -28,7 +28,7 @@ * against the target. */ public final class DefaultExpectConstraint - extends AbstractConstraint + extends AbstractConfigurableMessageConstraint implements IExpectConstraint { @NonNull private final Lazy testMetapath; diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/impl/DefaultMatchesConstraint.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/impl/DefaultMatchesConstraint.java index ce7a78acd..d500ecdc5 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/impl/DefaultMatchesConstraint.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/impl/DefaultMatchesConstraint.java @@ -25,7 +25,7 @@ * Enforces a value pattern and/or data type. */ public final class DefaultMatchesConstraint - extends AbstractConstraint + extends AbstractConfigurableMessageConstraint implements IMatchesConstraint { private final Pattern pattern; private final IDataTypeAdapter dataType; diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/impl/ConstraintXmlSupport.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/impl/ConstraintXmlSupport.java index f2149a271..c429bf834 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/impl/ConstraintXmlSupport.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/impl/ConstraintXmlSupport.java @@ -65,6 +65,9 @@ import edu.umd.cs.findbugs.annotations.NonNull; +/** + * Provides XMLBeans-based parsing support for constraints. + */ @SuppressWarnings("PMD.CouplingBetweenObjects") public final class ConstraintXmlSupport { @SuppressWarnings("PMD.UseConcurrentHashMap") diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/impl/AnnotationGenerator.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/impl/AnnotationGenerator.java index ee810edbd..c9751550d 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/impl/AnnotationGenerator.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/impl/AnnotationGenerator.java @@ -248,11 +248,6 @@ private static void applyAllowedValuesConstraints( constraintAnnotation.addMember("values", "$L", valueAnnotation.build()); } - String message = constraint.getMessage(); - if (message != null) { - constraintAnnotation.addMember("message", "$S", message); - } - MarkupMultiline remarks = constraint.getRemarks(); if (remarks != null) { constraintAnnotation.addMember("remarks", "$S", remarks.toMarkdown()); diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/json/MetaschemaJsonReader.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/json/MetaschemaJsonReader.java index 6018eb3b6..bc9238121 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/json/MetaschemaJsonReader.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/json/MetaschemaJsonReader.java @@ -407,7 +407,7 @@ private IBoundObject readComplexDefinitionObject( ? bodyHandler : new JsonKeyBodyHandler(jsonKey, bodyHandler); - JsonLocation location = getReader().currentLocation(); + @SuppressWarnings("resource") JsonLocation location = getReader().currentLocation(); // construct the item IBoundObject item = definition.newInstance( @@ -486,7 +486,7 @@ public void accept( IBoundObject parent, IJsonProblemHandler problemHandler) throws IOException { - JsonParser parser = getReader(); + @SuppressWarnings("resource") JsonParser parser = getReader(); JsonUtil.assertCurrent(parser, JsonToken.FIELD_NAME); // the field will be the JSON key @@ -523,7 +523,7 @@ public void accept( IBoundObject parent, IJsonProblemHandler problemHandler) throws IOException { - JsonParser parser = getReader(); + @SuppressWarnings("resource") JsonParser parser = getReader(); // advance past the start object JsonUtil.assertAndAdvance(parser, JsonToken.START_OBJECT); diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/annotations/AllowedValues.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/annotations/AllowedValues.java index f4e4641fd..e2ee240dc 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/annotations/AllowedValues.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/annotations/AllowedValues.java @@ -105,14 +105,6 @@ @NonNull IAllowedValuesConstraint.Extensible extensible() default IAllowedValuesConstraint.Extensible.EXTERNAL; - /** - * The message to emit when the constraint is violated. - * - * @return the message or an empty string otherwise - */ - @NonNull - String message() default ""; - /** * Any remarks about the constraint, encoded as an escaped Markdown string. * diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/impl/ConstraintFactory.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/impl/ConstraintFactory.java index 0e07aa0e3..42bdbb8f6 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/impl/ConstraintFactory.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/impl/ConstraintFactory.java @@ -10,6 +10,7 @@ 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.IAttributable; +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.IAllowedValue; @@ -114,7 +115,8 @@ static String toMetapath(@NonNull String metapath) { return builder; } - static > T applyMessage(@NonNull T builder, @Nullable String message) { + static > T applyMessage(@NonNull T builder, + @Nullable String message) { if (message != null && !message.isBlank()) { builder.message(message); } @@ -168,7 +170,6 @@ static IAllowedValuesConstraint newAllowedValuesConstraint( .level(constraint.level()); applyTarget(builder, constraint.target()); applyProperties(builder, constraint.properties()); - applyMessage(builder, constraint.message()); applyRemarks(builder, constraint.remarks()); applyAllowedValues(builder, constraint); diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/BindingConstraintLoader.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/BindingConstraintLoader.java index c45ffd26d..8456e7d34 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/BindingConstraintLoader.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/BindingConstraintLoader.java @@ -100,8 +100,7 @@ protected List parseResource(@NonNull URI resource, @NonNull Deq // now check if this constraint set imports other constraint sets List imports = CollectionUtil.listOrEmpty(obj.getImports()); - @NonNull - Set importedConstraints; + @NonNull Set importedConstraints; if (imports.isEmpty()) { importedConstraints = CollectionUtil.emptySet(); } else { diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/IConfigurableMessageConstraintBase.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/IConfigurableMessageConstraintBase.java new file mode 100644 index 000000000..0fbec1dea --- /dev/null +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/IConfigurableMessageConstraintBase.java @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.databind.model.metaschema; + +import edu.umd.cs.findbugs.annotations.Nullable; + +/** + * Represents constraint metadata that is common to all constraints. + */ +public interface IConfigurableMessageConstraintBase extends IConstraintBase { + /** + * Get a custom message to use when the constraint is not satisfied. + *

+ * A custom message allow for more meaningful information, tailored to the test + * case, to be provided in the case a constraint is not satisfied. + * + * @return the message or {@code null} if a default message is to be used + */ + @Nullable + String getMessage(); +} diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/IConstraintBase.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/IConstraintBase.java index d64b3979c..57d32402a 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/IConstraintBase.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/IConstraintBase.java @@ -60,17 +60,6 @@ public interface IConstraintBase { @Nullable List getProps(); - /** - * Get a custom message to use when the constraint is not satisfied. - *

- * A custom message allow for more meaningful information, tailored to the test - * case, to be provided in the case a constraint is not satisfied. - * - * @return the message or {@code null} if a default message is to be used - */ - @Nullable - String getMessage(); - /** * Get the optional remarks that provide additional details explanation the * intent or use of the constraint. diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/FlagAllowedValues.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/FlagAllowedValues.java index d8cd4de81..69da4cc2b 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/FlagAllowedValues.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/FlagAllowedValues.java @@ -118,11 +118,6 @@ public class FlagAllowedValues implements IBoundObject, IConstraintBase { groupAs = @GroupAs(name = "enums", inJson = JsonGroupAsBehavior.LIST)) private List _enums; - @BoundField( - formalName = "Constraint Condition Violation Message", - useName = "message") - private String _message; - @BoundField( formalName = "Remarks", description = "Any explanatory or helpful information to be provided about the remarks parent.", @@ -267,15 +262,6 @@ public boolean removeEnum(ConstraintValueEnum item) { return _enums != null && _enums.remove(value); } - @Override - public String getMessage() { - return _message; - } - - public void setMessage(String value) { - _message = value; - } - @Override public Remarks getRemarks() { return _remarks; diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/FlagExpect.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/FlagExpect.java index da7b4ee76..5aa748e67 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/FlagExpect.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/FlagExpect.java @@ -22,7 +22,7 @@ import gov.nist.secauto.metaschema.databind.model.annotations.GroupAs; import gov.nist.secauto.metaschema.databind.model.annotations.MetaschemaAssembly; import gov.nist.secauto.metaschema.databind.model.annotations.ValueConstraints; -import gov.nist.secauto.metaschema.databind.model.metaschema.IConstraintBase; +import gov.nist.secauto.metaschema.databind.model.metaschema.IConfigurableMessageConstraintBase; import org.apache.commons.lang3.builder.ReflectionToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; @@ -38,7 +38,7 @@ formalName = "Expect Condition Constraint", name = "flag-expect", moduleClass = MetaschemaModelModule.class) -public class FlagExpect implements IBoundObject, IConstraintBase { +public class FlagExpect implements IBoundObject, IConfigurableMessageConstraintBase { private final IMetaschemaData __metaschemaData; @BoundFlag( diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/FlagIndexHasKey.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/FlagIndexHasKey.java index 41f3b293a..4be00448c 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/FlagIndexHasKey.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/FlagIndexHasKey.java @@ -21,7 +21,7 @@ import gov.nist.secauto.metaschema.databind.model.annotations.GroupAs; import gov.nist.secauto.metaschema.databind.model.annotations.MetaschemaAssembly; import gov.nist.secauto.metaschema.databind.model.annotations.ValueConstraints; -import gov.nist.secauto.metaschema.databind.model.metaschema.IConstraintBase; +import gov.nist.secauto.metaschema.databind.model.metaschema.IConfigurableMessageConstraintBase; import org.apache.commons.lang3.builder.ReflectionToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; @@ -37,7 +37,7 @@ formalName = "Index Has Key Constraint", name = "flag-index-has-key", moduleClass = MetaschemaModelModule.class) -public class FlagIndexHasKey implements IBoundObject, IConstraintBase { +public class FlagIndexHasKey implements IBoundObject, IConfigurableMessageConstraintBase { private final IMetaschemaData __metaschemaData; @BoundFlag( diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/FlagMatches.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/FlagMatches.java index b42bfd1c0..b6f78c1b1 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/FlagMatches.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/FlagMatches.java @@ -22,7 +22,7 @@ import gov.nist.secauto.metaschema.databind.model.annotations.GroupAs; import gov.nist.secauto.metaschema.databind.model.annotations.MetaschemaAssembly; import gov.nist.secauto.metaschema.databind.model.annotations.ValueConstraints; -import gov.nist.secauto.metaschema.databind.model.metaschema.IConstraintBase; +import gov.nist.secauto.metaschema.databind.model.metaschema.IConfigurableMessageConstraintBase; import org.apache.commons.lang3.builder.ReflectionToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; @@ -38,7 +38,7 @@ formalName = "Value Matches Constraint", name = "flag-matches", moduleClass = MetaschemaModelModule.class) -public class FlagMatches implements IBoundObject, IConstraintBase { +public class FlagMatches implements IBoundObject, IConfigurableMessageConstraintBase { private final IMetaschemaData __metaschemaData; @BoundFlag( diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/TargetedAllowedValuesConstraint.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/TargetedAllowedValuesConstraint.java index d548760e8..835abba9e 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/TargetedAllowedValuesConstraint.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/TargetedAllowedValuesConstraint.java @@ -126,11 +126,6 @@ public class TargetedAllowedValuesConstraint implements IBoundObject, ITargetedC groupAs = @GroupAs(name = "enums", inJson = JsonGroupAsBehavior.LIST)) private List _enums; - @BoundField( - formalName = "Constraint Condition Violation Message", - useName = "message") - private String _message; - @BoundField( formalName = "Remarks", description = "Any explanatory or helpful information to be provided about the remarks parent.", @@ -284,15 +279,6 @@ public boolean removeEnum(ConstraintValueEnum item) { return _enums != null && _enums.remove(value); } - @Override - public String getMessage() { - return _message; - } - - public void setMessage(String value) { - _message = value; - } - @Override public Remarks getRemarks() { return _remarks; diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/TargetedExpectConstraint.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/TargetedExpectConstraint.java index c90f4063a..c7aec1ab7 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/TargetedExpectConstraint.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/TargetedExpectConstraint.java @@ -22,6 +22,7 @@ import gov.nist.secauto.metaschema.databind.model.annotations.GroupAs; import gov.nist.secauto.metaschema.databind.model.annotations.MetaschemaAssembly; import gov.nist.secauto.metaschema.databind.model.annotations.ValueConstraints; +import gov.nist.secauto.metaschema.databind.model.metaschema.IConfigurableMessageConstraintBase; import gov.nist.secauto.metaschema.databind.model.metaschema.ITargetedConstraintBase; import org.apache.commons.lang3.builder.ReflectionToStringBuilder; @@ -38,7 +39,8 @@ formalName = "Expect Condition Constraint", name = "targeted-expect-constraint", moduleClass = MetaschemaModelModule.class) -public class TargetedExpectConstraint implements IBoundObject, ITargetedConstraintBase { +public class TargetedExpectConstraint + implements IBoundObject, ITargetedConstraintBase, IConfigurableMessageConstraintBase { private final IMetaschemaData __metaschemaData; @BoundFlag( diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/TargetedHasCardinalityConstraint.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/TargetedHasCardinalityConstraint.java index c8a55d301..3968594f6 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/TargetedHasCardinalityConstraint.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/TargetedHasCardinalityConstraint.java @@ -24,6 +24,7 @@ import gov.nist.secauto.metaschema.databind.model.annotations.Matches; import gov.nist.secauto.metaschema.databind.model.annotations.MetaschemaAssembly; import gov.nist.secauto.metaschema.databind.model.annotations.ValueConstraints; +import gov.nist.secauto.metaschema.databind.model.metaschema.IConfigurableMessageConstraintBase; import gov.nist.secauto.metaschema.databind.model.metaschema.ITargetedConstraintBase; import org.apache.commons.lang3.builder.ReflectionToStringBuilder; @@ -41,7 +42,8 @@ formalName = "Targeted Cardinality Constraint", name = "targeted-has-cardinality-constraint", moduleClass = MetaschemaModelModule.class) -public class TargetedHasCardinalityConstraint implements IBoundObject, ITargetedConstraintBase { +public class TargetedHasCardinalityConstraint + implements IBoundObject, ITargetedConstraintBase, IConfigurableMessageConstraintBase { private final IMetaschemaData __metaschemaData; @BoundFlag( diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/TargetedIndexConstraint.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/TargetedIndexConstraint.java index 9aed9e682..c2199ea3f 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/TargetedIndexConstraint.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/TargetedIndexConstraint.java @@ -22,6 +22,7 @@ import gov.nist.secauto.metaschema.databind.model.annotations.GroupAs; import gov.nist.secauto.metaschema.databind.model.annotations.MetaschemaAssembly; import gov.nist.secauto.metaschema.databind.model.annotations.ValueConstraints; +import gov.nist.secauto.metaschema.databind.model.metaschema.IConfigurableMessageConstraintBase; import gov.nist.secauto.metaschema.databind.model.metaschema.ITargetedConstraintBase; import org.apache.commons.lang3.builder.ReflectionToStringBuilder; @@ -38,7 +39,8 @@ formalName = "Targeted Index Constraint", name = "targeted-index-constraint", moduleClass = MetaschemaModelModule.class) -public class TargetedIndexConstraint implements IBoundObject, ITargetedConstraintBase { +public class TargetedIndexConstraint + implements IBoundObject, ITargetedConstraintBase, IConfigurableMessageConstraintBase { private final IMetaschemaData __metaschemaData; @BoundFlag( diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/TargetedIndexHasKeyConstraint.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/TargetedIndexHasKeyConstraint.java index 1cb2aa8c7..1dd967039 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/TargetedIndexHasKeyConstraint.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/TargetedIndexHasKeyConstraint.java @@ -22,6 +22,7 @@ import gov.nist.secauto.metaschema.databind.model.annotations.GroupAs; import gov.nist.secauto.metaschema.databind.model.annotations.MetaschemaAssembly; import gov.nist.secauto.metaschema.databind.model.annotations.ValueConstraints; +import gov.nist.secauto.metaschema.databind.model.metaschema.IConfigurableMessageConstraintBase; import gov.nist.secauto.metaschema.databind.model.metaschema.ITargetedConstraintBase; import org.apache.commons.lang3.builder.ReflectionToStringBuilder; @@ -38,7 +39,8 @@ formalName = "Targeted Index Has Key Constraint", name = "targeted-index-has-key-constraint", moduleClass = MetaschemaModelModule.class) -public class TargetedIndexHasKeyConstraint implements IBoundObject, ITargetedConstraintBase { +public class TargetedIndexHasKeyConstraint + implements IBoundObject, ITargetedConstraintBase, IConfigurableMessageConstraintBase { private final IMetaschemaData __metaschemaData; @BoundFlag( diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/TargetedIsUniqueConstraint.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/TargetedIsUniqueConstraint.java index 76ab5cf2e..5cc57fc37 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/TargetedIsUniqueConstraint.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/TargetedIsUniqueConstraint.java @@ -22,6 +22,7 @@ import gov.nist.secauto.metaschema.databind.model.annotations.GroupAs; import gov.nist.secauto.metaschema.databind.model.annotations.MetaschemaAssembly; import gov.nist.secauto.metaschema.databind.model.annotations.ValueConstraints; +import gov.nist.secauto.metaschema.databind.model.metaschema.IConfigurableMessageConstraintBase; import gov.nist.secauto.metaschema.databind.model.metaschema.ITargetedConstraintBase; import org.apache.commons.lang3.builder.ReflectionToStringBuilder; @@ -38,7 +39,8 @@ formalName = "Targeted Unique Constraint", name = "targeted-is-unique-constraint", moduleClass = MetaschemaModelModule.class) -public class TargetedIsUniqueConstraint implements IBoundObject, ITargetedConstraintBase { +public class TargetedIsUniqueConstraint + implements IBoundObject, ITargetedConstraintBase, IConfigurableMessageConstraintBase { private final IMetaschemaData __metaschemaData; @BoundFlag( diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/TargetedMatchesConstraint.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/TargetedMatchesConstraint.java index 5035e981b..3ed89ac72 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/TargetedMatchesConstraint.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/binding/TargetedMatchesConstraint.java @@ -22,6 +22,7 @@ import gov.nist.secauto.metaschema.databind.model.annotations.GroupAs; import gov.nist.secauto.metaschema.databind.model.annotations.MetaschemaAssembly; import gov.nist.secauto.metaschema.databind.model.annotations.ValueConstraints; +import gov.nist.secauto.metaschema.databind.model.metaschema.IConfigurableMessageConstraintBase; import gov.nist.secauto.metaschema.databind.model.metaschema.ITargetedConstraintBase; import org.apache.commons.lang3.builder.ReflectionToStringBuilder; @@ -38,7 +39,8 @@ formalName = "Value Matches Constraint", name = "targeted-matches-constraint", moduleClass = MetaschemaModelModule.class) -public class TargetedMatchesConstraint implements IBoundObject, ITargetedConstraintBase { +public class TargetedMatchesConstraint + implements IBoundObject, ITargetedConstraintBase, IConfigurableMessageConstraintBase { private final IMetaschemaData __metaschemaData; @BoundFlag( diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/impl/ConstraintBindingSupport.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/impl/ConstraintBindingSupport.java index 292bb3096..e2c270153 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/impl/ConstraintBindingSupport.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/impl/ConstraintBindingSupport.java @@ -8,6 +8,7 @@ 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.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; @@ -24,6 +25,7 @@ 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; @@ -241,7 +243,7 @@ private static IExpectConstraint newExpect( @NonNull ISource source) { IExpectConstraint.Builder builder = IExpectConstraint.builder() .test(target(ObjectUtils.requireNonNull(obj.getTest()))); - applyCommonValues(obj, null, source, builder); + applyConfigurableCommonValues(obj, null, source, builder); String message = obj.getMessage(); if (message != null) { @@ -257,7 +259,7 @@ private static IExpectConstraint newExpect( @NonNull ISource source) { IExpectConstraint.Builder builder = IExpectConstraint.builder() .test(target(ObjectUtils.requireNonNull(obj.getTest()))); - applyCommonValues(obj, obj.getTarget(), source, builder); + applyConfigurableCommonValues(obj, obj.getTarget(), source, builder); return builder.build(); } @@ -285,7 +287,7 @@ private static IIndexHasKeyConstraint newIndexHasKey( @NonNull FlagIndexHasKey obj, @NonNull ISource source) { IIndexHasKeyConstraint.Builder builder = IIndexHasKeyConstraint.builder(ObjectUtils.requireNonNull(obj.getName())); - applyCommonValues(obj, null, source, builder); + applyConfigurableCommonValues(obj, null, source, builder); handleKeyConstraints(ObjectUtils.requireNonNull(obj.getKeyFields()), builder, source); return builder.build(); } @@ -295,7 +297,7 @@ private static IIndexHasKeyConstraint newIndexHasKey( @NonNull TargetedIndexHasKeyConstraint obj, @NonNull ISource source) { IIndexHasKeyConstraint.Builder builder = IIndexHasKeyConstraint.builder(ObjectUtils.requireNonNull(obj.getName())); - applyCommonValues(obj, obj.getTarget(), source, builder); + applyConfigurableCommonValues(obj, obj.getTarget(), source, builder); handleKeyConstraints(ObjectUtils.requireNonNull(obj.getKeyFields()), builder, source); return builder.build(); } @@ -305,7 +307,7 @@ private static IMatchesConstraint newMatches( @NonNull FlagMatches obj, @NonNull ISource source) { IMatchesConstraint.Builder builder = IMatchesConstraint.builder(); - applyCommonValues(obj, null, source, builder); + applyConfigurableCommonValues(obj, null, source, builder); Pattern regex = pattern(obj.getRegex()); if (regex != null) { @@ -326,7 +328,7 @@ private static IMatchesConstraint newMatches( @NonNull TargetedMatchesConstraint obj, @NonNull ISource source) { IMatchesConstraint.Builder builder = IMatchesConstraint.builder(); - applyCommonValues(obj, obj.getTarget(), source, builder); + applyConfigurableCommonValues(obj, obj.getTarget(), source, builder); Pattern regex = pattern(obj.getRegex()); if (regex != null) { @@ -347,7 +349,7 @@ private static IIndexConstraint newIndex( @NonNull TargetedIndexConstraint obj, @NonNull ISource source) { IIndexConstraint.Builder builder = IIndexConstraint.builder(ObjectUtils.requireNonNull(obj.getName())); - applyCommonValues(obj, obj.getTarget(), source, builder); + applyConfigurableCommonValues(obj, obj.getTarget(), source, builder); handleKeyConstraints(ObjectUtils.requireNonNull(obj.getKeyFields()), builder, source); return builder.build(); @@ -358,7 +360,7 @@ private static ICardinalityConstraint newHasCardinality( @NonNull TargetedHasCardinalityConstraint obj, @NonNull ISource source) { ICardinalityConstraint.Builder builder = ICardinalityConstraint.builder(); - applyCommonValues(obj, obj.getTarget(), source, builder); + applyConfigurableCommonValues(obj, obj.getTarget(), source, builder); BigInteger minOccurs = obj.getMinOccurs(); if (minOccurs != null) { @@ -378,12 +380,27 @@ private static IUniqueConstraint newUnique( @NonNull TargetedIsUniqueConstraint obj, @NonNull ISource source) { IUniqueConstraint.Builder builder = IUniqueConstraint.builder(); - applyCommonValues(obj, obj.getTarget(), source, builder); + applyConfigurableCommonValues(obj, obj.getTarget(), source, builder); handleKeyConstraints(ObjectUtils.requireNonNull(obj.getKeyFields()), builder, source); return builder.build(); } + @NonNull + private static > 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 applyCommonValues( @NonNull IConstraintBase constraint, @@ -410,11 +427,6 @@ private static IUniqueConstraint newUnique( List props = ObjectUtils.requireNonNull(constraint.getProps()); builder.properties(ModelSupport.parseProperties(props)); - String message = constraint.getMessage(); - if (message != null) { - builder.message(message); - } - Remarks remarks = constraint.getRemarks(); if (remarks != null) { builder.remarks(ObjectUtils.notNull(remarks.getRemark()));