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