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