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.oscal.lib.profile.resolver.policy; 028 029import com.vladsch.flexmark.ast.InlineLinkNode; 030import com.vladsch.flexmark.util.ast.Node; 031 032import gov.nist.secauto.metaschema.model.common.datatype.markup.IMarkupString; 033import gov.nist.secauto.metaschema.model.common.datatype.markup.flexmark.InsertAnchorExtension.InsertAnchorNode; 034import gov.nist.secauto.metaschema.model.common.metapath.MetapathExpression; 035import gov.nist.secauto.metaschema.model.common.metapath.format.IPathFormatter; 036import gov.nist.secauto.metaschema.model.common.metapath.function.library.FnData; 037import gov.nist.secauto.metaschema.model.common.metapath.item.IDocumentNodeItem; 038import gov.nist.secauto.metaschema.model.common.metapath.item.IMarkupItem; 039import gov.nist.secauto.metaschema.model.common.metapath.item.IRequiredValueModelNodeItem; 040import gov.nist.secauto.metaschema.model.common.util.CollectionUtil; 041import gov.nist.secauto.metaschema.model.common.util.ObjectUtils; 042import gov.nist.secauto.oscal.lib.model.CatalogGroup; 043import gov.nist.secauto.oscal.lib.model.Control; 044import gov.nist.secauto.oscal.lib.model.ControlPart; 045import gov.nist.secauto.oscal.lib.model.Link; 046import gov.nist.secauto.oscal.lib.model.Property; 047import gov.nist.secauto.oscal.lib.model.metadata.AbstractProperty; 048import gov.nist.secauto.oscal.lib.model.metadata.IProperty; 049import gov.nist.secauto.oscal.lib.profile.resolver.support.AbstractCatalogEntityVisitor; 050import gov.nist.secauto.oscal.lib.profile.resolver.support.IEntityItem; 051import gov.nist.secauto.oscal.lib.profile.resolver.support.IIndexer; 052 053import org.apache.logging.log4j.LogManager; 054import org.apache.logging.log4j.Logger; 055 056import java.net.URI; 057import java.util.EnumSet; 058import java.util.HashMap; 059import java.util.HashSet; 060import java.util.Locale; 061import java.util.Map; 062import java.util.Set; 063import java.util.UUID; 064import java.util.function.BiConsumer; 065 066import javax.xml.namespace.QName; 067 068import edu.umd.cs.findbugs.annotations.NonNull; 069import edu.umd.cs.findbugs.annotations.Nullable; 070import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 071 072public class ReferenceCountingVisitor 073 extends AbstractCatalogEntityVisitor<ReferenceCountingVisitor.Context, Void> 074 implements IReferenceVisitor<ReferenceCountingVisitor.Context> { 075 private static final Logger LOGGER = LogManager.getLogger(ReferenceCountingVisitor.class); 076 077 private static final ReferenceCountingVisitor SINGLETON = new ReferenceCountingVisitor(); 078 079 @NonNull 080 private static final MetapathExpression PARAM_MARKUP_METAPATH 081 = MetapathExpression 082 .compile("label|usage|constraint/(description|tests/remarks)|guideline/prose|select/choice|remarks"); 083 @NonNull 084 private static final MetapathExpression ROLE_MARKUP_METAPATH 085 = MetapathExpression.compile("title|description|remarks"); 086 @NonNull 087 private static final MetapathExpression LOCATION_MARKUP_METAPATH 088 = MetapathExpression.compile("title|remarks"); 089 @NonNull 090 private static final MetapathExpression PARTY_MARKUP_METAPATH 091 = MetapathExpression.compile("title|remarks"); 092 @NonNull 093 private static final MetapathExpression RESOURCE_MARKUP_METAPATH 094 = MetapathExpression.compile("title|description|remarks"); 095 096 @NonNull 097 private static final IReferencePolicy<Property> PROPERTY_POLICY_IGNORE = IReferencePolicy.ignore(); 098 @NonNull 099 private static final IReferencePolicy<Link> LINK_POLICY_IGNORE = IReferencePolicy.ignore(); 100 101 @NonNull 102 private static final Map<QName, IReferencePolicy<Property>> PROPERTY_POLICIES; 103 @NonNull 104 private static final Map<String, IReferencePolicy<Link>> LINK_POLICIES; 105 @NonNull 106 private static final InsertReferencePolicy INSERT_POLICY = new InsertReferencePolicy(); 107 @NonNull 108 private static final AnchorReferencePolicy ANCHOR_POLICY = new AnchorReferencePolicy(); 109 110 static { 111 PROPERTY_POLICIES = new HashMap<>(); 112 PROPERTY_POLICIES.put(AbstractProperty.qname(IProperty.OSCAL_NAMESPACE, "resolution-tool"), PROPERTY_POLICY_IGNORE); 113 PROPERTY_POLICIES.put(AbstractProperty.qname(IProperty.OSCAL_NAMESPACE, "label"), PROPERTY_POLICY_IGNORE); 114 PROPERTY_POLICIES.put(AbstractProperty.qname(IProperty.OSCAL_NAMESPACE, "sort-id"), PROPERTY_POLICY_IGNORE); 115 PROPERTY_POLICIES.put(AbstractProperty.qname(IProperty.OSCAL_NAMESPACE, "alt-label"), PROPERTY_POLICY_IGNORE); 116 PROPERTY_POLICIES.put(AbstractProperty.qname(IProperty.OSCAL_NAMESPACE, "alt-identifier"), PROPERTY_POLICY_IGNORE); 117 PROPERTY_POLICIES.put(AbstractProperty.qname(IProperty.OSCAL_NAMESPACE, "method"), PROPERTY_POLICY_IGNORE); 118 PROPERTY_POLICIES.put(AbstractProperty.qname(IProperty.OSCAL_NAMESPACE, "keep"), PROPERTY_POLICY_IGNORE); 119 PROPERTY_POLICIES.put(AbstractProperty.qname(IProperty.RMF_NAMESPACE, "method"), PROPERTY_POLICY_IGNORE); 120 PROPERTY_POLICIES.put(AbstractProperty.qname(IProperty.RMF_NAMESPACE, "aggregates"), 121 PropertyReferencePolicy.create(IIdentifierParser.IDENTITY_PARSER, IEntityItem.ItemType.PARAMETER)); 122 123 LINK_POLICIES = new HashMap<>(); 124 LINK_POLICIES.put("source-profile", LINK_POLICY_IGNORE); 125 LINK_POLICIES.put("citation", LinkReferencePolicy.create(IEntityItem.ItemType.RESOURCE)); 126 LINK_POLICIES.put("reference", LinkReferencePolicy.create(IEntityItem.ItemType.RESOURCE)); 127 LINK_POLICIES.put("related", LinkReferencePolicy.create(IEntityItem.ItemType.CONTROL)); 128 LINK_POLICIES.put("required", LinkReferencePolicy.create(IEntityItem.ItemType.CONTROL)); 129 LINK_POLICIES.put("corresp", LinkReferencePolicy.create(IEntityItem.ItemType.PART)); 130 } 131 132 public static ReferenceCountingVisitor instance() { 133 return SINGLETON; 134 } 135 136 public ReferenceCountingVisitor() { 137 // visit everything except parts, roles, locations, parties, parameters, and 138 // resources, which are 139 // handled differently by this visitor 140 super(ObjectUtils.notNull(EnumSet.complementOf( 141 EnumSet.of( 142 IEntityItem.ItemType.PART, 143 IEntityItem.ItemType.ROLE, 144 IEntityItem.ItemType.LOCATION, 145 IEntityItem.ItemType.PARTY, 146 IEntityItem.ItemType.PARAMETER, 147 IEntityItem.ItemType.RESOURCE)))); 148 } 149 150 @Override 151 protected Void newDefaultResult(Context context) { 152 // do nothing 153 return null; 154 } 155 156 @Override 157 protected Void aggregateResults(Void first, Void second, Context context) { 158 // do nothing 159 return null; 160 } 161 162 // 163 // public void visitProfile(@NonNull Profile profile) { 164 // // process children 165 // Metadata metadata = profile.getMetadata(); 166 // if (metadata != null) { 167 // visitMetadata(metadata); 168 // } 169 // 170 // BackMatter backMatter = profile.getBackMatter(); 171 // if (backMatter != null) { 172 // for (BackMatter.Resource resource : 173 // CollectionUtil.listOrEmpty(backMatter.getResources())) { 174 // visitResource(resource); 175 // } 176 // } 177 // } 178 179 public void visitCatalog(@NonNull IDocumentNodeItem catalogItem, @NonNull IIndexer indexer, @NonNull URI baseUri) { 180 Context context = new Context(indexer, baseUri); 181 visitCatalog(catalogItem, context); 182 183 IIndexer index = context.getIndexer(); 184 // resolve the entities picked up by the original indexing operation 185 // FIXME: Is this necessary? 186 IIndexer.getReferencedEntitiesAsStream(index.getEntitiesByItemType(IEntityItem.ItemType.ROLE)) 187 .forEachOrdered( 188 item -> resolveEntity(ObjectUtils.notNull(item), context, ReferenceCountingVisitor::resolveRole)); 189 IIndexer.getReferencedEntitiesAsStream(index.getEntitiesByItemType(IEntityItem.ItemType.LOCATION)) 190 .forEachOrdered( 191 item -> resolveEntity(ObjectUtils.notNull(item), context, 192 ReferenceCountingVisitor::resolveLocation)); 193 IIndexer.getReferencedEntitiesAsStream(index.getEntitiesByItemType(IEntityItem.ItemType.PARTY)) 194 .forEachOrdered( 195 item -> resolveEntity(ObjectUtils.notNull(item), context, 196 ReferenceCountingVisitor::resolveParty)); 197 IIndexer.getReferencedEntitiesAsStream(index.getEntitiesByItemType(IEntityItem.ItemType.PARAMETER)) 198 .forEachOrdered( 199 item -> resolveEntity(ObjectUtils.notNull(item), context, 200 ReferenceCountingVisitor::resolveParameter)); 201 IIndexer.getReferencedEntitiesAsStream(index.getEntitiesByItemType(IEntityItem.ItemType.RESOURCE)) 202 .forEachOrdered( 203 item -> resolveEntity(ObjectUtils.notNull(item), context, 204 ReferenceCountingVisitor::resolveResource)); 205 } 206 207 @Override 208 public Void visitGroup(@NonNull IRequiredValueModelNodeItem item, Void childResult, Context context) { 209 IIndexer index = context.getIndexer(); 210 // handle the group if it is selected 211 // a group will only be selected if it contains a descendant control that is 212 // selected 213 if (IIndexer.SelectionStatus.SELECTED.equals(index.getSelectionStatus(item))) { 214 CatalogGroup group = (CatalogGroup) item.getValue(); 215 String id = group.getId(); 216 217 boolean resolve; 218 if (id == null) { 219 // always resolve a group without an identifier 220 resolve = true; 221 } else { 222 IEntityItem entity = index.getEntity(IEntityItem.ItemType.GROUP, id, false); 223 if (entity != null && !context.isResolved(entity)) { 224 // only resolve if not already resolved 225 context.markResolved(entity); 226 resolve = true; 227 } else { 228 resolve = false; 229 } 230 } 231 232 // resolve only if requested 233 if (resolve) { 234 resolveGroup(item, context); 235 } 236 } 237 return null; 238 } 239 240 @Override 241 public Void visitControl(@NonNull IRequiredValueModelNodeItem item, Void childResult, Context context) { 242 IIndexer index = context.getIndexer(); 243 // handle the control if it is selected 244 if (IIndexer.SelectionStatus.SELECTED.equals(index.getSelectionStatus(item))) { 245 Control control = (Control) item.getValue(); 246 IEntityItem entity 247 = context.getIndexer().getEntity(IEntityItem.ItemType.CONTROL, ObjectUtils.notNull(control.getId()), false); 248 249 // the control must always appear in the index 250 assert entity != null; 251 252 if (!context.isResolved(entity)) { 253 context.markResolved(entity); 254 if (IIndexer.SelectionStatus.SELECTED.equals(context.getIndexer().getSelectionStatus(item))) { 255 resolveControl(item, context); 256 } 257 } 258 } 259 return null; 260 } 261 262 @Override 263 protected void visitParts(@NonNull IRequiredValueModelNodeItem groupOrControlItem, Context context) { 264 // visits all descendant parts 265 CHILD_PART_METAPATH.evaluate(groupOrControlItem).asStream() 266 .map(item -> (IRequiredValueModelNodeItem) item) 267 .forEachOrdered(partItem -> { 268 visitPart(ObjectUtils.notNull(partItem), groupOrControlItem, context); 269 }); 270 } 271 272 @Override 273 protected void visitPart(IRequiredValueModelNodeItem item, IRequiredValueModelNodeItem groupOrControlItem, 274 Context context) { 275 assert context != null; 276 277 ControlPart part = (ControlPart) item.getValue(); 278 String id = part.getId(); 279 280 boolean resolve; 281 if (id == null) { 282 // always resolve a part without an identifier 283 resolve = true; 284 } else { 285 IEntityItem entity = context.getIndexer().getEntity(IEntityItem.ItemType.PART, id, false); 286 if (entity != null && !context.isResolved(entity)) { 287 // only resolve if not already resolved 288 context.markResolved(entity); 289 resolve = true; 290 } else { 291 resolve = false; 292 } 293 } 294 295 if (resolve) { 296 resolvePart(item, context); 297 } 298 } 299 300 protected void resolveGroup( 301 @NonNull IRequiredValueModelNodeItem item, 302 @NonNull Context context) { 303 if (IIndexer.SelectionStatus.SELECTED.equals(context.getIndexer().getSelectionStatus(item))) { 304 305 // process children 306 item.getModelItemsByName("title").forEach(child -> handleMarkup(ObjectUtils.notNull(child), context)); 307 item.getModelItemsByName("prop").forEach(child -> handleProperty(ObjectUtils.notNull(child), context)); 308 item.getModelItemsByName("link").forEach(child -> handleLink(ObjectUtils.notNull(child), context)); 309 310 // always visit parts 311 visitParts(item, context); 312 313 // skip parameters for now. These will be processed by a separate pass. 314 } 315 } 316 317 protected void resolveControl( 318 @NonNull IRequiredValueModelNodeItem item, 319 @NonNull Context context) { 320 // process non-control, non-param children 321 item.getModelItemsByName("title").forEach(child -> handleMarkup(ObjectUtils.notNull(child), context)); 322 item.getModelItemsByName("prop").forEach(child -> handleProperty(ObjectUtils.notNull(child), context)); 323 item.getModelItemsByName("link").forEach(child -> handleLink(ObjectUtils.notNull(child), context)); 324 325 // always visit parts 326 visitParts(item, context); 327 328 // skip parameters for now. These will be processed by a separate pass. 329 } 330 331 private static void resolveRole(@NonNull IEntityItem entity, @NonNull Context context) { 332 IRequiredValueModelNodeItem item = entity.getInstance(); 333 item.getModelItemsByName("prop").forEach(child -> handleProperty(ObjectUtils.notNull(child), context)); 334 item.getModelItemsByName("link").forEach(child -> handleLink(ObjectUtils.notNull(child), context)); 335 ROLE_MARKUP_METAPATH.evaluate(item).asList() 336 .forEach(child -> handleMarkup(ObjectUtils.notNull((IRequiredValueModelNodeItem) child), context)); 337 } 338 339 private static void resolveParty(@NonNull IEntityItem entity, @NonNull Context context) { 340 IRequiredValueModelNodeItem item = entity.getInstance(); 341 item.getModelItemsByName("prop").forEach(child -> handleProperty(ObjectUtils.notNull(child), context)); 342 item.getModelItemsByName("link").forEach(child -> handleLink(ObjectUtils.notNull(child), context)); 343 PARTY_MARKUP_METAPATH.evaluate(item).asList() 344 .forEach(child -> handleMarkup(ObjectUtils.notNull((IRequiredValueModelNodeItem) child), context)); 345 } 346 347 public static void resolveLocation(@NonNull IEntityItem entity, @NonNull Context context) { 348 IRequiredValueModelNodeItem item = entity.getInstance(); 349 item.getModelItemsByName("prop").forEach(child -> handleProperty(ObjectUtils.notNull(child), context)); 350 item.getModelItemsByName("link").forEach(child -> handleLink(ObjectUtils.notNull(child), context)); 351 LOCATION_MARKUP_METAPATH.evaluate(item).asList() 352 .forEach(child -> handleMarkup(ObjectUtils.notNull((IRequiredValueModelNodeItem) child), context)); 353 } 354 355 public static void resolveResource(@NonNull IEntityItem entity, @NonNull Context context) { 356 IRequiredValueModelNodeItem item = entity.getInstance(); 357 358 item.getModelItemsByName("prop").forEach(child -> handleProperty(ObjectUtils.notNull(child), context)); 359 360 item.getModelItemsByName("citation").forEach(child -> { 361 if (child != null) { 362 child.getModelItemsByName("text") 363 .forEach(citationChild -> handleMarkup(ObjectUtils.notNull(citationChild), context)); 364 child.getModelItemsByName("prop") 365 .forEach(citationChild -> handleProperty(ObjectUtils.notNull(citationChild), context)); 366 child.getModelItemsByName("link") 367 .forEach(citationChild -> handleLink(ObjectUtils.notNull(citationChild), context)); 368 } 369 }); 370 371 RESOURCE_MARKUP_METAPATH.evaluate(item).asList() 372 .forEach(child -> handleMarkup(ObjectUtils.notNull((IRequiredValueModelNodeItem) child), context)); 373 } 374 375 public static void resolveParameter(@NonNull IEntityItem entity, @NonNull Context context) { 376 IRequiredValueModelNodeItem item = entity.getInstance(); 377 378 item.getModelItemsByName("prop").forEach(child -> handleProperty(ObjectUtils.notNull(child), context)); 379 item.getModelItemsByName("link").forEach(child -> handleLink(ObjectUtils.notNull(child), context)); 380 PARAM_MARKUP_METAPATH.evaluate(item).asList() 381 .forEach(child -> handleMarkup(ObjectUtils.notNull((IRequiredValueModelNodeItem) child), context)); 382 } 383 384 private static void resolvePart( 385 @NonNull IRequiredValueModelNodeItem item, 386 @NonNull Context context) { 387 item.getModelItemsByName("title").forEach(child -> handleMarkup(ObjectUtils.notNull(child), context)); 388 item.getModelItemsByName("prop").forEach(child -> handleProperty(ObjectUtils.notNull(child), context)); 389 item.getModelItemsByName("link").forEach(child -> handleLink(ObjectUtils.notNull(child), context)); 390 item.getModelItemsByName("prose").forEach(child -> handleMarkup(ObjectUtils.notNull(child), context)); 391 // item.getModelItemsByName("part").forEach(child -> 392 // visitor.visitPart(ObjectUtils.notNull(child), 393 // context)); 394 } 395 396 private static void handleMarkup( 397 @NonNull IRequiredValueModelNodeItem item, 398 @NonNull Context context) { 399 IMarkupItem markupItem = (IMarkupItem) FnData.fnDataItem(item); 400 IMarkupString<?> markup = markupItem.getValue(); 401 handleMarkup(item, markup, context); 402 } 403 404 private static void handleMarkup( 405 @NonNull IRequiredValueModelNodeItem contextItem, 406 @NonNull IMarkupString<?> text, 407 @NonNull Context context) { 408 for (Node node : CollectionUtil.toIterable(text.getNodesAsStream().iterator())) { 409 if (node instanceof InsertAnchorNode) { 410 handleInsert(contextItem, (InsertAnchorNode) node, context); 411 } else if (node instanceof InlineLinkNode) { 412 handleAnchor(contextItem, (InlineLinkNode) node, context); 413 } 414 } 415 } 416 417 private static void handleInsert( 418 @NonNull IRequiredValueModelNodeItem contextItem, 419 @NonNull InsertAnchorNode node, 420 @NonNull Context context) { 421 boolean retval = INSERT_POLICY.handleReference(contextItem, node, context); 422 if (LOGGER.isWarnEnabled() && !retval) { 423 LOGGER.atWarn().log("Unsupported insert type '{}' at '{}'", 424 node.getType().toString(), 425 contextItem.toPath(IPathFormatter.METAPATH_PATH_FORMATER)); 426 } 427 } 428 429 private static void handleAnchor( 430 @NonNull IRequiredValueModelNodeItem contextItem, 431 @NonNull InlineLinkNode node, 432 @NonNull Context context) { 433 boolean result = ANCHOR_POLICY.handleReference(contextItem, node, context); 434 if (LOGGER.isWarnEnabled() && !result) { 435 LOGGER.atWarn().log("Unsupported anchor with href '{}' at '{}'", 436 node.getUrl().toString(), 437 contextItem.toPath(IPathFormatter.METAPATH_PATH_FORMATER)); 438 } 439 } 440 441 private static void handleProperty( 442 @NonNull IRequiredValueModelNodeItem item, 443 @NonNull Context context) { 444 Property property = (Property) item.getValue(); 445 QName qname = property.getQName(); 446 447 IReferencePolicy<Property> policy = PROPERTY_POLICIES.get(qname); 448 449 boolean result = policy != null && policy.handleReference(item, property, context); 450 if (LOGGER.isWarnEnabled() && !result) { 451 LOGGER.atWarn().log("Unsupported property '{}' at '{}'", 452 property.getQName(), 453 item.toPath(IPathFormatter.METAPATH_PATH_FORMATER)); 454 } 455 } 456 457 private static void handleLink( 458 @NonNull IRequiredValueModelNodeItem item, 459 @NonNull Context context) { 460 Link link = (Link) item.getValue(); 461 IReferencePolicy<Link> policy = null; 462 String rel = link.getRel(); 463 if (rel != null) { 464 policy = LINK_POLICIES.get(rel); 465 } 466 467 boolean result = policy != null && policy.handleReference(item, link, context); 468 if (LOGGER.isWarnEnabled() && !result) { 469 LOGGER.atWarn().log("unsupported link rel '{}' at '{}'", 470 link.getRel(), 471 item.toPath(IPathFormatter.METAPATH_PATH_FORMATER)); 472 } 473 } 474 475 protected void resolveEntity( 476 @NonNull IEntityItem entity, 477 @NonNull Context context, 478 @NonNull BiConsumer<IEntityItem, Context> handler) { 479 480 if (!context.isResolved(entity)) { 481 context.markResolved(entity); 482 483 if (LOGGER.isDebugEnabled()) { 484 LOGGER.atDebug().log("Resolving {} identified as '{}'", 485 entity.getItemType().name(), 486 entity.getIdentifier()); 487 } 488 489 if (!IIndexer.SelectionStatus.UNSELECTED 490 .equals(context.getIndexer().getSelectionStatus(entity.getInstance()))) { 491 // only resolve selected and unknown entities 492 handler.accept(entity, context); 493 } 494 } 495 } 496 497 public void resolveEntity( 498 @NonNull IEntityItem entity, 499 @NonNull Context context) { 500 resolveEntity(entity, context, (theEntity, theContext) -> entityDispatch( 501 ObjectUtils.notNull(theEntity), 502 ObjectUtils.notNull(theContext))); 503 } 504 505 protected void entityDispatch(@NonNull IEntityItem entity, @NonNull Context context) { 506 IRequiredValueModelNodeItem item = entity.getInstance(); 507 switch (entity.getItemType()) { 508 case CONTROL: 509 resolveControl(item, context); 510 break; 511 case GROUP: 512 resolveGroup(item, context); 513 break; 514 case LOCATION: 515 resolveLocation(entity, context); 516 break; 517 case PARAMETER: 518 resolveParameter(entity, context); 519 break; 520 case PART: 521 resolvePart(item, context); 522 break; 523 case PARTY: 524 resolveParty(entity, context); 525 break; 526 case RESOURCE: 527 resolveResource(entity, context); 528 break; 529 case ROLE: 530 resolveRole(entity, context); 531 break; 532 default: 533 throw new UnsupportedOperationException(entity.getItemType().name()); 534 } 535 } 536 // 537 // @Override 538 // protected Void newDefaultResult(Object context) { 539 // return null; 540 // } 541 // 542 // @Override 543 // protected Void aggregateResults(Object first, Object second, Object context) 544 // { 545 // return null; 546 // } 547 548 public static final class Context { 549 @NonNull 550 private final IIndexer indexer; 551 @NonNull 552 private final URI source; 553 @NonNull 554 private final Set<IEntityItem> resolvedEntities = new HashSet<>(); 555 556 private Context(@NonNull IIndexer indexer, @NonNull URI source) { 557 this.indexer = indexer; 558 this.source = source; 559 } 560 561 @NonNull 562 @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "intending to expose this field") 563 public IIndexer getIndexer() { 564 return indexer; 565 } 566 567 @Nullable 568 public IEntityItem getEntity(@NonNull IEntityItem.ItemType itemType, @NonNull String identifier) { 569 return getIndexer().getEntity(itemType, identifier); 570 } 571 572 @SuppressWarnings("unused") 573 @NonNull 574 private URI getSource() { 575 return source; 576 } 577 578 public void markResolved(@NonNull IEntityItem entity) { 579 resolvedEntities.add(entity); 580 } 581 582 public boolean isResolved(@NonNull IEntityItem entity) { 583 return resolvedEntities.contains(entity); 584 } 585 586 public void incrementReferenceCount( 587 @NonNull IRequiredValueModelNodeItem contextItem, 588 @NonNull IEntityItem.ItemType type, 589 @NonNull UUID identifier) { 590 incrementReferenceCountInternal( 591 contextItem, 592 type, 593 ObjectUtils.notNull(identifier.toString()), 594 false); 595 } 596 597 public void incrementReferenceCount( 598 @NonNull IRequiredValueModelNodeItem contextItem, 599 @NonNull IEntityItem.ItemType type, 600 @NonNull String identifier) { 601 incrementReferenceCountInternal( 602 contextItem, 603 type, 604 identifier, 605 type.isUuid()); 606 } 607 608 private void incrementReferenceCountInternal( 609 @NonNull IRequiredValueModelNodeItem contextItem, 610 @NonNull IEntityItem.ItemType type, 611 @NonNull String identifier, 612 boolean normalize) { 613 IEntityItem item = getIndexer().getEntity(type, identifier, normalize); 614 if (item == null) { 615 if (LOGGER.isErrorEnabled()) { 616 LOGGER.atError().log("Unknown reference to {} '{}' at '{}'", 617 type.toString().toLowerCase(Locale.ROOT), 618 identifier, 619 contextItem.toPath(IPathFormatter.METAPATH_PATH_FORMATER)); 620 } 621 } else { 622 item.incrementReferenceCount(); 623 } 624 } 625 } 626}