1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 package gov.nist.secauto.metaschema.core.model.constraint;
28
29 import gov.nist.secauto.metaschema.core.datatype.IDataTypeAdapter;
30 import gov.nist.secauto.metaschema.core.metapath.DynamicContext;
31 import gov.nist.secauto.metaschema.core.metapath.ISequence;
32 import gov.nist.secauto.metaschema.core.metapath.format.IPathFormatter;
33 import gov.nist.secauto.metaschema.core.metapath.function.library.FnData;
34 import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem;
35 import gov.nist.secauto.metaschema.core.util.CustomCollectors;
36
37 import java.util.List;
38 import java.util.stream.Collectors;
39
40 import edu.umd.cs.findbugs.annotations.NonNull;
41
42 public abstract class AbstractConstraintValidationHandler implements IConstraintValidationHandler {
43 @NonNull
44 public abstract IPathFormatter getPathFormatter();
45
46 protected String toPath(@NonNull INodeItem nodeItem) {
47 return nodeItem.toPath(getPathFormatter());
48 }
49
50 @SuppressWarnings("null")
51 @NonNull
52 protected String newCardinalityMinimumViolationMessage(
53 @NonNull ICardinalityConstraint constraint,
54 @SuppressWarnings("unused") @NonNull INodeItem node,
55 @NonNull ISequence<? extends INodeItem> targets) {
56
57 return String.format(
58 "The cardinality '%d' is below the required minimum '%d' for items matching the expression '%s'.",
59 targets.size(), constraint.getMinOccurs(), constraint.getTarget().getPath());
60 }
61
62 @SuppressWarnings("null")
63 @NonNull
64 protected String newCardinalityMaximumViolationMessage(
65 @NonNull ICardinalityConstraint constraint,
66 @SuppressWarnings("unused") @NonNull INodeItem node,
67 @NonNull ISequence<? extends INodeItem> targets) {
68
69 return String.format(
70 "The cardinality '%d' is greater than the required maximum '%d' for items matching the expression '%s'.",
71 targets.size(), constraint.getMinOccurs(), constraint.getTarget().getPath());
72 }
73
74 @SuppressWarnings("null")
75 @NonNull
76 protected String newIndexDuplicateKeyViolationMessage(
77 @NonNull IIndexConstraint constraint,
78 @SuppressWarnings("unused") @NonNull INodeItem node,
79 @NonNull INodeItem oldItem,
80 @NonNull INodeItem target) {
81
82 return String.format("Index '%s' has duplicate key for items at paths '%s' and '%s'", constraint.getName(),
83 toPath(oldItem), toPath(target));
84 }
85
86 @SuppressWarnings("null")
87 @NonNull
88 protected String newUniqueKeyViolationMessage(
89 @SuppressWarnings("unused") @NonNull IUniqueConstraint constraint,
90 @SuppressWarnings("unused") @NonNull INodeItem node,
91 @NonNull INodeItem oldItem,
92 @NonNull INodeItem target) {
93
94 return String.format("Unique constraint violation at paths '%s' and '%s'",
95 toPath(oldItem), toPath(target));
96 }
97
98 @SuppressWarnings("null")
99 @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 }