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