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