1
2
3
4
5
6 package gov.nist.secauto.metaschema.core.model.constraint;
7
8 import gov.nist.secauto.metaschema.core.datatype.IDataTypeAdapter;
9 import gov.nist.secauto.metaschema.core.metapath.DynamicContext;
10 import gov.nist.secauto.metaschema.core.metapath.ISequence;
11 import gov.nist.secauto.metaschema.core.metapath.format.IPathFormatter;
12 import gov.nist.secauto.metaschema.core.metapath.function.library.FnData;
13 import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem;
14 import gov.nist.secauto.metaschema.core.util.CustomCollectors;
15
16 import java.util.List;
17 import java.util.Objects;
18 import java.util.regex.Pattern;
19 import java.util.stream.Collectors;
20
21 import edu.umd.cs.findbugs.annotations.NonNull;
22
23
24
25
26 public abstract class AbstractConstraintValidationHandler implements IConstraintValidationHandler {
27 @NonNull
28 private IPathFormatter pathFormatter = IPathFormatter.METAPATH_PATH_FORMATER;
29
30
31
32
33
34
35
36 @NonNull
37 public IPathFormatter getPathFormatter() {
38 return pathFormatter;
39 }
40
41
42
43
44
45
46
47
48 public void setPathFormatter(@NonNull IPathFormatter formatter) {
49 this.pathFormatter = Objects.requireNonNull(formatter, "pathFormatter");
50 }
51
52
53
54
55
56
57
58
59
60 protected String toPath(@NonNull INodeItem item) {
61 return item.toPath(getPathFormatter());
62 }
63
64
65
66
67
68
69
70
71
72
73
74
75
76 @SuppressWarnings("null")
77 @NonNull
78 protected String newCardinalityMinimumViolationMessage(
79 @NonNull ICardinalityConstraint constraint,
80 @NonNull INodeItem node,
81 @NonNull ISequence<? extends INodeItem> targets) {
82 return String.format(
83 "The cardinality '%d' is below the required minimum '%d' for items matching '%s'.",
84 targets.size(),
85 constraint.getMinOccurs(),
86 constraint.getTarget());
87 }
88
89
90
91
92
93
94
95
96
97
98
99
100
101 @SuppressWarnings("null")
102 @NonNull
103 protected String newCardinalityMaximumViolationMessage(
104 @NonNull ICardinalityConstraint constraint,
105 @NonNull INodeItem node,
106 @NonNull ISequence<? extends INodeItem> targets) {
107 return String.format(
108 "The cardinality '%d' is greater than the required maximum '%d' at: %s.",
109 targets.size(),
110 constraint.getMinOccurs(),
111 targets.safeStream()
112 .map(item -> new StringBuilder(12)
113 .append('\'')
114 .append(toPath(item))
115 .append('\'')
116 .toString())
117 .collect(CustomCollectors.joiningWithOxfordComma("and")));
118 }
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134 @SuppressWarnings("null")
135 @NonNull
136 protected String newIndexDuplicateKeyViolationMessage(
137 @NonNull IIndexConstraint constraint,
138 @NonNull INodeItem node,
139 @NonNull INodeItem oldItem,
140 @NonNull INodeItem newItem) {
141
142 return String.format("Index '%s' has duplicate key for items at paths '%s' and '%s'",
143 constraint.getName(),
144 toPath(oldItem),
145 toPath(newItem));
146 }
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162 @SuppressWarnings("null")
163 @NonNull
164 protected String newUniqueKeyViolationMessage(
165 @NonNull IUniqueConstraint constraint,
166 @NonNull INodeItem node,
167 @NonNull INodeItem oldItem,
168 @NonNull INodeItem newItem) {
169 return String.format("Unique constraint violation at paths '%s' and '%s'",
170 toPath(oldItem),
171 toPath(newItem));
172 }
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190 @SuppressWarnings("null")
191 @NonNull
192 protected String newMatchPatternViolationMessage(
193 @NonNull IMatchesConstraint constraint,
194 @NonNull INodeItem node,
195 @NonNull INodeItem target,
196 @NonNull String value,
197 @NonNull Pattern pattern) {
198 return String.format("Value '%s' did not match the pattern '%s' at path '%s'",
199 value,
200 pattern.pattern(),
201 toPath(target));
202 }
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220 @SuppressWarnings("null")
221 @NonNull
222 protected String newMatchDatatypeViolationMessage(
223 @NonNull IMatchesConstraint constraint,
224 @NonNull INodeItem node,
225 @NonNull INodeItem target,
226 @NonNull String value,
227 @NonNull IDataTypeAdapter<?> adapter) {
228 return String.format("Value '%s' did not conform to the data type '%s' at path '%s'", value,
229 adapter.getPreferredName(), toPath(target));
230 }
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247 @SuppressWarnings("null")
248 @NonNull
249 protected String newExpectViolationMessage(
250 @NonNull IExpectConstraint constraint,
251 @SuppressWarnings("unused") @NonNull INodeItem node,
252 @NonNull INodeItem target,
253 @NonNull DynamicContext dynamicContext) {
254 String message;
255 if (constraint.getMessage() != null) {
256 message = constraint.generateMessage(target, dynamicContext);
257 } else {
258 message = String.format("Expect constraint '%s' did not match the data at path '%s'",
259 constraint.getTest(),
260 toPath(target));
261 }
262 return message;
263 }
264
265
266
267
268
269
270
271
272
273
274
275 @SuppressWarnings("null")
276 @NonNull
277 protected String newAllowedValuesViolationMessage(
278 @NonNull List<IAllowedValuesConstraint> constraints,
279 @NonNull INodeItem target) {
280
281 String allowedValues = constraints.stream()
282 .flatMap(constraint -> constraint.getAllowedValues().values().stream())
283 .map(IAllowedValue::getValue)
284 .sorted()
285 .distinct()
286 .collect(CustomCollectors.joiningWithOxfordComma("or"));
287
288 return String.format("Value '%s' doesn't match one of '%s' at path '%s'",
289 FnData.fnDataItem(target).asString(),
290 allowedValues,
291 toPath(target));
292 }
293
294
295
296
297
298
299
300
301
302
303
304 @SuppressWarnings("null")
305 @NonNull
306 protected String newIndexDuplicateViolationMessage(
307 @NonNull IIndexConstraint constraint,
308 @NonNull INodeItem node) {
309 return String.format("Duplicate index named '%s' found at path '%s'",
310 constraint.getName(),
311 node.getMetapath());
312 }
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328 @SuppressWarnings("null")
329 @NonNull
330 protected String newIndexMissMessage(
331 @NonNull IIndexHasKeyConstraint constraint,
332 @NonNull INodeItem node,
333 @NonNull INodeItem target,
334 @NonNull List<String> key) {
335 String keyValues = key.stream()
336 .collect(Collectors.joining(","));
337
338 return String.format("Key reference [%s] not found in index '%s' for item at path '%s'",
339 keyValues,
340 constraint.getIndexName(),
341 target.getMetapath());
342 }
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358 @SuppressWarnings("null")
359 @NonNull
360 protected String newMissingIndexViolationMessage(
361 @NonNull IIndexHasKeyConstraint constraint,
362 @NonNull INodeItem node,
363 @NonNull INodeItem target,
364 @NonNull String message) {
365 return String.format("%s for constraint '%s' for item at path '%s'",
366 message,
367 Objects.requireNonNullElse(constraint.getId(), "?"),
368 target.getMetapath());
369 }
370 }