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.MetapathException;
33 import gov.nist.secauto.metaschema.core.metapath.MetapathExpression;
34 import gov.nist.secauto.metaschema.core.metapath.function.library.FnBoolean;
35 import gov.nist.secauto.metaschema.core.metapath.function.library.FnData;
36 import gov.nist.secauto.metaschema.core.metapath.item.node.AbstractNodeItemVisitor;
37 import gov.nist.secauto.metaschema.core.metapath.item.node.IAssemblyNodeItem;
38 import gov.nist.secauto.metaschema.core.metapath.item.node.IDefinitionNodeItem;
39 import gov.nist.secauto.metaschema.core.metapath.item.node.IDocumentNodeItem;
40 import gov.nist.secauto.metaschema.core.metapath.item.node.IFieldNodeItem;
41 import gov.nist.secauto.metaschema.core.metapath.item.node.IFlagNodeItem;
42 import gov.nist.secauto.metaschema.core.metapath.item.node.IModuleNodeItem;
43 import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem;
44 import gov.nist.secauto.metaschema.core.model.IAssemblyDefinition;
45 import gov.nist.secauto.metaschema.core.model.IFieldDefinition;
46 import gov.nist.secauto.metaschema.core.model.IFlagDefinition;
47 import gov.nist.secauto.metaschema.core.util.CollectionUtil;
48
49 import org.apache.logging.log4j.LogManager;
50 import org.apache.logging.log4j.Logger;
51
52 import java.util.ArrayList;
53 import java.util.LinkedHashMap;
54 import java.util.LinkedList;
55 import java.util.List;
56 import java.util.Map;
57 import java.util.concurrent.ConcurrentHashMap;
58 import java.util.regex.Pattern;
59
60 import edu.umd.cs.findbugs.annotations.NonNull;
61 import edu.umd.cs.findbugs.annotations.Nullable;
62
63
64
65
66
67
68 public class DefaultConstraintValidator implements IConstraintValidator {
69 private static final Logger LOGGER = LogManager.getLogger(DefaultConstraintValidator.class);
70
71 @NonNull
72 private final Map<INodeItem, ValueStatus> valueMap = new LinkedHashMap<>();
73 @NonNull
74 private final Map<String, IIndex> indexNameToIndexMap = new ConcurrentHashMap<>();
75 @NonNull
76 private final Map<String, List<KeyRef>> indexNameToKeyRefMap = new ConcurrentHashMap<>();
77 @NonNull
78 private final DynamicContext metapathContext;
79 @NonNull
80 private final IConstraintValidationHandler handler;
81
82 public DefaultConstraintValidator(
83 @NonNull DynamicContext metapathContext,
84 @NonNull IConstraintValidationHandler handler) {
85 this.metapathContext = metapathContext;
86 this.handler = handler;
87 }
88
89 @NonNull
90 public IConstraintValidationHandler getConstraintValidationHandler() {
91 return handler;
92 }
93
94 @NonNull
95 protected DynamicContext getMetapathContext() {
96 return metapathContext;
97 }
98
99 @Override
100 public void validate(@NonNull INodeItem item) {
101 item.accept(new Visitor(), null);
102 }
103
104
105
106
107
108
109
110
111
112
113 protected void validateFlag(@NonNull IFlagNodeItem item) {
114 IFlagDefinition definition = item.getDefinition();
115
116 validateExpect(definition.getExpectConstraints(), item);
117 validateAllowedValues(definition.getAllowedValuesConstraints(), item);
118 validateIndexHasKey(definition.getIndexHasKeyConstraints(), item);
119 validateMatches(definition.getMatchesConstraints(), item);
120 }
121
122
123
124
125
126
127
128
129
130
131 protected void validateField(@NonNull IFieldNodeItem item) {
132 IFieldDefinition definition = item.getDefinition();
133
134 validateExpect(definition.getExpectConstraints(), item);
135 validateAllowedValues(definition.getAllowedValuesConstraints(), item);
136 validateIndexHasKey(definition.getIndexHasKeyConstraints(), item);
137 validateMatches(definition.getMatchesConstraints(), item);
138 }
139
140
141
142
143
144
145
146
147
148
149 protected void validateAssembly(@NonNull IAssemblyNodeItem item) {
150 IAssemblyDefinition definition = item.getDefinition();
151
152 validateExpect(definition.getExpectConstraints(), item);
153 validateAllowedValues(definition.getAllowedValuesConstraints(), item);
154 validateIndexHasKey(definition.getIndexHasKeyConstraints(), item);
155 validateMatches(definition.getMatchesConstraints(), item);
156 validateHasCardinality(definition.getHasCardinalityConstraints(), item);
157 validateIndex(definition.getIndexConstraints(), item);
158 validateUnique(definition.getUniqueConstraints(), item);
159 }
160
161 protected void validateHasCardinality(@NonNull List<? extends ICardinalityConstraint> constraints,
162 @NonNull List<? extends IAssemblyNodeItem> items) {
163
164 items.stream().forEachOrdered(item -> {
165 assert item != null;
166 validateHasCardinality(constraints, item);
167 });
168 }
169
170 protected void validateHasCardinality(@NonNull List<? extends ICardinalityConstraint> constraints,
171 @NonNull IAssemblyNodeItem item) {
172 for (ICardinalityConstraint constraint : constraints) {
173 ISequence<? extends IDefinitionNodeItem<?, ?>> targets = constraint.matchTargets(item, getMetapathContext());
174 try {
175 validateHasCardinality(constraint, item, targets);
176 } catch (MetapathException ex) {
177 rethrowConstraintError(constraint, item, ex);
178 }
179 }
180 }
181
182 protected void validateHasCardinality(@NonNull ICardinalityConstraint constraint, @NonNull IAssemblyNodeItem node,
183 ISequence<? extends INodeItem> targets) {
184 int itemCount = targets.size();
185
186 Integer minOccurs = constraint.getMinOccurs();
187 if (minOccurs != null && itemCount < minOccurs) {
188 getConstraintValidationHandler().handleCardinalityMinimumViolation(constraint, node, targets);
189 }
190
191 Integer maxOccurs = constraint.getMaxOccurs();
192 if (maxOccurs != null && itemCount > maxOccurs) {
193 getConstraintValidationHandler().handleCardinalityMaximumViolation(constraint, node, targets);
194 }
195 }
196
197 protected void validateIndex(@NonNull List<? extends IIndexConstraint> constraints,
198 @NonNull List<? extends IAssemblyNodeItem> items) {
199 items.stream().forEachOrdered(item -> {
200 assert item != null;
201 validateIndex(constraints, item);
202 });
203 }
204
205 protected void validateIndex(@NonNull List<? extends IIndexConstraint> constraints,
206 @NonNull IAssemblyNodeItem item) {
207 for (IIndexConstraint constraint : constraints) {
208 ISequence<? extends IDefinitionNodeItem<?, ?>> targets = constraint.matchTargets(item, getMetapathContext());
209 try {
210 validateIndex(constraint, item, targets);
211 } catch (MetapathException ex) {
212 rethrowConstraintError(constraint, item, ex);
213 }
214 }
215 }
216
217 protected void validateIndex(@NonNull IIndexConstraint constraint, @NonNull IAssemblyNodeItem node,
218 @NonNull ISequence<? extends INodeItem> targets) {
219 String indexName = constraint.getName();
220 if (indexNameToIndexMap.containsKey(indexName)) {
221 getConstraintValidationHandler().handleIndexDuplicateViolation(constraint, node);
222 return;
223 }
224
225 IIndex index = IIndex.newInstance(constraint.getKeyFields());
226 targets.asStream()
227 .forEachOrdered(item -> {
228 assert item != null;
229 if (item.hasValue()) {
230 try {
231 INodeItem oldItem = index.put(item, metapathContext);
232 if (oldItem != null) {
233 getConstraintValidationHandler().handleIndexDuplicateKeyViolation(constraint, node, oldItem, item);
234 }
235 } catch (MetapathException ex) {
236 getConstraintValidationHandler().handleKeyMatchError(constraint, node, item, ex);
237 }
238 }
239 });
240 indexNameToIndexMap.put(indexName, index);
241 }
242
243 protected void validateUnique(@NonNull List<? extends IUniqueConstraint> constraints,
244 @NonNull List<? extends IAssemblyNodeItem> items) {
245
246 items.stream().forEachOrdered(item -> {
247 assert item != null;
248 validateUnique(constraints, item);
249 });
250 }
251
252 protected void validateUnique(@NonNull List<? extends IUniqueConstraint> constraints,
253 @NonNull IAssemblyNodeItem item) {
254 for (IUniqueConstraint constraint : constraints) {
255 ISequence<? extends IDefinitionNodeItem<?, ?>> targets = constraint.matchTargets(item, getMetapathContext());
256 try {
257 validateUnique(constraint, item, targets);
258 } catch (MetapathException ex) {
259 rethrowConstraintError(constraint, item, ex);
260 }
261 }
262 }
263
264 protected void validateUnique(@NonNull IUniqueConstraint constraint,
265 @NonNull IAssemblyNodeItem node, @NonNull ISequence<? extends INodeItem> targets) {
266 IIndex index = IIndex.newInstance(constraint.getKeyFields());
267 targets.asStream()
268 .forEachOrdered(item -> {
269 assert item != null;
270 if (item.hasValue()) {
271 try {
272 INodeItem oldItem = index.put(item, metapathContext);
273 if (oldItem != null) {
274 getConstraintValidationHandler().handleUniqueKeyViolation(constraint, node, oldItem, item);
275 }
276 } catch (MetapathException ex) {
277 getConstraintValidationHandler().handleKeyMatchError(constraint, node, item, ex);
278 throw ex;
279 }
280 }
281 });
282 }
283
284 protected void validateMatches(@NonNull List<? extends IMatchesConstraint> constraints,
285 @NonNull IDefinitionNodeItem<?, ?> item) {
286
287 for (IMatchesConstraint constraint : constraints) {
288 ISequence<? extends IDefinitionNodeItem<?, ?>> targets = constraint.matchTargets(item, getMetapathContext());
289 try {
290 validateMatches(constraint, item, targets);
291 } catch (MetapathException ex) {
292 rethrowConstraintError(constraint, item, ex);
293 }
294 }
295 }
296
297 protected void validateMatches(@NonNull IMatchesConstraint constraint, @NonNull INodeItem node,
298 ISequence<? extends INodeItem> targets) {
299 targets.asStream()
300 .forEachOrdered(item -> {
301 assert item != null;
302 if (item.hasValue()) {
303 String value = FnData.fnDataItem(item).asString();
304
305 Pattern pattern = constraint.getPattern();
306 if (pattern != null && !pattern.asMatchPredicate().test(value)) {
307
308 getConstraintValidationHandler().handleMatchPatternViolation(constraint, node, item, value);
309 }
310
311 IDataTypeAdapter<?> adapter = constraint.getDataType();
312 if (adapter != null) {
313 try {
314 adapter.parse(value);
315 } catch (IllegalArgumentException ex) {
316 getConstraintValidationHandler().handleMatchDatatypeViolation(constraint, node, item, value, ex);
317 }
318 }
319 }
320 });
321 }
322
323 protected void validateIndexHasKey(
324 @NonNull List<? extends IIndexHasKeyConstraint> constraints,
325 @NonNull IDefinitionNodeItem<?, ?> node) {
326
327 for (IIndexHasKeyConstraint constraint : constraints) {
328 ISequence<? extends IDefinitionNodeItem<?, ?>> targets = constraint.matchTargets(node, getMetapathContext());
329 validateIndexHasKey(constraint, node, targets);
330 }
331 }
332
333 protected void validateIndexHasKey(
334 @NonNull IIndexHasKeyConstraint constraint,
335 @NonNull IDefinitionNodeItem<?, ?> node,
336 @NonNull ISequence<? extends INodeItem> targets) {
337 String indexName = constraint.getIndexName();
338
339 List<KeyRef> keyRefItems = indexNameToKeyRefMap.get(indexName);
340 if (keyRefItems == null) {
341 keyRefItems = new LinkedList<>();
342 indexNameToKeyRefMap.put(indexName, keyRefItems);
343 }
344
345 KeyRef keyRef = new KeyRef(constraint, node, new ArrayList<>(targets.asList()));
346 keyRefItems.add(keyRef);
347 }
348
349 protected void validateExpect(@NonNull List<? extends IExpectConstraint> constraints,
350 @NonNull IDefinitionNodeItem<?, ?> item) {
351 for (IExpectConstraint constraint : constraints) {
352 ISequence<? extends IDefinitionNodeItem<?, ?>> targets = constraint.matchTargets(item, getMetapathContext());
353 validateExpect(constraint, item, targets);
354 }
355 }
356
357 protected void validateExpect(@NonNull IExpectConstraint constraint, @NonNull INodeItem node,
358 @NonNull ISequence<? extends INodeItem> targets) {
359 targets.asStream()
360 .map(item -> (INodeItem) item)
361 .forEachOrdered(item -> {
362 assert item != null;
363 if (item.hasValue()) {
364 MetapathExpression metapath = constraint.getTest();
365 try {
366 ISequence<?> result = metapath.evaluate(item, getMetapathContext());
367 if (!FnBoolean.fnBoolean(result).toBoolean()) {
368 getConstraintValidationHandler().handleExpectViolation(constraint, node, item, getMetapathContext());
369 }
370 } catch (MetapathException ex) {
371 rethrowConstraintError(constraint, item, ex);
372 }
373 }
374 });
375 }
376
377 protected void validateAllowedValues(@NonNull List<? extends IAllowedValuesConstraint> constraints,
378 @NonNull IDefinitionNodeItem<?, ?> item) {
379 for (IAllowedValuesConstraint constraint : constraints) {
380 ISequence<? extends IDefinitionNodeItem<?, ?>> targets = constraint.matchTargets(item, getMetapathContext());
381 validateAllowedValues(constraint, targets);
382 }
383 }
384
385 protected void validateAllowedValues(@NonNull IAllowedValuesConstraint constraint,
386 ISequence<? extends IDefinitionNodeItem<?, ?>> targets) {
387 targets.asStream().forEachOrdered(item -> {
388 assert item != null;
389 if (item.hasValue()) {
390 try {
391 updateValueStatus(item, constraint);
392 } catch (MetapathException ex) {
393 rethrowConstraintError(constraint, item, ex);
394 }
395 }
396 });
397 }
398
399 private static void rethrowConstraintError(@NonNull IConstraint constraint, INodeItem item,
400 MetapathException ex) {
401 StringBuilder builder = new StringBuilder(128);
402 builder.append("A ")
403 .append(constraint.getClass().getName())
404 .append(" constraint");
405
406 String id = constraint.getId();
407 if (id == null) {
408 builder.append(" targeting the metapath '")
409 .append(constraint.getTarget().getPath())
410 .append('\'');
411 } else {
412 builder.append(" with id '")
413 .append(id)
414 .append('\'');
415 }
416
417 builder.append(", matching the item at path '")
418 .append(item.getMetapath())
419 .append("', resulted in an unexpected error. The error was: ")
420 .append(ex.getLocalizedMessage());
421
422 throw new MetapathException(builder.toString(), ex);
423 }
424
425
426
427
428
429
430
431
432
433 protected void updateValueStatus(@NonNull INodeItem targetItem, @NonNull IAllowedValuesConstraint allowedValues) {
434
435
436 @Nullable ValueStatus valueStatus = valueMap.get(targetItem);
437 if (valueStatus == null) {
438 valueStatus = new ValueStatus(targetItem);
439 valueMap.put(targetItem, valueStatus);
440 }
441
442 valueStatus.registerAllowedValue(allowedValues);
443 }
444
445 protected void handleAllowedValues(@NonNull INodeItem targetItem) {
446 ValueStatus valueStatus = valueMap.remove(targetItem);
447 if (valueStatus != null) {
448 valueStatus.validate();
449 }
450 }
451
452 @Override
453 public void finalizeValidation() {
454
455 for (Map.Entry<String, List<KeyRef>> entry : indexNameToKeyRefMap.entrySet()) {
456 String indexName = entry.getKey();
457 IIndex index = indexNameToIndexMap.get(indexName);
458
459 List<KeyRef> keyRefs = entry.getValue();
460
461 for (KeyRef keyRef : keyRefs) {
462 IIndexHasKeyConstraint constraint = keyRef.getConstraint();
463 for (INodeItem item : keyRef.getTargets()) {
464 assert item != null;
465
466 try {
467 List<String> key = IIndex.toKey(item, constraint.getKeyFields(), getMetapathContext());
468
469 INodeItem referencedItem = index.get(key);
470
471 if (referencedItem == null) {
472 getConstraintValidationHandler().handleIndexMiss(constraint, keyRef.getNode(), item, key);
473 }
474 } catch (MetapathException ex) {
475 getConstraintValidationHandler().handleKeyMatchError(constraint, keyRef.getNode(), item, ex);
476 }
477 }
478 }
479 }
480 }
481
482 private class ValueStatus {
483 @NonNull
484 private final List<IAllowedValuesConstraint> constraints = new LinkedList<>();
485 @NonNull
486 private final String value;
487 @NonNull
488 private final INodeItem item;
489 private boolean allowOthers = true;
490 @NonNull
491 private IAllowedValuesConstraint.Extensible extensible = IAllowedValuesConstraint.Extensible.EXTERNAL;
492
493 public ValueStatus(@NonNull INodeItem item) {
494 this.item = item;
495 this.value = FnData.fnDataItem(item).asString();
496 }
497
498 public void registerAllowedValue(@NonNull IAllowedValuesConstraint allowedValues) {
499 this.constraints.add(allowedValues);
500 if (!allowedValues.isAllowedOther()) {
501
502 allowOthers = false;
503 }
504
505 IAllowedValuesConstraint.Extensible newExtensible = allowedValues.getExtensible();
506 if (newExtensible.ordinal() > extensible.ordinal()) {
507
508 extensible = allowedValues.getExtensible();
509 } else if (IAllowedValuesConstraint.Extensible.NONE.equals(newExtensible)
510 && IAllowedValuesConstraint.Extensible.NONE.equals(extensible)) {
511
512 throw new MetapathException(
513 String.format("Multiple constraints have extensibility scope=none at path '%s'", item.getMetapath()));
514 } else if (allowedValues.getExtensible().ordinal() < extensible.ordinal()) {
515 String msg = String.format(
516 "An allowed values constraint with an extensibility scope '%s'"
517 + " exceeds the allowed scope '%s' at path '%s'",
518 allowedValues.getExtensible().name(), extensible.name(), item.getMetapath());
519 LOGGER.atError().log(msg);
520 throw new MetapathException(msg);
521 }
522 }
523
524 public void validate() {
525 if (!constraints.isEmpty()) {
526 boolean match = false;
527 List<IAllowedValuesConstraint> failedConstraints = new LinkedList<>();
528 for (IAllowedValuesConstraint allowedValues : constraints) {
529 IAllowedValue matchingValue = allowedValues.getAllowedValue(value);
530 if (matchingValue != null) {
531 match = true;
532 } else if (IAllowedValuesConstraint.Extensible.NONE.equals(allowedValues.getExtensible())) {
533
534 failedConstraints = CollectionUtil.singletonList(allowedValues);
535 match = false;
536 break;
537 } else {
538 failedConstraints.add(allowedValues);
539 }
540 }
541
542
543 if (!match && !allowOthers) {
544 getConstraintValidationHandler().handleAllowedValuesViolation(failedConstraints, item);
545 }
546 }
547 }
548 }
549
550 class Visitor
551 extends AbstractNodeItemVisitor<Void, Void> {
552 @Override
553 public Void visitDocument(@NonNull IDocumentNodeItem item, Void context) {
554 return super.visitDocument(item, context);
555 }
556
557 @Override
558 public Void visitFlag(@NonNull IFlagNodeItem item, Void context) {
559 validateFlag(item);
560 super.visitFlag(item, context);
561 handleAllowedValues(item);
562 return null;
563 }
564
565 @Override
566 public Void visitField(@NonNull IFieldNodeItem item, Void context) {
567 validateField(item);
568 super.visitField(item, context);
569 handleAllowedValues(item);
570 return null;
571 }
572
573 @Override
574 public Void visitAssembly(@NonNull IAssemblyNodeItem item, Void context) {
575 validateAssembly(item);
576 super.visitAssembly(item, context);
577 return null;
578 }
579
580 @Override
581 public Void visitMetaschema(@NonNull IModuleNodeItem item, Void context) {
582 throw new UnsupportedOperationException("not needed");
583 }
584
585 @Override
586 protected Void defaultResult() {
587
588 return null;
589 }
590 }
591
592 private static class KeyRef {
593 @NonNull
594 private final IIndexHasKeyConstraint constraint;
595 @NonNull
596 private final INodeItem node;
597 @NonNull
598 private final List<INodeItem> targets;
599
600 public KeyRef(
601 @NonNull IIndexHasKeyConstraint constraint,
602 @NonNull INodeItem node,
603 @NonNull List<INodeItem> targets) {
604 this.node = node;
605 this.constraint = constraint;
606 this.targets = targets;
607 }
608
609 @NonNull
610 public IIndexHasKeyConstraint getConstraint() {
611 return constraint;
612 }
613
614 @NonNull
615 protected INodeItem getNode() {
616 return node;
617 }
618
619 @NonNull
620 public List<INodeItem> getTargets() {
621 return targets;
622 }
623 }
624 }