001/* 002 * Portions of this software was developed by employees of the National Institute 003 * of Standards and Technology (NIST), an agency of the Federal Government and is 004 * being made available as a public service. Pursuant to title 17 United States 005 * Code Section 105, works of NIST employees are not subject to copyright 006 * protection in the United States. This software may be subject to foreign 007 * copyright. Permission in the United States and in foreign countries, to the 008 * extent that NIST may hold copyright, to use, copy, modify, create derivative 009 * works, and distribute this software and its documentation without fee is hereby 010 * granted on a non-exclusive basis, provided that this notice and disclaimer 011 * of warranty appears in all copies. 012 * 013 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER 014 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY 015 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF 016 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM 017 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE 018 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE. IN NO EVENT 019 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT, 020 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM, 021 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY, 022 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR 023 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT 024 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER. 025 */ 026 027package gov.nist.secauto.metaschema.core.model.constraint; 028 029import gov.nist.secauto.metaschema.core.datatype.IDataTypeAdapter; 030import gov.nist.secauto.metaschema.core.metapath.DynamicContext; 031import gov.nist.secauto.metaschema.core.metapath.ISequence; 032import gov.nist.secauto.metaschema.core.metapath.MetapathException; 033import gov.nist.secauto.metaschema.core.metapath.MetapathExpression; 034import gov.nist.secauto.metaschema.core.metapath.function.library.FnBoolean; 035import gov.nist.secauto.metaschema.core.metapath.function.library.FnData; 036import gov.nist.secauto.metaschema.core.metapath.item.node.AbstractNodeItemVisitor; 037import gov.nist.secauto.metaschema.core.metapath.item.node.IAssemblyNodeItem; 038import gov.nist.secauto.metaschema.core.metapath.item.node.IDefinitionNodeItem; 039import gov.nist.secauto.metaschema.core.metapath.item.node.IDocumentNodeItem; 040import gov.nist.secauto.metaschema.core.metapath.item.node.IFieldNodeItem; 041import gov.nist.secauto.metaschema.core.metapath.item.node.IFlagNodeItem; 042import gov.nist.secauto.metaschema.core.metapath.item.node.IModuleNodeItem; 043import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem; 044import gov.nist.secauto.metaschema.core.model.IAssemblyDefinition; 045import gov.nist.secauto.metaschema.core.model.IFieldDefinition; 046import gov.nist.secauto.metaschema.core.model.IFlagDefinition; 047import gov.nist.secauto.metaschema.core.util.CollectionUtil; 048 049import org.apache.logging.log4j.LogManager; 050import org.apache.logging.log4j.Logger; 051 052import java.util.ArrayList; 053import java.util.LinkedHashMap; 054import java.util.LinkedList; 055import java.util.List; 056import java.util.Map; 057import java.util.concurrent.ConcurrentHashMap; 058import java.util.regex.Pattern; 059 060import edu.umd.cs.findbugs.annotations.NonNull; 061import edu.umd.cs.findbugs.annotations.Nullable; 062 063/** 064 * Used to perform constraint validation over one or more node items. 065 * <p> 066 * This class is not thread safe. 067 */ 068public class DefaultConstraintValidator implements IConstraintValidator { // NOPMD - intentional 069 private static final Logger LOGGER = LogManager.getLogger(DefaultConstraintValidator.class); 070 071 @NonNull 072 private final Map<INodeItem, ValueStatus> valueMap = new LinkedHashMap<>(); // NOPMD - intentional 073 @NonNull 074 private final Map<String, IIndex> indexNameToIndexMap = new ConcurrentHashMap<>(); 075 @NonNull 076 private final Map<String, List<KeyRef>> indexNameToKeyRefMap = new ConcurrentHashMap<>(); 077 @NonNull 078 private final DynamicContext metapathContext; 079 @NonNull 080 private final IConstraintValidationHandler handler; 081 082 public DefaultConstraintValidator( 083 @NonNull DynamicContext metapathContext, 084 @NonNull IConstraintValidationHandler handler) { 085 this.metapathContext = metapathContext; 086 this.handler = handler; 087 } 088 089 @NonNull 090 public IConstraintValidationHandler getConstraintValidationHandler() { 091 return handler; 092 } 093 094 @NonNull 095 protected DynamicContext getMetapathContext() { 096 return metapathContext; 097 } 098 099 @Override 100 public void validate(@NonNull INodeItem item) { 101 item.accept(new Visitor(), null); 102 } 103 104 /** 105 * Validate the provided flag item against any associated constraints. 106 * 107 * @param item 108 * the flag item to validate 109 * @throws MetapathException 110 * if an error occurred while evaluating a Metapath used in a 111 * constraint 112 */ 113 protected void validateFlag(@NonNull IFlagNodeItem item) { 114 IFlagDefinition definition = item.getDefinition(); 115 116 validateExpect(definition.getExpectConstraints(), item); 117 validateAllowedValues(definition.getAllowedValuesConstraints(), item); 118 validateIndexHasKey(definition.getIndexHasKeyConstraints(), item); 119 validateMatches(definition.getMatchesConstraints(), item); 120 } 121 122 /** 123 * Validate the provided field item against any associated constraints. 124 * 125 * @param item 126 * the field item to validate 127 * @throws MetapathException 128 * if an error occurred while evaluating a Metapath used in a 129 * constraint 130 */ 131 protected void validateField(@NonNull IFieldNodeItem item) { 132 IFieldDefinition definition = item.getDefinition(); 133 134 validateExpect(definition.getExpectConstraints(), item); 135 validateAllowedValues(definition.getAllowedValuesConstraints(), item); 136 validateIndexHasKey(definition.getIndexHasKeyConstraints(), item); 137 validateMatches(definition.getMatchesConstraints(), item); 138 } 139 140 /** 141 * Validate the provided assembly item against any associated constraints. 142 * 143 * @param item 144 * the assembly item to validate 145 * @throws MetapathException 146 * if an error occurred while evaluating a Metapath used in a 147 * constraint 148 */ 149 protected void validateAssembly(@NonNull IAssemblyNodeItem item) { 150 IAssemblyDefinition definition = item.getDefinition(); 151 152 validateExpect(definition.getExpectConstraints(), item); 153 validateAllowedValues(definition.getAllowedValuesConstraints(), item); 154 validateIndexHasKey(definition.getIndexHasKeyConstraints(), item); 155 validateMatches(definition.getMatchesConstraints(), item); 156 validateHasCardinality(definition.getHasCardinalityConstraints(), item); 157 validateIndex(definition.getIndexConstraints(), item); 158 validateUnique(definition.getUniqueConstraints(), item); 159 } 160 161 protected void validateHasCardinality(@NonNull List<? extends ICardinalityConstraint> constraints, 162 @NonNull List<? extends IAssemblyNodeItem> items) { 163 164 items.stream().forEachOrdered(item -> { 165 assert item != null; 166 validateHasCardinality(constraints, item); 167 }); 168 } 169 170 protected void validateHasCardinality(@NonNull List<? extends ICardinalityConstraint> constraints, 171 @NonNull IAssemblyNodeItem item) { 172 for (ICardinalityConstraint constraint : constraints) { 173 ISequence<? extends IDefinitionNodeItem<?, ?>> targets = constraint.matchTargets(item, getMetapathContext()); 174 try { 175 validateHasCardinality(constraint, item, targets); 176 } catch (MetapathException ex) { 177 rethrowConstraintError(constraint, item, ex); 178 } 179 } 180 } 181 182 protected void validateHasCardinality(@NonNull ICardinalityConstraint constraint, @NonNull IAssemblyNodeItem node, 183 ISequence<? extends INodeItem> targets) { 184 int itemCount = targets.size(); 185 186 Integer minOccurs = constraint.getMinOccurs(); 187 if (minOccurs != null && itemCount < minOccurs) { 188 getConstraintValidationHandler().handleCardinalityMinimumViolation(constraint, node, targets); 189 } 190 191 Integer maxOccurs = constraint.getMaxOccurs(); 192 if (maxOccurs != null && itemCount > maxOccurs) { 193 getConstraintValidationHandler().handleCardinalityMaximumViolation(constraint, node, targets); 194 } 195 } 196 197 protected void validateIndex(@NonNull List<? extends IIndexConstraint> constraints, 198 @NonNull List<? extends IAssemblyNodeItem> items) { 199 items.stream().forEachOrdered(item -> { 200 assert item != null; 201 validateIndex(constraints, item); 202 }); 203 } 204 205 protected void validateIndex(@NonNull List<? extends IIndexConstraint> constraints, 206 @NonNull IAssemblyNodeItem item) { 207 for (IIndexConstraint constraint : constraints) { 208 ISequence<? extends IDefinitionNodeItem<?, ?>> targets = constraint.matchTargets(item, getMetapathContext()); 209 try { 210 validateIndex(constraint, item, targets); 211 } catch (MetapathException ex) { 212 rethrowConstraintError(constraint, item, ex); 213 } 214 } 215 } 216 217 protected void validateIndex(@NonNull IIndexConstraint constraint, @NonNull IAssemblyNodeItem node, 218 @NonNull ISequence<? extends INodeItem> targets) { 219 String indexName = constraint.getName(); 220 if (indexNameToIndexMap.containsKey(indexName)) { 221 getConstraintValidationHandler().handleIndexDuplicateViolation(constraint, node); 222 return; // NOPMD - readability 223 } 224 225 IIndex index = IIndex.newInstance(constraint.getKeyFields()); 226 targets.asStream() 227 .forEachOrdered(item -> { 228 assert item != null; 229 if (item.hasValue()) { 230 try { 231 INodeItem oldItem = index.put(item, metapathContext); 232 if (oldItem != null) { 233 getConstraintValidationHandler().handleIndexDuplicateKeyViolation(constraint, node, oldItem, item); 234 } 235 } catch (MetapathException ex) { 236 getConstraintValidationHandler().handleKeyMatchError(constraint, node, item, ex); 237 } 238 } 239 }); 240 indexNameToIndexMap.put(indexName, index); 241 } 242 243 protected void validateUnique(@NonNull List<? extends IUniqueConstraint> constraints, 244 @NonNull List<? extends IAssemblyNodeItem> items) { 245 246 items.stream().forEachOrdered(item -> { 247 assert item != null; 248 validateUnique(constraints, item); 249 }); 250 } 251 252 protected void validateUnique(@NonNull List<? extends IUniqueConstraint> constraints, 253 @NonNull IAssemblyNodeItem item) { 254 for (IUniqueConstraint constraint : constraints) { 255 ISequence<? extends IDefinitionNodeItem<?, ?>> targets = constraint.matchTargets(item, getMetapathContext()); 256 try { 257 validateUnique(constraint, item, targets); 258 } catch (MetapathException ex) { 259 rethrowConstraintError(constraint, item, ex); 260 } 261 } 262 } 263 264 protected void validateUnique(@NonNull IUniqueConstraint constraint, 265 @NonNull IAssemblyNodeItem node, @NonNull ISequence<? extends INodeItem> targets) { 266 IIndex index = IIndex.newInstance(constraint.getKeyFields()); 267 targets.asStream() 268 .forEachOrdered(item -> { 269 assert item != null; 270 if (item.hasValue()) { 271 try { 272 INodeItem oldItem = index.put(item, metapathContext); 273 if (oldItem != null) { 274 getConstraintValidationHandler().handleUniqueKeyViolation(constraint, node, oldItem, item); 275 } 276 } catch (MetapathException ex) { 277 getConstraintValidationHandler().handleKeyMatchError(constraint, node, item, ex); 278 throw ex; 279 } 280 } 281 }); 282 } 283 284 protected void validateMatches(@NonNull List<? extends IMatchesConstraint> constraints, 285 @NonNull IDefinitionNodeItem<?, ?> item) { 286 287 for (IMatchesConstraint constraint : constraints) { 288 ISequence<? extends IDefinitionNodeItem<?, ?>> targets = constraint.matchTargets(item, getMetapathContext()); 289 try { 290 validateMatches(constraint, item, targets); 291 } catch (MetapathException ex) { 292 rethrowConstraintError(constraint, item, ex); 293 } 294 } 295 } 296 297 protected void validateMatches(@NonNull IMatchesConstraint constraint, @NonNull INodeItem node, 298 ISequence<? extends INodeItem> targets) { 299 targets.asStream() 300 .forEachOrdered(item -> { 301 assert item != null; 302 if (item.hasValue()) { 303 String value = FnData.fnDataItem(item).asString(); 304 305 Pattern pattern = constraint.getPattern(); 306 if (pattern != null && !pattern.asMatchPredicate().test(value)) { 307 // failed pattern match 308 getConstraintValidationHandler().handleMatchPatternViolation(constraint, node, item, value); 309 } 310 311 IDataTypeAdapter<?> adapter = constraint.getDataType(); 312 if (adapter != null) { 313 try { 314 adapter.parse(value); 315 } catch (IllegalArgumentException ex) { 316 getConstraintValidationHandler().handleMatchDatatypeViolation(constraint, node, item, value, ex); 317 } 318 } 319 } 320 }); 321 } 322 323 protected void validateIndexHasKey( 324 @NonNull List<? extends IIndexHasKeyConstraint> constraints, 325 @NonNull IDefinitionNodeItem<?, ?> node) { 326 327 for (IIndexHasKeyConstraint constraint : constraints) { 328 ISequence<? extends IDefinitionNodeItem<?, ?>> targets = constraint.matchTargets(node, getMetapathContext()); 329 validateIndexHasKey(constraint, node, targets); 330 } 331 } 332 333 protected void validateIndexHasKey( 334 @NonNull IIndexHasKeyConstraint constraint, 335 @NonNull IDefinitionNodeItem<?, ?> node, 336 @NonNull ISequence<? extends INodeItem> targets) { 337 String indexName = constraint.getIndexName(); 338 339 List<KeyRef> keyRefItems = indexNameToKeyRefMap.get(indexName); 340 if (keyRefItems == null) { 341 keyRefItems = new LinkedList<>(); 342 indexNameToKeyRefMap.put(indexName, keyRefItems); 343 } 344 345 KeyRef keyRef = new KeyRef(constraint, node, new ArrayList<>(targets.asList())); 346 keyRefItems.add(keyRef); 347 } 348 349 protected void validateExpect(@NonNull List<? extends IExpectConstraint> constraints, 350 @NonNull IDefinitionNodeItem<?, ?> item) { 351 for (IExpectConstraint constraint : constraints) { 352 ISequence<? extends IDefinitionNodeItem<?, ?>> targets = constraint.matchTargets(item, getMetapathContext()); 353 validateExpect(constraint, item, targets); 354 } 355 } 356 357 protected void validateExpect(@NonNull IExpectConstraint constraint, @NonNull INodeItem node, 358 @NonNull ISequence<? extends INodeItem> targets) { 359 targets.asStream() 360 .map(item -> (INodeItem) item) 361 .forEachOrdered(item -> { 362 assert item != null; 363 if (item.hasValue()) { 364 MetapathExpression metapath = constraint.getTest(); 365 try { 366 ISequence<?> result = metapath.evaluate(item, getMetapathContext()); 367 if (!FnBoolean.fnBoolean(result).toBoolean()) { 368 getConstraintValidationHandler().handleExpectViolation(constraint, node, item, getMetapathContext()); 369 } 370 } catch (MetapathException ex) { 371 rethrowConstraintError(constraint, item, ex); 372 } 373 } 374 }); 375 } 376 377 protected void validateAllowedValues(@NonNull List<? extends IAllowedValuesConstraint> constraints, 378 @NonNull IDefinitionNodeItem<?, ?> item) { 379 for (IAllowedValuesConstraint constraint : constraints) { 380 ISequence<? extends IDefinitionNodeItem<?, ?>> targets = constraint.matchTargets(item, getMetapathContext()); 381 validateAllowedValues(constraint, targets); 382 } 383 } 384 385 protected void validateAllowedValues(@NonNull IAllowedValuesConstraint constraint, 386 ISequence<? extends IDefinitionNodeItem<?, ?>> targets) { 387 targets.asStream().forEachOrdered(item -> { 388 assert item != null; 389 if (item.hasValue()) { 390 try { 391 updateValueStatus(item, constraint); 392 } catch (MetapathException ex) { 393 rethrowConstraintError(constraint, item, ex); 394 } 395 } 396 }); 397 } 398 399 private static void rethrowConstraintError(@NonNull IConstraint constraint, INodeItem item, 400 MetapathException ex) { 401 StringBuilder builder = new StringBuilder(128); 402 builder.append("A ") 403 .append(constraint.getClass().getName()) 404 .append(" constraint"); 405 406 String id = constraint.getId(); 407 if (id == null) { 408 builder.append(" targeting the metapath '") 409 .append(constraint.getTarget().getPath()) 410 .append('\''); 411 } else { 412 builder.append(" with id '") 413 .append(id) 414 .append('\''); 415 } 416 417 builder.append(", matching the item at path '") 418 .append(item.getMetapath()) 419 .append("', resulted in an unexpected error. The error was: ") 420 .append(ex.getLocalizedMessage()); 421 422 throw new MetapathException(builder.toString(), ex); 423 } 424 425 /** 426 * Add a new allowed value to the value status tracker. 427 * 428 * @param targetItem 429 * the item whose value is targeted by the constraint 430 * @param allowedValues 431 * the set of allowed values 432 */ 433 protected void updateValueStatus(@NonNull INodeItem targetItem, @NonNull IAllowedValuesConstraint allowedValues) { 434 // constraint.getAllowedValues().containsKey(value) 435 436 @Nullable ValueStatus valueStatus = valueMap.get(targetItem); 437 if (valueStatus == null) { 438 valueStatus = new ValueStatus(targetItem); 439 valueMap.put(targetItem, valueStatus); 440 } 441 442 valueStatus.registerAllowedValue(allowedValues); 443 } 444 445 protected void handleAllowedValues(@NonNull INodeItem targetItem) { 446 ValueStatus valueStatus = valueMap.remove(targetItem); 447 if (valueStatus != null) { 448 valueStatus.validate(); 449 } 450 } 451 452 @Override 453 public void finalizeValidation() { 454 // key references 455 for (Map.Entry<String, List<KeyRef>> entry : indexNameToKeyRefMap.entrySet()) { 456 String indexName = entry.getKey(); 457 IIndex index = indexNameToIndexMap.get(indexName); 458 459 List<KeyRef> keyRefs = entry.getValue(); 460 461 for (KeyRef keyRef : keyRefs) { 462 IIndexHasKeyConstraint constraint = keyRef.getConstraint(); 463 for (INodeItem item : keyRef.getTargets()) { 464 assert item != null; 465 466 try { 467 List<String> key = IIndex.toKey(item, constraint.getKeyFields(), getMetapathContext()); 468 469 INodeItem referencedItem = index.get(key); 470 471 if (referencedItem == null) { 472 getConstraintValidationHandler().handleIndexMiss(constraint, keyRef.getNode(), item, key); 473 } 474 } catch (MetapathException ex) { 475 getConstraintValidationHandler().handleKeyMatchError(constraint, keyRef.getNode(), item, ex); 476 } 477 } 478 } 479 } 480 } 481 482 private class ValueStatus { 483 @NonNull 484 private final List<IAllowedValuesConstraint> constraints = new LinkedList<>(); 485 @NonNull 486 private final String value; 487 @NonNull 488 private final INodeItem item; 489 private boolean allowOthers = true; 490 @NonNull 491 private IAllowedValuesConstraint.Extensible extensible = IAllowedValuesConstraint.Extensible.EXTERNAL; 492 493 public ValueStatus(@NonNull INodeItem item) { 494 this.item = item; 495 this.value = FnData.fnDataItem(item).asString(); 496 } 497 498 public void registerAllowedValue(@NonNull IAllowedValuesConstraint allowedValues) { 499 this.constraints.add(allowedValues); 500 if (!allowedValues.isAllowedOther()) { 501 // record the most restrictive value 502 allowOthers = false; 503 } 504 505 IAllowedValuesConstraint.Extensible newExtensible = allowedValues.getExtensible(); 506 if (newExtensible.ordinal() > extensible.ordinal()) { 507 // record the most restrictive value 508 extensible = allowedValues.getExtensible(); 509 } else if (IAllowedValuesConstraint.Extensible.NONE.equals(newExtensible) 510 && IAllowedValuesConstraint.Extensible.NONE.equals(extensible)) { 511 // this is an error, where there are two none constraints that conflict 512 throw new MetapathException( 513 String.format("Multiple constraints have extensibility scope=none at path '%s'", item.getMetapath())); 514 } else if (allowedValues.getExtensible().ordinal() < extensible.ordinal()) { 515 String msg = String.format( 516 "An allowed values constraint with an extensibility scope '%s'" 517 + " exceeds the allowed scope '%s' at path '%s'", 518 allowedValues.getExtensible().name(), extensible.name(), item.getMetapath()); 519 LOGGER.atError().log(msg); 520 throw new MetapathException(msg); 521 } 522 } 523 524 public void validate() { 525 if (!constraints.isEmpty()) { 526 boolean match = false; 527 List<IAllowedValuesConstraint> failedConstraints = new LinkedList<>(); 528 for (IAllowedValuesConstraint allowedValues : constraints) { 529 IAllowedValue matchingValue = allowedValues.getAllowedValue(value); 530 if (matchingValue != null) { 531 match = true; 532 } else if (IAllowedValuesConstraint.Extensible.NONE.equals(allowedValues.getExtensible())) { 533 // hard failure, since no other values can satisfy this constraint 534 failedConstraints = CollectionUtil.singletonList(allowedValues); 535 match = false; 536 break; 537 } else { 538 failedConstraints.add(allowedValues); 539 } // this constraint passes, but we need to make sure other constraints do as well 540 } 541 542 // it's not a failure if allow others is true 543 if (!match && !allowOthers) { 544 getConstraintValidationHandler().handleAllowedValuesViolation(failedConstraints, item); 545 } 546 } 547 } 548 } 549 550 class Visitor 551 extends AbstractNodeItemVisitor<Void, Void> { 552 @Override 553 public Void visitDocument(@NonNull IDocumentNodeItem item, Void context) { 554 return super.visitDocument(item, context); 555 } 556 557 @Override 558 public Void visitFlag(@NonNull IFlagNodeItem item, Void context) { 559 validateFlag(item); 560 super.visitFlag(item, context); 561 handleAllowedValues(item); 562 return null; 563 } 564 565 @Override 566 public Void visitField(@NonNull IFieldNodeItem item, Void context) { 567 validateField(item); 568 super.visitField(item, context); 569 handleAllowedValues(item); 570 return null; 571 } 572 573 @Override 574 public Void visitAssembly(@NonNull IAssemblyNodeItem item, Void context) { 575 validateAssembly(item); 576 super.visitAssembly(item, context); 577 return null; 578 } 579 580 @Override 581 public Void visitMetaschema(@NonNull IModuleNodeItem item, Void context) { 582 throw new UnsupportedOperationException("not needed"); 583 } 584 585 @Override 586 protected Void defaultResult() { 587 // no result value 588 return null; 589 } 590 } 591 592 private static class KeyRef { 593 @NonNull 594 private final IIndexHasKeyConstraint constraint; 595 @NonNull 596 private final INodeItem node; 597 @NonNull 598 private final List<INodeItem> targets; 599 600 public KeyRef( 601 @NonNull IIndexHasKeyConstraint constraint, 602 @NonNull INodeItem node, 603 @NonNull List<INodeItem> targets) { 604 this.node = node; 605 this.constraint = constraint; 606 this.targets = targets; 607 } 608 609 @NonNull 610 public IIndexHasKeyConstraint getConstraint() { 611 return constraint; 612 } 613 614 @NonNull 615 protected INodeItem getNode() { 616 return node; 617 } 618 619 @NonNull 620 public List<INodeItem> getTargets() { 621 return targets; 622 } 623 } 624}