DefaultConstraintValidator.java

  1. /*
  2.  * Portions of this software was developed by employees of the National Institute
  3.  * of Standards and Technology (NIST), an agency of the Federal Government and is
  4.  * being made available as a public service. Pursuant to title 17 United States
  5.  * Code Section 105, works of NIST employees are not subject to copyright
  6.  * protection in the United States. This software may be subject to foreign
  7.  * copyright. Permission in the United States and in foreign countries, to the
  8.  * extent that NIST may hold copyright, to use, copy, modify, create derivative
  9.  * works, and distribute this software and its documentation without fee is hereby
  10.  * granted on a non-exclusive basis, provided that this notice and disclaimer
  11.  * of warranty appears in all copies.
  12.  *
  13.  * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
  14.  * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
  15.  * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
  16.  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
  17.  * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
  18.  * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
  19.  * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
  20.  * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
  21.  * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
  22.  * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
  23.  * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
  24.  * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
  25.  */

  26. package gov.nist.secauto.metaschema.core.model.constraint;

  27. import gov.nist.secauto.metaschema.core.datatype.IDataTypeAdapter;
  28. import gov.nist.secauto.metaschema.core.metapath.DynamicContext;
  29. import gov.nist.secauto.metaschema.core.metapath.ISequence;
  30. import gov.nist.secauto.metaschema.core.metapath.MetapathException;
  31. import gov.nist.secauto.metaschema.core.metapath.MetapathExpression;
  32. import gov.nist.secauto.metaschema.core.metapath.function.library.FnBoolean;
  33. import gov.nist.secauto.metaschema.core.metapath.function.library.FnData;
  34. import gov.nist.secauto.metaschema.core.metapath.item.node.AbstractNodeItemVisitor;
  35. import gov.nist.secauto.metaschema.core.metapath.item.node.IAssemblyNodeItem;
  36. import gov.nist.secauto.metaschema.core.metapath.item.node.IDefinitionNodeItem;
  37. import gov.nist.secauto.metaschema.core.metapath.item.node.IDocumentNodeItem;
  38. import gov.nist.secauto.metaschema.core.metapath.item.node.IFieldNodeItem;
  39. import gov.nist.secauto.metaschema.core.metapath.item.node.IFlagNodeItem;
  40. import gov.nist.secauto.metaschema.core.metapath.item.node.IModuleNodeItem;
  41. import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem;
  42. import gov.nist.secauto.metaschema.core.model.IAssemblyDefinition;
  43. import gov.nist.secauto.metaschema.core.model.IFieldDefinition;
  44. import gov.nist.secauto.metaschema.core.model.IFlagDefinition;
  45. import gov.nist.secauto.metaschema.core.util.CollectionUtil;

  46. import org.apache.logging.log4j.LogManager;
  47. import org.apache.logging.log4j.Logger;

  48. import java.util.ArrayList;
  49. import java.util.LinkedHashMap;
  50. import java.util.LinkedList;
  51. import java.util.List;
  52. import java.util.Map;
  53. import java.util.concurrent.ConcurrentHashMap;
  54. import java.util.regex.Pattern;

  55. import edu.umd.cs.findbugs.annotations.NonNull;
  56. import edu.umd.cs.findbugs.annotations.Nullable;

  57. /**
  58.  * Used to perform constraint validation over one or more node items.
  59.  * <p>
  60.  * This class is not thread safe.
  61.  */
  62. public class DefaultConstraintValidator implements IConstraintValidator { // NOPMD - intentional
  63.   private static final Logger LOGGER = LogManager.getLogger(DefaultConstraintValidator.class);

  64.   @NonNull
  65.   private final Map<INodeItem, ValueStatus> valueMap = new LinkedHashMap<>(); // NOPMD - intentional
  66.   @NonNull
  67.   private final Map<String, IIndex> indexNameToIndexMap = new ConcurrentHashMap<>();
  68.   @NonNull
  69.   private final Map<String, List<KeyRef>> indexNameToKeyRefMap = new ConcurrentHashMap<>();
  70.   @NonNull
  71.   private final DynamicContext metapathContext;
  72.   @NonNull
  73.   private final IConstraintValidationHandler handler;

  74.   public DefaultConstraintValidator(
  75.       @NonNull DynamicContext metapathContext,
  76.       @NonNull IConstraintValidationHandler handler) {
  77.     this.metapathContext = metapathContext;
  78.     this.handler = handler;
  79.   }

  80.   @NonNull
  81.   public IConstraintValidationHandler getConstraintValidationHandler() {
  82.     return handler;
  83.   }

  84.   @NonNull
  85.   protected DynamicContext getMetapathContext() {
  86.     return metapathContext;
  87.   }

  88.   @Override
  89.   public void validate(@NonNull INodeItem item) {
  90.     item.accept(new Visitor(), null);
  91.   }

  92.   /**
  93.    * Validate the provided flag item against any associated constraints.
  94.    *
  95.    * @param item
  96.    *          the flag item to validate
  97.    * @throws MetapathException
  98.    *           if an error occurred while evaluating a Metapath used in a
  99.    *           constraint
  100.    */
  101.   protected void validateFlag(@NonNull IFlagNodeItem item) {
  102.     IFlagDefinition definition = item.getDefinition();

  103.     validateExpect(definition.getExpectConstraints(), item);
  104.     validateAllowedValues(definition.getAllowedValuesConstraints(), item);
  105.     validateIndexHasKey(definition.getIndexHasKeyConstraints(), item);
  106.     validateMatches(definition.getMatchesConstraints(), item);
  107.   }

  108.   /**
  109.    * Validate the provided field item against any associated constraints.
  110.    *
  111.    * @param item
  112.    *          the field item to validate
  113.    * @throws MetapathException
  114.    *           if an error occurred while evaluating a Metapath used in a
  115.    *           constraint
  116.    */
  117.   protected void validateField(@NonNull IFieldNodeItem item) {
  118.     IFieldDefinition definition = item.getDefinition();

  119.     validateExpect(definition.getExpectConstraints(), item);
  120.     validateAllowedValues(definition.getAllowedValuesConstraints(), item);
  121.     validateIndexHasKey(definition.getIndexHasKeyConstraints(), item);
  122.     validateMatches(definition.getMatchesConstraints(), item);
  123.   }

  124.   /**
  125.    * Validate the provided assembly item against any associated constraints.
  126.    *
  127.    * @param item
  128.    *          the assembly item to validate
  129.    * @throws MetapathException
  130.    *           if an error occurred while evaluating a Metapath used in a
  131.    *           constraint
  132.    */
  133.   protected void validateAssembly(@NonNull IAssemblyNodeItem item) {
  134.     IAssemblyDefinition definition = item.getDefinition();

  135.     validateExpect(definition.getExpectConstraints(), item);
  136.     validateAllowedValues(definition.getAllowedValuesConstraints(), item);
  137.     validateIndexHasKey(definition.getIndexHasKeyConstraints(), item);
  138.     validateMatches(definition.getMatchesConstraints(), item);
  139.     validateHasCardinality(definition.getHasCardinalityConstraints(), item);
  140.     validateIndex(definition.getIndexConstraints(), item);
  141.     validateUnique(definition.getUniqueConstraints(), item);
  142.   }

  143.   protected void validateHasCardinality(@NonNull List<? extends ICardinalityConstraint> constraints,
  144.       @NonNull List<? extends IAssemblyNodeItem> items) {

  145.     items.stream().forEachOrdered(item -> {
  146.       assert item != null;
  147.       validateHasCardinality(constraints, item);
  148.     });
  149.   }

  150.   protected void validateHasCardinality(@NonNull List<? extends ICardinalityConstraint> constraints,
  151.       @NonNull IAssemblyNodeItem item) {
  152.     for (ICardinalityConstraint constraint : constraints) {
  153.       ISequence<? extends IDefinitionNodeItem<?, ?>> targets = constraint.matchTargets(item, getMetapathContext());
  154.       try {
  155.         validateHasCardinality(constraint, item, targets);
  156.       } catch (MetapathException ex) {
  157.         rethrowConstraintError(constraint, item, ex);
  158.       }
  159.     }
  160.   }

  161.   protected void validateHasCardinality(@NonNull ICardinalityConstraint constraint, @NonNull IAssemblyNodeItem node,
  162.       ISequence<? extends INodeItem> targets) {
  163.     int itemCount = targets.size();

  164.     Integer minOccurs = constraint.getMinOccurs();
  165.     if (minOccurs != null && itemCount < minOccurs) {
  166.       getConstraintValidationHandler().handleCardinalityMinimumViolation(constraint, node, targets);
  167.     }

  168.     Integer maxOccurs = constraint.getMaxOccurs();
  169.     if (maxOccurs != null && itemCount > maxOccurs) {
  170.       getConstraintValidationHandler().handleCardinalityMaximumViolation(constraint, node, targets);
  171.     }
  172.   }

  173.   protected void validateIndex(@NonNull List<? extends IIndexConstraint> constraints,
  174.       @NonNull List<? extends IAssemblyNodeItem> items) {
  175.     items.stream().forEachOrdered(item -> {
  176.       assert item != null;
  177.       validateIndex(constraints, item);
  178.     });
  179.   }

  180.   protected void validateIndex(@NonNull List<? extends IIndexConstraint> constraints,
  181.       @NonNull IAssemblyNodeItem item) {
  182.     for (IIndexConstraint constraint : constraints) {
  183.       ISequence<? extends IDefinitionNodeItem<?, ?>> targets = constraint.matchTargets(item, getMetapathContext());
  184.       try {
  185.         validateIndex(constraint, item, targets);
  186.       } catch (MetapathException ex) {
  187.         rethrowConstraintError(constraint, item, ex);
  188.       }
  189.     }
  190.   }

  191.   protected void validateIndex(@NonNull IIndexConstraint constraint, @NonNull IAssemblyNodeItem node,
  192.       @NonNull ISequence<? extends INodeItem> targets) {
  193.     String indexName = constraint.getName();
  194.     if (indexNameToIndexMap.containsKey(indexName)) {
  195.       getConstraintValidationHandler().handleIndexDuplicateViolation(constraint, node);
  196.       return; // NOPMD - readability
  197.     }

  198.     IIndex index = IIndex.newInstance(constraint.getKeyFields());
  199.     targets.asStream()
  200.         .forEachOrdered(item -> {
  201.           assert item != null;
  202.           if (item.hasValue()) {
  203.             try {
  204.               INodeItem oldItem = index.put(item, metapathContext);
  205.               if (oldItem != null) {
  206.                 getConstraintValidationHandler().handleIndexDuplicateKeyViolation(constraint, node, oldItem, item);
  207.               }
  208.             } catch (MetapathException ex) {
  209.               getConstraintValidationHandler().handleKeyMatchError(constraint, node, item, ex);
  210.             }
  211.           }
  212.         });
  213.     indexNameToIndexMap.put(indexName, index);
  214.   }

  215.   protected void validateUnique(@NonNull List<? extends IUniqueConstraint> constraints,
  216.       @NonNull List<? extends IAssemblyNodeItem> items) {

  217.     items.stream().forEachOrdered(item -> {
  218.       assert item != null;
  219.       validateUnique(constraints, item);
  220.     });
  221.   }

  222.   protected void validateUnique(@NonNull List<? extends IUniqueConstraint> constraints,
  223.       @NonNull IAssemblyNodeItem item) {
  224.     for (IUniqueConstraint constraint : constraints) {
  225.       ISequence<? extends IDefinitionNodeItem<?, ?>> targets = constraint.matchTargets(item, getMetapathContext());
  226.       try {
  227.         validateUnique(constraint, item, targets);
  228.       } catch (MetapathException ex) {
  229.         rethrowConstraintError(constraint, item, ex);
  230.       }
  231.     }
  232.   }

  233.   protected void validateUnique(@NonNull IUniqueConstraint constraint,
  234.       @NonNull IAssemblyNodeItem node, @NonNull ISequence<? extends INodeItem> targets) {
  235.     IIndex index = IIndex.newInstance(constraint.getKeyFields());
  236.     targets.asStream()
  237.         .forEachOrdered(item -> {
  238.           assert item != null;
  239.           if (item.hasValue()) {
  240.             try {
  241.               INodeItem oldItem = index.put(item, metapathContext);
  242.               if (oldItem != null) {
  243.                 getConstraintValidationHandler().handleUniqueKeyViolation(constraint, node, oldItem, item);
  244.               }
  245.             } catch (MetapathException ex) {
  246.               getConstraintValidationHandler().handleKeyMatchError(constraint, node, item, ex);
  247.               throw ex;
  248.             }
  249.           }
  250.         });
  251.   }

  252.   protected void validateMatches(@NonNull List<? extends IMatchesConstraint> constraints,
  253.       @NonNull IDefinitionNodeItem<?, ?> item) {

  254.     for (IMatchesConstraint constraint : constraints) {
  255.       ISequence<? extends IDefinitionNodeItem<?, ?>> targets = constraint.matchTargets(item, getMetapathContext());
  256.       try {
  257.         validateMatches(constraint, item, targets);
  258.       } catch (MetapathException ex) {
  259.         rethrowConstraintError(constraint, item, ex);
  260.       }
  261.     }
  262.   }

  263.   protected void validateMatches(@NonNull IMatchesConstraint constraint, @NonNull INodeItem node,
  264.       ISequence<? extends INodeItem> targets) {
  265.     targets.asStream()
  266.         .forEachOrdered(item -> {
  267.           assert item != null;
  268.           if (item.hasValue()) {
  269.             String value = FnData.fnDataItem(item).asString();

  270.             Pattern pattern = constraint.getPattern();
  271.             if (pattern != null && !pattern.asMatchPredicate().test(value)) {
  272.               // failed pattern match
  273.               getConstraintValidationHandler().handleMatchPatternViolation(constraint, node, item, value);
  274.             }

  275.             IDataTypeAdapter<?> adapter = constraint.getDataType();
  276.             if (adapter != null) {
  277.               try {
  278.                 adapter.parse(value);
  279.               } catch (IllegalArgumentException ex) {
  280.                 getConstraintValidationHandler().handleMatchDatatypeViolation(constraint, node, item, value, ex);
  281.               }
  282.             }
  283.           }
  284.         });
  285.   }

  286.   protected void validateIndexHasKey(
  287.       @NonNull List<? extends IIndexHasKeyConstraint> constraints,
  288.       @NonNull IDefinitionNodeItem<?, ?> node) {

  289.     for (IIndexHasKeyConstraint constraint : constraints) {
  290.       ISequence<? extends IDefinitionNodeItem<?, ?>> targets = constraint.matchTargets(node, getMetapathContext());
  291.       validateIndexHasKey(constraint, node, targets);
  292.     }
  293.   }

  294.   protected void validateIndexHasKey(
  295.       @NonNull IIndexHasKeyConstraint constraint,
  296.       @NonNull IDefinitionNodeItem<?, ?> node,
  297.       @NonNull ISequence<? extends INodeItem> targets) {
  298.     String indexName = constraint.getIndexName();

  299.     List<KeyRef> keyRefItems = indexNameToKeyRefMap.get(indexName);
  300.     if (keyRefItems == null) {
  301.       keyRefItems = new LinkedList<>();
  302.       indexNameToKeyRefMap.put(indexName, keyRefItems);
  303.     }

  304.     KeyRef keyRef = new KeyRef(constraint, node, new ArrayList<>(targets.asList()));
  305.     keyRefItems.add(keyRef);
  306.   }

  307.   protected void validateExpect(@NonNull List<? extends IExpectConstraint> constraints,
  308.       @NonNull IDefinitionNodeItem<?, ?> item) {
  309.     for (IExpectConstraint constraint : constraints) {
  310.       ISequence<? extends IDefinitionNodeItem<?, ?>> targets = constraint.matchTargets(item, getMetapathContext());
  311.       validateExpect(constraint, item, targets);
  312.     }
  313.   }

  314.   protected void validateExpect(@NonNull IExpectConstraint constraint, @NonNull INodeItem node,
  315.       @NonNull ISequence<? extends INodeItem> targets) {
  316.     targets.asStream()
  317.         .map(item -> (INodeItem) item)
  318.         .forEachOrdered(item -> {
  319.           assert item != null;
  320.           if (item.hasValue()) {
  321.             MetapathExpression metapath = constraint.getTest();
  322.             try {
  323.               ISequence<?> result = metapath.evaluate(item, getMetapathContext());
  324.               if (!FnBoolean.fnBoolean(result).toBoolean()) {
  325.                 getConstraintValidationHandler().handleExpectViolation(constraint, node, item, getMetapathContext());
  326.               }
  327.             } catch (MetapathException ex) {
  328.               rethrowConstraintError(constraint, item, ex);
  329.             }
  330.           }
  331.         });
  332.   }

  333.   protected void validateAllowedValues(@NonNull List<? extends IAllowedValuesConstraint> constraints,
  334.       @NonNull IDefinitionNodeItem<?, ?> item) {
  335.     for (IAllowedValuesConstraint constraint : constraints) {
  336.       ISequence<? extends IDefinitionNodeItem<?, ?>> targets = constraint.matchTargets(item, getMetapathContext());
  337.       validateAllowedValues(constraint, targets);
  338.     }
  339.   }

  340.   protected void validateAllowedValues(@NonNull IAllowedValuesConstraint constraint,
  341.       ISequence<? extends IDefinitionNodeItem<?, ?>> targets) {
  342.     targets.asStream().forEachOrdered(item -> {
  343.       assert item != null;
  344.       if (item.hasValue()) {
  345.         try {
  346.           updateValueStatus(item, constraint);
  347.         } catch (MetapathException ex) {
  348.           rethrowConstraintError(constraint, item, ex);
  349.         }
  350.       }
  351.     });
  352.   }

  353.   private static void rethrowConstraintError(@NonNull IConstraint constraint, INodeItem item,
  354.       MetapathException ex) {
  355.     StringBuilder builder = new StringBuilder(128);
  356.     builder.append("A ")
  357.         .append(constraint.getClass().getName())
  358.         .append(" constraint");

  359.     String id = constraint.getId();
  360.     if (id == null) {
  361.       builder.append(" targeting the metapath '")
  362.           .append(constraint.getTarget().getPath())
  363.           .append('\'');
  364.     } else {
  365.       builder.append(" with id '")
  366.           .append(id)
  367.           .append('\'');
  368.     }

  369.     builder.append(", matching the item at path '")
  370.         .append(item.getMetapath())
  371.         .append("', resulted in an unexpected error. The error was: ")
  372.         .append(ex.getLocalizedMessage());

  373.     throw new MetapathException(builder.toString(), ex);
  374.   }

  375.   /**
  376.    * Add a new allowed value to the value status tracker.
  377.    *
  378.    * @param targetItem
  379.    *          the item whose value is targeted by the constraint
  380.    * @param allowedValues
  381.    *          the set of allowed values
  382.    */
  383.   protected void updateValueStatus(@NonNull INodeItem targetItem, @NonNull IAllowedValuesConstraint allowedValues) {
  384.     // constraint.getAllowedValues().containsKey(value)

  385.     @Nullable ValueStatus valueStatus = valueMap.get(targetItem);
  386.     if (valueStatus == null) {
  387.       valueStatus = new ValueStatus(targetItem);
  388.       valueMap.put(targetItem, valueStatus);
  389.     }

  390.     valueStatus.registerAllowedValue(allowedValues);
  391.   }

  392.   protected void handleAllowedValues(@NonNull INodeItem targetItem) {
  393.     ValueStatus valueStatus = valueMap.remove(targetItem);
  394.     if (valueStatus != null) {
  395.       valueStatus.validate();
  396.     }
  397.   }

  398.   @Override
  399.   public void finalizeValidation() {
  400.     // key references
  401.     for (Map.Entry<String, List<KeyRef>> entry : indexNameToKeyRefMap.entrySet()) {
  402.       String indexName = entry.getKey();
  403.       IIndex index = indexNameToIndexMap.get(indexName);

  404.       List<KeyRef> keyRefs = entry.getValue();

  405.       for (KeyRef keyRef : keyRefs) {
  406.         IIndexHasKeyConstraint constraint = keyRef.getConstraint();
  407.         for (INodeItem item : keyRef.getTargets()) {
  408.           assert item != null;

  409.           try {
  410.             List<String> key = IIndex.toKey(item, constraint.getKeyFields(), getMetapathContext());

  411.             INodeItem referencedItem = index.get(key);

  412.             if (referencedItem == null) {
  413.               getConstraintValidationHandler().handleIndexMiss(constraint, keyRef.getNode(), item, key);
  414.             }
  415.           } catch (MetapathException ex) {
  416.             getConstraintValidationHandler().handleKeyMatchError(constraint, keyRef.getNode(), item, ex);
  417.           }
  418.         }
  419.       }
  420.     }
  421.   }

  422.   private class ValueStatus {
  423.     @NonNull
  424.     private final List<IAllowedValuesConstraint> constraints = new LinkedList<>();
  425.     @NonNull
  426.     private final String value;
  427.     @NonNull
  428.     private final INodeItem item;
  429.     private boolean allowOthers = true;
  430.     @NonNull
  431.     private IAllowedValuesConstraint.Extensible extensible = IAllowedValuesConstraint.Extensible.EXTERNAL;

  432.     public ValueStatus(@NonNull INodeItem item) {
  433.       this.item = item;
  434.       this.value = FnData.fnDataItem(item).asString();
  435.     }

  436.     public void registerAllowedValue(@NonNull IAllowedValuesConstraint allowedValues) {
  437.       this.constraints.add(allowedValues);
  438.       if (!allowedValues.isAllowedOther()) {
  439.         // record the most restrictive value
  440.         allowOthers = false;
  441.       }

  442.       IAllowedValuesConstraint.Extensible newExtensible = allowedValues.getExtensible();
  443.       if (newExtensible.ordinal() > extensible.ordinal()) {
  444.         // record the most restrictive value
  445.         extensible = allowedValues.getExtensible();
  446.       } else if (IAllowedValuesConstraint.Extensible.NONE.equals(newExtensible)
  447.           && IAllowedValuesConstraint.Extensible.NONE.equals(extensible)) {
  448.         // this is an error, where there are two none constraints that conflict
  449.         throw new MetapathException(
  450.             String.format("Multiple constraints have extensibility scope=none at path '%s'", item.getMetapath()));
  451.       } else if (allowedValues.getExtensible().ordinal() < extensible.ordinal()) {
  452.         String msg = String.format(
  453.             "An allowed values constraint with an extensibility scope '%s'"
  454.                 + " exceeds the allowed scope '%s' at path '%s'",
  455.             allowedValues.getExtensible().name(), extensible.name(), item.getMetapath());
  456.         LOGGER.atError().log(msg);
  457.         throw new MetapathException(msg);
  458.       }
  459.     }

  460.     public void validate() {
  461.       if (!constraints.isEmpty()) {
  462.         boolean match = false;
  463.         List<IAllowedValuesConstraint> failedConstraints = new LinkedList<>();
  464.         for (IAllowedValuesConstraint allowedValues : constraints) {
  465.           IAllowedValue matchingValue = allowedValues.getAllowedValue(value);
  466.           if (matchingValue != null) {
  467.             match = true;
  468.           } else if (IAllowedValuesConstraint.Extensible.NONE.equals(allowedValues.getExtensible())) {
  469.             // hard failure, since no other values can satisfy this constraint
  470.             failedConstraints = CollectionUtil.singletonList(allowedValues);
  471.             match = false;
  472.             break;
  473.           } else {
  474.             failedConstraints.add(allowedValues);
  475.           } // this constraint passes, but we need to make sure other constraints do as well
  476.         }

  477.         // it's not a failure if allow others is true
  478.         if (!match && !allowOthers) {
  479.           getConstraintValidationHandler().handleAllowedValuesViolation(failedConstraints, item);
  480.         }
  481.       }
  482.     }
  483.   }

  484.   class Visitor
  485.       extends AbstractNodeItemVisitor<Void, Void> {
  486.     @Override
  487.     public Void visitDocument(@NonNull IDocumentNodeItem item, Void context) {
  488.       return super.visitDocument(item, context);
  489.     }

  490.     @Override
  491.     public Void visitFlag(@NonNull IFlagNodeItem item, Void context) {
  492.       validateFlag(item);
  493.       super.visitFlag(item, context);
  494.       handleAllowedValues(item);
  495.       return null;
  496.     }

  497.     @Override
  498.     public Void visitField(@NonNull IFieldNodeItem item, Void context) {
  499.       validateField(item);
  500.       super.visitField(item, context);
  501.       handleAllowedValues(item);
  502.       return null;
  503.     }

  504.     @Override
  505.     public Void visitAssembly(@NonNull IAssemblyNodeItem item, Void context) {
  506.       validateAssembly(item);
  507.       super.visitAssembly(item, context);
  508.       return null;
  509.     }

  510.     @Override
  511.     public Void visitMetaschema(@NonNull IModuleNodeItem item, Void context) {
  512.       throw new UnsupportedOperationException("not needed");
  513.     }

  514.     @Override
  515.     protected Void defaultResult() {
  516.       // no result value
  517.       return null;
  518.     }
  519.   }

  520.   private static class KeyRef {
  521.     @NonNull
  522.     private final IIndexHasKeyConstraint constraint;
  523.     @NonNull
  524.     private final INodeItem node;
  525.     @NonNull
  526.     private final List<INodeItem> targets;

  527.     public KeyRef(
  528.         @NonNull IIndexHasKeyConstraint constraint,
  529.         @NonNull INodeItem node,
  530.         @NonNull List<INodeItem> targets) {
  531.       this.node = node;
  532.       this.constraint = constraint;
  533.       this.targets = targets;
  534.     }

  535.     @NonNull
  536.     public IIndexHasKeyConstraint getConstraint() {
  537.       return constraint;
  538.     }

  539.     @NonNull
  540.     protected INodeItem getNode() {
  541.       return node;
  542.     }

  543.     @NonNull
  544.     public List<INodeItem> getTargets() {
  545.       return targets;
  546.     }
  547.   }
  548. }