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