1
2
3
4
5
6 package dev.metaschema.core.model.constraint;
7
8 import org.apache.commons.lang3.tuple.Pair;
9 import org.apache.logging.log4j.LogManager;
10 import org.apache.logging.log4j.Logger;
11 import org.eclipse.jdt.annotation.Owning;
12
13 import java.util.ArrayList;
14 import java.util.Collection;
15 import java.util.Collections;
16 import java.util.List;
17 import java.util.Map;
18 import java.util.concurrent.ConcurrentHashMap;
19 import java.util.concurrent.ExecutionException;
20 import java.util.concurrent.ExecutorService;
21 import java.util.concurrent.Future;
22 import java.util.regex.Pattern;
23 import java.util.stream.Collectors;
24 import java.util.stream.Stream;
25
26 import dev.metaschema.core.configuration.DefaultConfiguration;
27 import dev.metaschema.core.configuration.IConfiguration;
28 import dev.metaschema.core.configuration.IMutableConfiguration;
29 import dev.metaschema.core.datatype.IDataTypeAdapter;
30 import dev.metaschema.core.metapath.DynamicContext;
31 import dev.metaschema.core.metapath.IMetapathExpression;
32 import dev.metaschema.core.metapath.MetapathException;
33 import dev.metaschema.core.metapath.function.library.FnBoolean;
34 import dev.metaschema.core.metapath.item.ISequence;
35 import dev.metaschema.core.metapath.item.node.AbstractNodeItemVisitor;
36 import dev.metaschema.core.metapath.item.node.IAssemblyNodeItem;
37 import dev.metaschema.core.metapath.item.node.IDefinitionNodeItem;
38 import dev.metaschema.core.metapath.item.node.IFieldNodeItem;
39 import dev.metaschema.core.metapath.item.node.IFlagNodeItem;
40 import dev.metaschema.core.metapath.item.node.IModelNodeItem;
41 import dev.metaschema.core.metapath.item.node.IModuleNodeItem;
42 import dev.metaschema.core.metapath.item.node.INodeItem;
43 import dev.metaschema.core.model.IAssemblyDefinition;
44 import dev.metaschema.core.model.IFieldDefinition;
45 import dev.metaschema.core.model.IFlagDefinition;
46 import dev.metaschema.core.qname.IEnhancedQName;
47 import dev.metaschema.core.util.CollectionUtil;
48 import dev.metaschema.core.util.ExceptionUtils;
49 import dev.metaschema.core.util.ExceptionUtils.WrappedException;
50 import dev.metaschema.core.util.ObjectUtils;
51 import edu.umd.cs.findbugs.annotations.NonNull;
52 import edu.umd.cs.findbugs.annotations.Nullable;
53
54
55
56
57
58
59
60 @SuppressWarnings({
61 "PMD.CouplingBetweenObjects",
62 "PMD.GodClass"
63 })
64 public class DefaultConstraintValidator
65 implements IConstraintValidator, IMutableConfiguration<ValidationFeature<?>> {
66 private static final Logger LOGGER = LogManager.getLogger(DefaultConstraintValidator.class);
67
68 @NonNull
69 private final Map<INodeItem, ValueStatus> valueMap = new ConcurrentHashMap<>();
70 @NonNull
71 private final Map<String, IIndex> indexNameToIndexMap = new ConcurrentHashMap<>();
72 @NonNull
73 private final Map<String, List<KeyRef>> indexNameToKeyRefMap = new ConcurrentHashMap<>();
74 @NonNull
75 private final IConstraintValidationHandler handler;
76 @NonNull
77 private final IMutableConfiguration<ValidationFeature<?>> configuration;
78 @NonNull
79 @Owning
80 private final ValidationConfig validationConfig;
81
82
83
84
85
86
87
88 public DefaultConstraintValidator(
89 @NonNull IConstraintValidationHandler handler) {
90 this(handler, ValidationConfig.SEQUENTIAL);
91 }
92
93
94
95
96
97
98
99
100
101
102
103 public DefaultConstraintValidator(
104 @NonNull IConstraintValidationHandler handler,
105 @NonNull ValidationConfig validationConfig) {
106 this.handler = handler;
107 this.configuration = new DefaultConfiguration<>();
108 this.validationConfig = validationConfig;
109 }
110
111
112
113
114
115
116 @NonNull
117 protected IMutableConfiguration<ValidationFeature<?>> getConfiguration() {
118 return configuration;
119 }
120
121 @Override
122 @Owning
123 public DefaultConstraintValidator enableFeature(ValidationFeature<?> feature) {
124 return set(feature, true);
125 }
126
127 @Override
128 @Owning
129 public DefaultConstraintValidator disableFeature(ValidationFeature<?> feature) {
130 return set(feature, false);
131 }
132
133 @Override
134 @Owning
135 public DefaultConstraintValidator applyConfiguration(
136 @NonNull IConfiguration<ValidationFeature<?>> other) {
137 getConfiguration().applyConfiguration(other);
138 return this;
139 }
140
141 @Override
142 @Owning
143 public DefaultConstraintValidator set(ValidationFeature<?> feature, Object value) {
144 getConfiguration().set(feature, value);
145 return this;
146 }
147
148 @Override
149 public boolean isFeatureEnabled(ValidationFeature<?> feature) {
150 return getConfiguration().isFeatureEnabled(feature);
151 }
152
153 @Override
154 public Map<ValidationFeature<?>, Object> getFeatureValues() {
155 return getConfiguration().getFeatureValues();
156 }
157
158
159
160
161
162
163 @NonNull
164 protected IConstraintValidationHandler getConstraintValidationHandler() {
165 return handler;
166 }
167
168
169
170
171
172
173 @NonNull
174 private ValidationEventListener getListener() {
175 return validationConfig.getListener();
176 }
177
178 @Override
179 public void close() {
180 validationConfig.close();
181 }
182
183 @Override
184 public void validate(
185 @NonNull INodeItem item,
186 @NonNull DynamicContext dynamicContext) throws ConstraintValidationException {
187 try {
188 item.accept(new Visitor(), dynamicContext);
189 } catch (WrappedException ex) {
190 ex.unwrapAndThrow(ConstraintValidationException.class);
191 }
192 }
193
194
195
196
197
198
199
200
201
202
203
204
205
206 protected void validateFlag(
207 @NonNull IFlagNodeItem item,
208 @NonNull DynamicContext dynamicContext) {
209 IFlagDefinition definition = item.getDefinition();
210
211 try {
212 validateExpect(definition.getExpectConstraints(), item, dynamicContext);
213 validateReport(definition.getReportConstraints(), item, dynamicContext);
214 validateAllowedValues(definition.getAllowedValuesConstraints(), item, dynamicContext);
215 validateIndexHasKey(definition.getIndexHasKeyConstraints(), item, dynamicContext);
216 validateMatches(definition.getMatchesConstraints(), item, dynamicContext);
217 } catch (ConstraintValidationException ex) {
218 throw ExceptionUtils.wrap(ex);
219 }
220 }
221
222
223
224
225
226
227
228
229
230
231
232
233
234 protected void validateField(
235 @NonNull IFieldNodeItem item,
236 @NonNull DynamicContext dynamicContext) {
237 IFieldDefinition definition = item.getDefinition();
238
239 try {
240 validateExpect(definition.getExpectConstraints(), item, dynamicContext);
241 validateReport(definition.getReportConstraints(), item, dynamicContext);
242 validateAllowedValues(definition.getAllowedValuesConstraints(), item, dynamicContext);
243 validateIndexHasKey(definition.getIndexHasKeyConstraints(), item, dynamicContext);
244 validateMatches(definition.getMatchesConstraints(), item, dynamicContext);
245 } catch (ConstraintValidationException ex) {
246 throw ExceptionUtils.wrap(ex);
247 }
248 }
249
250
251
252
253
254
255
256
257
258
259
260
261 protected void validateAssembly(
262 @NonNull IAssemblyNodeItem item,
263 @NonNull DynamicContext dynamicContext) throws ConstraintValidationException {
264 IAssemblyDefinition definition = item.getDefinition();
265
266 try {
267 validateExpect(definition.getExpectConstraints(), item, dynamicContext);
268 validateReport(definition.getReportConstraints(), item, dynamicContext);
269 validateAllowedValues(definition.getAllowedValuesConstraints(), item, dynamicContext);
270 validateIndexHasKey(definition.getIndexHasKeyConstraints(), item, dynamicContext);
271 validateMatches(definition.getMatchesConstraints(), item, dynamicContext);
272 validateHasCardinality(definition.getHasCardinalityConstraints(), item, dynamicContext);
273 validateIndex(definition.getIndexConstraints(), item, dynamicContext);
274 validateUnique(definition.getUniqueConstraints(), item, dynamicContext);
275 } catch (ConstraintValidationException ex) {
276 throw ExceptionUtils.wrap(ex);
277 }
278 }
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294 private void validateHasCardinality(
295 @NonNull List<? extends ICardinalityConstraint> constraints,
296 @NonNull IAssemblyNodeItem item,
297 @NonNull DynamicContext dynamicContext) throws ConstraintValidationException {
298 ValidationEventListener listener = getListener();
299 for (ICardinalityConstraint constraint : constraints) {
300 assert constraint != null;
301 listener.beforeConstraintEvaluation(constraint, item);
302 try {
303 ISequence<? extends IDefinitionNodeItem<?, ?>> targets = constraint.matchTargets(item, dynamicContext);
304 validateHasCardinality(constraint, item, targets, dynamicContext);
305 } catch (MetapathException ex) {
306 handleError(constraint, item, ex, dynamicContext);
307 } finally {
308 listener.afterConstraintEvaluation(constraint, item);
309 }
310 }
311 }
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328 private void validateHasCardinality(
329 @NonNull ICardinalityConstraint constraint,
330 @NonNull IAssemblyNodeItem node,
331 @NonNull ISequence<? extends INodeItem> targets,
332 @NonNull DynamicContext dynamicContext) throws ConstraintValidationException {
333 int itemCount = targets.size();
334
335 IConstraintValidationHandler handler = getConstraintValidationHandler();
336
337 boolean violation = false;
338 Integer minOccurs = constraint.getMinOccurs();
339 if (minOccurs != null && itemCount < minOccurs) {
340 handler.handleCardinalityMinimumViolation(constraint, node, targets, dynamicContext);
341 violation = true;
342 }
343
344 Integer maxOccurs = constraint.getMaxOccurs();
345 if (maxOccurs != null && itemCount > maxOccurs) {
346 handler.handleCardinalityMaximumViolation(constraint, node, targets, dynamicContext);
347 violation = true;
348 }
349
350 if (!violation) {
351 handlePass(constraint, node, node, dynamicContext);
352 }
353 }
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369 private void validateIndex(
370 @NonNull List<? extends IIndexConstraint> constraints,
371 @NonNull IAssemblyNodeItem item,
372 @NonNull DynamicContext dynamicContext) throws ConstraintValidationException {
373 ValidationEventListener listener = getListener();
374 for (IIndexConstraint constraint : constraints) {
375 assert constraint != null;
376 listener.beforeConstraintEvaluation(constraint, item);
377 try {
378 ISequence<? extends IDefinitionNodeItem<?, ?>> targets = constraint.matchTargets(item, dynamicContext);
379 validateIndex(constraint, item, targets, dynamicContext);
380 } catch (MetapathException ex) {
381 handleError(constraint, item, ex, dynamicContext);
382 } finally {
383 listener.afterConstraintEvaluation(constraint, item);
384 }
385 }
386 }
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406 private void validateIndex(
407 @NonNull IIndexConstraint constraint,
408 @NonNull IAssemblyNodeItem node,
409 @NonNull ISequence<? extends INodeItem> targets,
410 @NonNull DynamicContext dynamicContext) throws ConstraintValidationException {
411 IConstraintValidationHandler handler = getConstraintValidationHandler();
412 if (indexNameToIndexMap.containsKey(constraint.getName())) {
413 handler.handleIndexDuplicateViolation(constraint, node, dynamicContext);
414 } else {
415 validateIndexEntries(constraint, node, targets, dynamicContext);
416 }
417 }
418
419 private void validateIndexEntries(
420 @NonNull IIndexConstraint constraint,
421 @NonNull IAssemblyNodeItem node,
422 @NonNull ISequence<? extends INodeItem> targets,
423 @NonNull DynamicContext dynamicContext) throws ConstraintValidationException {
424 IIndex index = IIndex.newInstance(constraint.getKeyFields());
425 for (INodeItem item : targets) {
426 assert item != null;
427 if (item.hasValue()) {
428 INodeItem oldItem = null;
429 try {
430 oldItem = index.put(item, IIndex.toKey(item, index.getKeyFields(), dynamicContext));
431 } catch (IllegalArgumentException ex) {
432
433 handleError(constraint, item, ex, dynamicContext);
434 }
435 try {
436 if (oldItem == null) {
437 handlePass(constraint, node, item, dynamicContext);
438 } else {
439 handler.handleIndexDuplicateKeyViolation(constraint, node, oldItem, item, dynamicContext);
440 }
441 } catch (MetapathException ex) {
442 handler.handleKeyMatchError(constraint, node, item, ex, dynamicContext);
443 }
444 }
445 }
446 indexNameToIndexMap.put(constraint.getName(), index);
447 }
448
449 private void handlePass(
450 @NonNull IConstraint constraint,
451 @NonNull INodeItem node,
452 @NonNull INodeItem item,
453 @NonNull DynamicContext dynamicContext) {
454 if (isFeatureEnabled(ValidationFeature.VALIDATE_GENERATE_PASS_FINDINGS)) {
455 getConstraintValidationHandler().handlePass(constraint, node, item, dynamicContext);
456 }
457 }
458
459 private void handleError(
460 @NonNull IConstraint constraint,
461 @NonNull INodeItem node,
462 @NonNull Throwable ex,
463 @NonNull DynamicContext dynamicContext) throws ConstraintValidationException {
464 if (isFeatureEnabled(ValidationFeature.THROW_EXCEPTION_ON_ERROR)) {
465 if (ex instanceof ConstraintValidationException) {
466 throw (ConstraintValidationException) ex;
467 }
468 throw new ConstraintValidationException(ex);
469 }
470 getConstraintValidationHandler()
471 .handleError(constraint, node, toErrorMessage(constraint, node, ex), ex, dynamicContext);
472 }
473
474 @NonNull
475 private static String toErrorMessage(
476 @NonNull IConstraint constraint,
477 @NonNull INodeItem item,
478 @NonNull Throwable ex) {
479 StringBuilder builder = new StringBuilder(128);
480 builder.append("A ")
481 .append(constraint.getType().getName())
482 .append(" constraint");
483
484 String id = constraint.getId();
485 if (id == null) {
486 builder.append(" targeting the metapath '")
487 .append(constraint.getTarget().getPath())
488 .append('\'');
489 } else {
490 builder.append(" with id '")
491 .append(id)
492 .append('\'');
493 }
494
495 builder.append(", matching the item at path '")
496 .append(item.getMetapath())
497 .append("', resulted in an unexpected error. ")
498 .append(ex.getLocalizedMessage());
499 return ObjectUtils.notNull(builder.toString());
500 }
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516 private void validateUnique(
517 @NonNull List<? extends IUniqueConstraint> constraints,
518 @NonNull IAssemblyNodeItem item,
519 @NonNull DynamicContext dynamicContext) throws ConstraintValidationException {
520 ValidationEventListener listener = getListener();
521 for (IUniqueConstraint constraint : constraints) {
522 assert constraint != null;
523 listener.beforeConstraintEvaluation(constraint, item);
524 try {
525 ISequence<? extends IDefinitionNodeItem<?, ?>> targets = constraint.matchTargets(item, dynamicContext);
526 validateUnique(constraint, item, targets, dynamicContext);
527 } catch (MetapathException ex) {
528 handleError(constraint, item, ex, dynamicContext);
529 } finally {
530 listener.afterConstraintEvaluation(constraint, item);
531 }
532 }
533 }
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553 private void validateUnique(
554 @NonNull IUniqueConstraint constraint,
555 @NonNull IAssemblyNodeItem node,
556 @NonNull ISequence<? extends INodeItem> targets,
557 @NonNull DynamicContext dynamicContext) throws ConstraintValidationException {
558
559 IConstraintValidationHandler handler = getConstraintValidationHandler();
560 IIndex index = IIndex.newInstance(constraint.getKeyFields());
561
562 for (INodeItem item : targets) {
563 assert item != null;
564 if (item.hasValue()) {
565 INodeItem oldItem = null;
566 try {
567 oldItem = index.put(item, IIndex.toKey(item, index.getKeyFields(), dynamicContext));
568 } catch (IllegalArgumentException ex) {
569
570 handleError(constraint, item, ex, dynamicContext);
571 }
572
573 try {
574 if (oldItem == null) {
575 handlePass(constraint, node, item, dynamicContext);
576 } else {
577 handler.handleUniqueKeyViolation(constraint, node, oldItem, item, dynamicContext);
578 }
579 } catch (MetapathException ex) {
580 handleError(constraint, item, ex, dynamicContext);
581 }
582 }
583 }
584 }
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600 private void validateMatches(
601 @NonNull List<? extends IMatchesConstraint> constraints,
602 @NonNull IDefinitionNodeItem<?, ?> item,
603 @NonNull DynamicContext dynamicContext) throws ConstraintValidationException {
604
605 ValidationEventListener listener = getListener();
606 for (IMatchesConstraint constraint : constraints) {
607 assert constraint != null;
608 listener.beforeConstraintEvaluation(constraint, item);
609 try {
610 ISequence<? extends IDefinitionNodeItem<?, ?>> targets = constraint.matchTargets(item, dynamicContext);
611 validateMatches(constraint, item, targets, dynamicContext);
612 } catch (MetapathException ex) {
613 handleError(constraint, item, ex, dynamicContext);
614 } finally {
615 listener.afterConstraintEvaluation(constraint, item);
616 }
617 }
618 }
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635 private void validateMatches(
636 @NonNull IMatchesConstraint constraint,
637 @NonNull INodeItem node,
638 @NonNull ISequence<? extends INodeItem> targets,
639 @NonNull DynamicContext dynamicContext) throws ConstraintValidationException {
640 for (INodeItem item : targets) {
641 assert item != null;
642 if (item.hasValue()) {
643 validateMatchesItem(constraint, node, item, dynamicContext);
644 }
645 }
646 }
647
648 private void validateMatchesItem(
649 @NonNull IMatchesConstraint constraint,
650 @NonNull INodeItem node,
651 @NonNull INodeItem item,
652 @NonNull DynamicContext dynamicContext) throws ConstraintValidationException {
653 String value = item.toAtomicItem().asString();
654
655 IConstraintValidationHandler handler = getConstraintValidationHandler();
656 boolean valid = true;
657 Pattern pattern = constraint.getPattern();
658 if (pattern != null && !pattern.asMatchPredicate().test(value)) {
659
660 handler.handleMatchPatternViolation(constraint, node, item, value, pattern, dynamicContext);
661 valid = false;
662 }
663
664 IDataTypeAdapter<?> adapter = constraint.getDataType();
665 if (adapter != null) {
666 try {
667 adapter.parse(value);
668 } catch (IllegalArgumentException ex) {
669 handler.handleMatchDatatypeViolation(constraint, node, item, value, adapter, ex, dynamicContext);
670 valid = false;
671 }
672 }
673
674 if (valid) {
675 handlePass(constraint, node, item, dynamicContext);
676 }
677 }
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693 private void validateIndexHasKey(
694 @NonNull List<? extends IIndexHasKeyConstraint> constraints,
695 @NonNull IDefinitionNodeItem<?, ?> item,
696 @NonNull DynamicContext dynamicContext) throws ConstraintValidationException {
697
698 ValidationEventListener listener = getListener();
699 for (IIndexHasKeyConstraint constraint : constraints) {
700 assert constraint != null;
701 listener.beforeConstraintEvaluation(constraint, item);
702 try {
703 ISequence<? extends IDefinitionNodeItem<?, ?>> targets = constraint.matchTargets(item, dynamicContext);
704 validateIndexHasKey(constraint, item, targets);
705 } catch (MetapathException ex) {
706 handleError(constraint, item, ex, dynamicContext);
707 } finally {
708 listener.afterConstraintEvaluation(constraint, item);
709 }
710 }
711 }
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726 private void validateIndexHasKey(
727 @NonNull IIndexHasKeyConstraint constraint,
728 @NonNull IDefinitionNodeItem<?, ?> node,
729 @NonNull ISequence<? extends INodeItem> targets) {
730 String indexName = constraint.getIndexName();
731
732
733
734 List<KeyRef> keyRefItems = indexNameToKeyRefMap.computeIfAbsent(
735 indexName,
736 k -> Collections.synchronizedList(new ArrayList<>()));
737
738 keyRefItems.add(new KeyRef(constraint, node, new ArrayList<>(targets)));
739 }
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755 private void validateExpect(
756 @NonNull List<? extends IExpectConstraint> constraints,
757 @NonNull IDefinitionNodeItem<?, ?> item,
758 @NonNull DynamicContext dynamicContext) throws ConstraintValidationException {
759 ValidationEventListener listener = getListener();
760 for (IExpectConstraint constraint : constraints) {
761 assert constraint != null;
762 listener.beforeConstraintEvaluation(constraint, item);
763 try {
764 ISequence<? extends IDefinitionNodeItem<?, ?>> targets = constraint.matchTargets(item, dynamicContext);
765 validateExpect(constraint, item, targets, dynamicContext);
766 } catch (MetapathException ex) {
767 handleError(constraint, item, ex, dynamicContext);
768 } finally {
769 listener.afterConstraintEvaluation(constraint, item);
770 }
771 }
772 }
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792 private void validateExpect(
793 @NonNull IExpectConstraint constraint,
794 @NonNull INodeItem node,
795 @NonNull ISequence<? extends INodeItem> targets,
796 @NonNull DynamicContext dynamicContext) throws ConstraintValidationException {
797 IMetapathExpression test = constraint.getTest();
798 IConstraintValidationHandler handler = getConstraintValidationHandler();
799 for (INodeItem item : targets) {
800 assert item != null;
801
802 if (item.hasValue()) {
803 try {
804 ISequence<?> result = test.evaluate(item, dynamicContext);
805 if (FnBoolean.fnBoolean(result).toBoolean()) {
806 handlePass(constraint, node, item, dynamicContext);
807 } else {
808 handler.handleExpectViolation(constraint, node, item, dynamicContext);
809 }
810 } catch (MetapathException ex) {
811 handleError(constraint, item, ex, dynamicContext);
812 }
813 }
814 }
815 }
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834 private void validateReport(
835 @NonNull List<? extends IReportConstraint> constraints,
836 @NonNull IDefinitionNodeItem<?, ?> item,
837 @NonNull DynamicContext dynamicContext) throws ConstraintValidationException {
838 ValidationEventListener listener = getListener();
839 for (IReportConstraint constraint : constraints) {
840 assert constraint != null;
841 listener.beforeConstraintEvaluation(constraint, item);
842 try {
843 ISequence<? extends IDefinitionNodeItem<?, ?>> targets = constraint.matchTargets(item, dynamicContext);
844 validateReport(constraint, item, targets, dynamicContext);
845 } catch (MetapathException ex) {
846 handleError(constraint, item, ex, dynamicContext);
847 } finally {
848 listener.afterConstraintEvaluation(constraint, item);
849 }
850 }
851 }
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874 private void validateReport(
875 @NonNull IReportConstraint constraint,
876 @NonNull INodeItem node,
877 @NonNull ISequence<? extends INodeItem> targets,
878 @NonNull DynamicContext dynamicContext) throws ConstraintValidationException {
879 IMetapathExpression test = constraint.getTest();
880 IConstraintValidationHandler handler = getConstraintValidationHandler();
881 for (INodeItem item : targets) {
882 assert item != null;
883
884 if (item.hasValue()) {
885 try {
886 ISequence<?> result = test.evaluate(item, dynamicContext);
887
888 if (FnBoolean.fnBoolean(result).toBoolean()) {
889 handler.handleReportViolation(constraint, node, item, dynamicContext);
890 } else {
891 handlePass(constraint, node, item, dynamicContext);
892 }
893 } catch (MetapathException ex) {
894 handleError(constraint, item, ex, dynamicContext);
895 }
896 }
897 }
898 }
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914 private void validateAllowedValues(
915 @NonNull List<? extends IAllowedValuesConstraint> constraints,
916 @NonNull IDefinitionNodeItem<?, ?> item,
917 @NonNull DynamicContext dynamicContext) throws ConstraintValidationException {
918 ValidationEventListener listener = getListener();
919 for (IAllowedValuesConstraint constraint : constraints) {
920 assert constraint != null;
921 listener.beforeConstraintEvaluation(constraint, item);
922 try {
923 ISequence<? extends IDefinitionNodeItem<?, ?>> targets = constraint.matchTargets(item, dynamicContext);
924 validateAllowedValues(constraint, item, targets, dynamicContext);
925 } catch (MetapathException ex) {
926 handleError(constraint, item, ex, dynamicContext);
927 } finally {
928 listener.afterConstraintEvaluation(constraint, item);
929 }
930 }
931 }
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951 private void validateAllowedValues(
952 @NonNull IAllowedValuesConstraint constraint,
953 @NonNull IDefinitionNodeItem<?, ?> node,
954 @NonNull ISequence<? extends IDefinitionNodeItem<?, ?>> targets,
955 @NonNull DynamicContext dynamicContext) throws ConstraintValidationException {
956 for (INodeItem item : targets) {
957 assert item != null;
958 if (item.hasValue()) {
959 try {
960 updateValueStatus(item, constraint, node);
961 } catch (ConstraintValidationException ex) {
962 handleError(constraint, item, ex, dynamicContext);
963 }
964 }
965 }
966 }
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982 protected void updateValueStatus(
983 @NonNull INodeItem targetItem,
984 @NonNull IAllowedValuesConstraint allowedValues,
985 @NonNull IDefinitionNodeItem<?, ?> node) throws ConstraintValidationException {
986
987 ValueStatus valueStatus = valueMap.computeIfAbsent(targetItem, ValueStatus::new);
988
989 valueStatus.registerAllowedValues(allowedValues, node);
990 }
991
992
993
994
995
996
997
998
999
1000
1001
1002 protected void handleAllowedValues(
1003 @NonNull INodeItem targetItem,
1004 @NonNull DynamicContext dynamicContext) {
1005 ValueStatus valueStatus = valueMap.remove(targetItem);
1006 if (valueStatus != null) {
1007 valueStatus.validate(dynamicContext);
1008 }
1009 }
1010
1011 @Override
1012 public void finalizeValidation(DynamicContext dynamicContext) throws ConstraintValidationException {
1013
1014 for (Map.Entry<String, List<KeyRef>> entry : indexNameToKeyRefMap.entrySet()) {
1015 String indexName = ObjectUtils.notNull(entry.getKey());
1016 IIndex index = indexNameToIndexMap.get(indexName);
1017
1018 List<KeyRef> keyRefs = entry.getValue();
1019
1020 for (KeyRef keyRef : keyRefs) {
1021 IIndexHasKeyConstraint constraint = keyRef.getConstraint();
1022
1023 INodeItem node = keyRef.getNode();
1024 List<INodeItem> targets = keyRef.getTargets();
1025 for (INodeItem item : targets) {
1026 assert item != null;
1027 try {
1028 validateKeyRef(constraint, node, item, indexName, index, dynamicContext);
1029 } catch (MetapathException ex) {
1030 handleError(constraint, item, ex, dynamicContext);
1031 }
1032 }
1033 }
1034 }
1035 }
1036
1037 private void validateKeyRef(
1038 @NonNull IIndexHasKeyConstraint constraint,
1039 @NonNull INodeItem contextNode,
1040 @NonNull INodeItem item,
1041 @NonNull String indexName,
1042 @Nullable IIndex index,
1043 @NonNull DynamicContext dynamicContext) throws ConstraintValidationException {
1044 IConstraintValidationHandler handler = getConstraintValidationHandler();
1045 try {
1046 List<String> key;
1047 try {
1048 key = IIndex.toKey(item, constraint.getKeyFields(), dynamicContext);
1049 } catch (IllegalArgumentException ex) {
1050 handler.handleError(constraint, item, toErrorMessage(constraint, item, ex), ex, dynamicContext);
1051 throw ex;
1052 }
1053
1054 if (index == null) {
1055 handler.handleMissingIndexViolation(
1056 constraint,
1057 contextNode,
1058 item,
1059 ObjectUtils.notNull(String.format("Key reference to undefined index with name '%s'",
1060 indexName)),
1061 dynamicContext);
1062 } else {
1063 INodeItem referencedItem = index.get(key);
1064
1065 if (referencedItem == null) {
1066 handler.handleIndexMiss(constraint, contextNode, item, key, dynamicContext);
1067 } else {
1068 handlePass(constraint, contextNode, item, dynamicContext);
1069 }
1070 }
1071 } catch (MetapathException ex) {
1072 handler.handleKeyMatchError(constraint, contextNode, item, ex, dynamicContext);
1073 }
1074 }
1075
1076 @SuppressWarnings("PMD.AvoidUsingVolatile")
1077 private class ValueStatus {
1078 @NonNull
1079 private final List<Pair<IAllowedValuesConstraint, IDefinitionNodeItem<?, ?>>> constraints
1080 = Collections.synchronizedList(new ArrayList<>());
1081 @NonNull
1082 private final String value;
1083 @NonNull
1084 private final INodeItem item;
1085 private volatile boolean allowOthers = true;
1086 @NonNull
1087 private volatile IAllowedValuesConstraint.Extensible extensible = IAllowedValuesConstraint.Extensible.EXTERNAL;
1088
1089 public ValueStatus(@NonNull INodeItem item) {
1090 this.item = item;
1091 this.value = item.toAtomicItem().asString();
1092 }
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107 public synchronized void registerAllowedValues(
1108 @NonNull IAllowedValuesConstraint allowedValues,
1109 @NonNull IDefinitionNodeItem<?, ?> node) throws ConstraintValidationException {
1110 IAllowedValuesConstraint.Extensible newExtensible = allowedValues.getExtensible();
1111 if (newExtensible.ordinal() > extensible.ordinal()) {
1112
1113 extensible = allowedValues.getExtensible();
1114 } else if (newExtensible == IAllowedValuesConstraint.Extensible.NONE
1115 && extensible == IAllowedValuesConstraint.Extensible.NONE) {
1116
1117
1118 throw new ConstraintValidationException(
1119 ObjectUtils.notNull(String.format(
1120 "Multiple constraints matching path '%s' have scope='none', which prevents extension. Involved" +
1121 " constraints are: %s",
1122 Stream.concat(
1123 Stream.of(allowedValues),
1124 constraints.stream()
1125 .map(Pair::getLeft)
1126 .filter(
1127 constraint -> constraint.getExtensible() == IAllowedValuesConstraint.Extensible.NONE))
1128 .map(IConstraint::getConstraintIdentity)
1129 .collect(Collectors.joining(", ", "{", "}")),
1130 item.getMetapath())));
1131 } else if (allowedValues.getExtensible().ordinal() < extensible.ordinal()) {
1132 String msg = ObjectUtils.notNull(String.format(
1133 "An allowed values constraint with an extensibility scope '%s'"
1134 + " exceeds the allowed scope '%s' at path '%s'",
1135 allowedValues.getExtensible().name(), extensible.name(), item.getMetapath()));
1136 LOGGER.atError().log(msg);
1137 throw new ConstraintValidationException(msg);
1138 }
1139 this.constraints.add(Pair.of(allowedValues, node));
1140 if (!allowedValues.isAllowedOther()) {
1141
1142 allowOthers = false;
1143 }
1144 }
1145
1146 public void validate(@NonNull DynamicContext dynamicContext) {
1147
1148 final boolean localAllowOthers;
1149 final List<Pair<IAllowedValuesConstraint, IDefinitionNodeItem<?, ?>>> localConstraints;
1150 synchronized (this) {
1151 localAllowOthers = this.allowOthers;
1152 localConstraints = new ArrayList<>(this.constraints);
1153 }
1154
1155 if (!localConstraints.isEmpty()) {
1156 boolean match = false;
1157 List<IAllowedValuesConstraint> failedConstraints = new ArrayList<>();
1158 IConstraintValidationHandler handler = getConstraintValidationHandler();
1159 for (Pair<IAllowedValuesConstraint, IDefinitionNodeItem<?, ?>> pair : localConstraints) {
1160 IAllowedValuesConstraint allowedValues = pair.getLeft();
1161 IDefinitionNodeItem<?, ?> node = ObjectUtils.notNull(pair.getRight());
1162 IAllowedValue matchingValue = allowedValues.getAllowedValue(value);
1163 if (matchingValue != null) {
1164 match = true;
1165 handlePass(allowedValues, node, item, dynamicContext);
1166 } else if (allowedValues.getExtensible() == IAllowedValuesConstraint.Extensible.NONE) {
1167
1168 failedConstraints = CollectionUtil.singletonList(allowedValues);
1169 match = false;
1170 break;
1171 } else {
1172 failedConstraints.add(allowedValues);
1173 }
1174 }
1175
1176
1177 if (!match && !localAllowOthers) {
1178 handler.handleAllowedValuesViolation(failedConstraints, item, dynamicContext);
1179 }
1180 }
1181 }
1182 }
1183
1184 class Visitor
1185 extends AbstractNodeItemVisitor<DynamicContext, Void> {
1186
1187
1188
1189
1190 private static final int PARALLEL_THRESHOLD = 4;
1191
1192 @NonNull
1193 private DynamicContext handleLetStatements(
1194 @NonNull INodeItem focus,
1195 @NonNull Map<IEnhancedQName, ILet> letExpressions,
1196 @NonNull DynamicContext dynamicContext) {
1197
1198 DynamicContext retval;
1199 Collection<ILet> lets = letExpressions.values();
1200 if (lets.isEmpty()) {
1201 retval = dynamicContext;
1202 } else {
1203 final DynamicContext subContext = dynamicContext.subContext();
1204 ValidationEventListener listener = getListener();
1205
1206 for (ILet let : lets) {
1207 IEnhancedQName name = let.getName();
1208 listener.beforeLetEvaluation(let);
1209 try {
1210 ISequence<?> result = let.getValueExpression().evaluate(focus, subContext);
1211 subContext.bindVariableValue(
1212 name,
1213
1214 result.reusable());
1215 } finally {
1216 listener.afterLetEvaluation(let);
1217 }
1218 }
1219 retval = subContext;
1220 }
1221 return retval;
1222 }
1223
1224 @Override
1225 public Void visitFlag(@NonNull IFlagNodeItem item, DynamicContext context) {
1226 assert context != null;
1227
1228 IFlagDefinition definition = item.getDefinition();
1229 DynamicContext effectiveContext = handleLetStatements(item, definition.getLetExpressions(), context);
1230
1231 validateFlag(item, effectiveContext);
1232 super.visitFlag(item, effectiveContext);
1233 handleAllowedValues(item, context);
1234 return null;
1235 }
1236
1237 @Override
1238 public Void visitField(@NonNull IFieldNodeItem item, DynamicContext context) {
1239 assert context != null;
1240
1241 IFieldDefinition definition = item.getDefinition();
1242 DynamicContext effectiveContext = handleLetStatements(item, definition.getLetExpressions(), context);
1243
1244 validateField(item, effectiveContext);
1245 super.visitField(item, effectiveContext);
1246 handleAllowedValues(item, context);
1247 return null;
1248 }
1249
1250 @Override
1251 public Void visitAssembly(@NonNull IAssemblyNodeItem item, DynamicContext context) {
1252 assert context != null;
1253
1254 IAssemblyDefinition definition = item.getDefinition();
1255 DynamicContext effectiveContext = handleLetStatements(item, definition.getLetExpressions(), context);
1256
1257 try {
1258 validateAssembly(item, effectiveContext);
1259 } catch (ConstraintValidationException ex) {
1260 throw ExceptionUtils.wrap(ex);
1261 }
1262
1263
1264 if (validationConfig.isParallel() && shouldParallelize(item)) {
1265 visitFlags(item, effectiveContext);
1266 visitChildrenParallel(item, effectiveContext);
1267 } else {
1268 super.visitAssembly(item, effectiveContext);
1269 }
1270
1271 return null;
1272 }
1273
1274
1275
1276
1277
1278
1279
1280
1281 private boolean shouldParallelize(@NonNull IAssemblyNodeItem item) {
1282 return item.modelItems().count() >= PARALLEL_THRESHOLD;
1283 }
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293 private void visitChildrenParallel(
1294 @NonNull IAssemblyNodeItem item,
1295 @NonNull DynamicContext context) {
1296
1297 ExecutorService executor = validationConfig.getExecutor();
1298 List<? extends IModelNodeItem<?, ?>> children = item.modelItems()
1299 .collect(Collectors.toList());
1300
1301 List<Future<?>> futures = new ArrayList<>(children.size());
1302 for (IModelNodeItem<?, ?> child : children) {
1303 futures.add(executor.submit(() -> {
1304
1305 DynamicContext childContext = context.subContext();
1306 child.accept(this, childContext);
1307 return null;
1308 }));
1309 }
1310
1311
1312 try {
1313 for (Future<?> future : futures) {
1314 future.get();
1315 }
1316 } catch (ExecutionException e) {
1317 cancelRemainingFutures(futures);
1318 Throwable cause = e.getCause();
1319 if (cause instanceof RuntimeException) {
1320 throw (RuntimeException) cause;
1321 }
1322 throw ExceptionUtils.wrap(new ConstraintValidationException("Error during parallel validation", cause));
1323 } catch (InterruptedException e) {
1324 cancelRemainingFutures(futures);
1325 Thread.currentThread().interrupt();
1326 throw ExceptionUtils.wrap(new ConstraintValidationException("Validation interrupted", e));
1327 }
1328 }
1329
1330
1331
1332
1333
1334
1335
1336 private void cancelRemainingFutures(@NonNull List<Future<?>> futures) {
1337 for (Future<?> future : futures) {
1338 if (!future.isDone()) {
1339 future.cancel(true);
1340 }
1341 }
1342 }
1343
1344 @Override
1345 public Void visitMetaschema(@NonNull IModuleNodeItem item, DynamicContext context) {
1346 throw new UnsupportedOperationException("Method not used.");
1347 }
1348
1349 @Override
1350 protected Void defaultResult() {
1351
1352 return null;
1353 }
1354 }
1355
1356 private static class KeyRef {
1357 @NonNull
1358 private final IIndexHasKeyConstraint constraint;
1359 @NonNull
1360 private final INodeItem node;
1361 @NonNull
1362 private final List<INodeItem> targets;
1363
1364 public KeyRef(
1365 @NonNull IIndexHasKeyConstraint constraint,
1366 @NonNull INodeItem node,
1367 @NonNull List<INodeItem> targets) {
1368 this.node = node;
1369 this.constraint = constraint;
1370 this.targets = targets;
1371 }
1372
1373 @NonNull
1374 public IIndexHasKeyConstraint getConstraint() {
1375 return constraint;
1376 }
1377
1378 @NonNull
1379 protected INodeItem getNode() {
1380 return node;
1381 }
1382
1383 @NonNull
1384 public List<INodeItem> getTargets() {
1385 return targets;
1386 }
1387 }
1388 }