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