001/*
002 * Portions of this software was developed by employees of the National Institute
003 * of Standards and Technology (NIST), an agency of the Federal Government and is
004 * being made available as a public service. Pursuant to title 17 United States
005 * Code Section 105, works of NIST employees are not subject to copyright
006 * protection in the United States. This software may be subject to foreign
007 * copyright. Permission in the United States and in foreign countries, to the
008 * extent that NIST may hold copyright, to use, copy, modify, create derivative
009 * works, and distribute this software and its documentation without fee is hereby
010 * granted on a non-exclusive basis, provided that this notice and disclaimer
011 * of warranty appears in all copies.
012 *
013 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
014 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
015 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
016 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
017 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
018 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
019 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
020 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
021 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
022 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
023 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
024 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
025 */
026
027package gov.nist.secauto.metaschema.core.model.constraint;
028
029import gov.nist.secauto.metaschema.core.datatype.IDataTypeAdapter;
030import gov.nist.secauto.metaschema.core.metapath.DynamicContext;
031import gov.nist.secauto.metaschema.core.metapath.ISequence;
032import gov.nist.secauto.metaschema.core.metapath.format.IPathFormatter;
033import gov.nist.secauto.metaschema.core.metapath.function.library.FnData;
034import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem;
035import gov.nist.secauto.metaschema.core.util.CustomCollectors;
036
037import java.util.List;
038import java.util.stream.Collectors;
039
040import edu.umd.cs.findbugs.annotations.NonNull;
041
042public abstract class AbstractConstraintValidationHandler implements IConstraintValidationHandler {
043  @NonNull
044  public abstract IPathFormatter getPathFormatter();
045
046  protected String toPath(@NonNull INodeItem nodeItem) {
047    return nodeItem.toPath(getPathFormatter());
048  }
049
050  @SuppressWarnings("null")
051  @NonNull
052  protected String newCardinalityMinimumViolationMessage(
053      @NonNull ICardinalityConstraint constraint,
054      @SuppressWarnings("unused") @NonNull INodeItem node,
055      @NonNull ISequence<? extends INodeItem> targets) {
056    // TODO: render the item paths instead of the expression
057    return String.format(
058        "The cardinality '%d' is below the required minimum '%d' for items matching the expression '%s'.",
059        targets.size(), constraint.getMinOccurs(), constraint.getTarget().getPath());
060  }
061
062  @SuppressWarnings("null")
063  @NonNull
064  protected String newCardinalityMaximumViolationMessage(
065      @NonNull ICardinalityConstraint constraint,
066      @SuppressWarnings("unused") @NonNull INodeItem node,
067      @NonNull ISequence<? extends INodeItem> targets) {
068    // TODO: render the item paths instead of the expression
069    return String.format(
070        "The cardinality '%d' is greater than the required maximum '%d' for items matching the expression '%s'.",
071        targets.size(), constraint.getMinOccurs(), constraint.getTarget().getPath());
072  }
073
074  @SuppressWarnings("null")
075  @NonNull
076  protected String newIndexDuplicateKeyViolationMessage(
077      @NonNull IIndexConstraint constraint,
078      @SuppressWarnings("unused") @NonNull INodeItem node,
079      @NonNull INodeItem oldItem,
080      @NonNull INodeItem target) {
081    // TODO: render the key paths
082    return String.format("Index '%s' has duplicate key for items at paths '%s' and '%s'", constraint.getName(),
083        toPath(oldItem), toPath(target));
084  }
085
086  @SuppressWarnings("null")
087  @NonNull
088  protected String newUniqueKeyViolationMessage(
089      @SuppressWarnings("unused") @NonNull IUniqueConstraint constraint,
090      @SuppressWarnings("unused") @NonNull INodeItem node,
091      @NonNull INodeItem oldItem,
092      @NonNull INodeItem target) {
093    // TODO: render the key paths
094    return String.format("Unique constraint violation at paths '%s' and '%s'",
095        toPath(oldItem), toPath(target));
096  }
097
098  @SuppressWarnings("null")
099  @NonNull
100  protected String newMatchPatternViolationMessage(
101      @NonNull IMatchesConstraint constraint,
102      @SuppressWarnings("unused") @NonNull INodeItem node,
103      @NonNull INodeItem target,
104      @NonNull String value) {
105    return String.format("Value '%s' did not match the pattern '%s' at path '%s'",
106        value,
107        constraint.getPattern().pattern(),
108        toPath(target));
109  }
110
111  @SuppressWarnings("null")
112  @NonNull
113  protected String newMatchDatatypeViolationMessage(
114      @NonNull IMatchesConstraint constraint,
115      @SuppressWarnings("unused") @NonNull INodeItem node,
116      @NonNull INodeItem target,
117      @NonNull String value) {
118    IDataTypeAdapter<?> adapter = constraint.getDataType();
119    return String.format("Value '%s' did not conform to the data type '%s' at path '%s'", value,
120        adapter.getPreferredName(), toPath(target));
121  }
122
123  @SuppressWarnings("null")
124  @NonNull
125  protected CharSequence newExpectViolationMessage(
126      @NonNull IExpectConstraint constraint,
127      @SuppressWarnings("unused") @NonNull INodeItem node,
128      @NonNull INodeItem target,
129      @NonNull DynamicContext dynamicContext) {
130    CharSequence message;
131    if (constraint.getMessage() != null) {
132      message = constraint.generateMessage(target, dynamicContext);
133    } else {
134      message = String.format("Expect constraint '%s' did not match the data at path '%s'",
135          constraint.getTest().getPath(),
136          toPath(target));
137    }
138    return message;
139  }
140
141  @SuppressWarnings("null")
142  @NonNull
143  protected CharSequence newAllowedValuesViolationMessage(
144      @NonNull List<IAllowedValuesConstraint> constraints,
145      @NonNull INodeItem target) {
146
147    String allowedValues = constraints.stream()
148        .flatMap(constraint -> constraint.getAllowedValues().values().stream())
149        .map(allowedValue -> allowedValue.getValue())
150        .sorted()
151        .distinct()
152        .collect(CustomCollectors.joiningWithOxfordComma("or"));
153
154    return String.format("Value '%s' doesn't match one of '%s' at path '%s'",
155        FnData.fnDataItem(target).asString(),
156        allowedValues,
157        toPath(target));
158  }
159
160  @SuppressWarnings("null")
161  @NonNull
162  protected CharSequence newIndexDuplicateViolationMessage(
163      @NonNull IIndexConstraint constraint,
164      @NonNull INodeItem node) {
165    return String.format("Duplicate index named '%s' found at path '%s'",
166        constraint.getName(),
167        node.getMetapath());
168  }
169
170  @SuppressWarnings("null")
171  @NonNull
172  protected CharSequence newIndexMissMessage(
173      @NonNull IIndexHasKeyConstraint constraint,
174      @SuppressWarnings("unused") @NonNull INodeItem node,
175      @NonNull INodeItem target,
176      @NonNull List<String> key) {
177    String keyValues = key.stream()
178        .collect(Collectors.joining(","));
179
180    return String.format("Key reference [%s] not found in index '%s' for item at path '%s'",
181        keyValues,
182        constraint.getIndexName(),
183        target.getMetapath());
184  }
185
186}