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.IMetapathExpression; 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.ProfileResolver.UriResolver; 034import gov.nist.secauto.oscal.lib.profile.resolver.support.AbstractCatalogEntityVisitor; 035import gov.nist.secauto.oscal.lib.profile.resolver.support.IEntityItem; 036import gov.nist.secauto.oscal.lib.profile.resolver.support.IIndexer; 037 038import org.apache.logging.log4j.LogManager; 039import org.apache.logging.log4j.Logger; 040 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 IMetapathExpression PARAM_MARKUP_METAPATH 063 = IMetapathExpression 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 IMetapathExpression ROLE_MARKUP_METAPATH 069 = IMetapathExpression.compile("title|description|remarks", 070 OscalBindingContext.OSCAL_STATIC_METAPATH_CONTEXT); 071 @NonNull 072 private static final IMetapathExpression LOCATION_MARKUP_METAPATH 073 = IMetapathExpression.compile("title|remarks", 074 OscalBindingContext.OSCAL_STATIC_METAPATH_CONTEXT); 075 @NonNull 076 private static final IMetapathExpression PARTY_MARKUP_METAPATH 077 = IMetapathExpression.compile("title|remarks", 078 OscalBindingContext.OSCAL_STATIC_METAPATH_CONTEXT); 079 @NonNull 080 private static final IMetapathExpression RESOURCE_MARKUP_METAPATH 081 = IMetapathExpression.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( 169 @NonNull IDocumentNodeItem catalogItem, 170 @NonNull IIndexer indexer, 171 @NonNull UriResolver resolver) { 172 Context context = new Context(indexer, resolver); 173 visitCatalog(catalogItem, context); 174 175 IIndexer index = context.getIndexer(); 176 // resolve the entities picked up by the original indexing operation 177 // FIXME: Is this necessary? 178 IIndexer.getReferencedEntitiesAsStream(index.getEntitiesByItemType(IEntityItem.ItemType.ROLE)) 179 .forEachOrdered( 180 item -> resolveEntity(ObjectUtils.notNull(item), context, ReferenceCountingVisitor::resolveRole)); 181 IIndexer.getReferencedEntitiesAsStream(index.getEntitiesByItemType(IEntityItem.ItemType.LOCATION)) 182 .forEachOrdered( 183 item -> resolveEntity(ObjectUtils.notNull(item), context, 184 ReferenceCountingVisitor::resolveLocation)); 185 IIndexer.getReferencedEntitiesAsStream(index.getEntitiesByItemType(IEntityItem.ItemType.PARTY)) 186 .forEachOrdered( 187 item -> resolveEntity(ObjectUtils.notNull(item), context, 188 ReferenceCountingVisitor::resolveParty)); 189 IIndexer.getReferencedEntitiesAsStream(index.getEntitiesByItemType(IEntityItem.ItemType.PARAMETER)) 190 .forEachOrdered( 191 item -> resolveEntity(ObjectUtils.notNull(item), context, 192 ReferenceCountingVisitor::resolveParameter)); 193 IIndexer.getReferencedEntitiesAsStream(index.getEntitiesByItemType(IEntityItem.ItemType.RESOURCE)) 194 .forEachOrdered( 195 item -> resolveEntity(ObjectUtils.notNull(item), context, 196 ReferenceCountingVisitor::resolveResource)); 197 } 198 199 @Override 200 public Void visitGroup( 201 IAssemblyNodeItem item, 202 Void childResult, 203 Context context) { 204 IIndexer index = context.getIndexer(); 205 // handle the group if it is selected 206 // a group will only be selected if it contains a descendant control that is 207 // selected 208 if (IIndexer.SelectionStatus.SELECTED.equals(index.getSelectionStatus(item))) { 209 CatalogGroup group = ObjectUtils.requireNonNull((CatalogGroup) item.getValue()); 210 String id = group.getId(); 211 212 boolean resolve; 213 if (id == null) { 214 // always resolve a group without an identifier 215 resolve = true; 216 } else { 217 IEntityItem entity = index.getEntity(IEntityItem.ItemType.GROUP, id, false); 218 if (entity != null && !context.isResolved(entity)) { 219 // only resolve if not already resolved 220 context.markResolved(entity); 221 resolve = true; 222 } else { 223 resolve = false; 224 } 225 } 226 227 // resolve only if requested 228 if (resolve) { 229 resolveGroup(item, context); 230 } 231 } 232 return null; 233 } 234 235 @Override 236 public Void visitControl( 237 IAssemblyNodeItem item, 238 Void childResult, 239 Context context) { 240 IIndexer index = context.getIndexer(); 241 // handle the control if it is selected 242 if (IIndexer.SelectionStatus.SELECTED.equals(index.getSelectionStatus(item))) { 243 Control control = ObjectUtils.requireNonNull((Control) item.getValue()); 244 IEntityItem entity 245 = context.getIndexer().getEntity(IEntityItem.ItemType.CONTROL, ObjectUtils.notNull(control.getId()), false); 246 247 // the control must always appear in the index 248 assert entity != null; 249 250 if (!context.isResolved(entity)) { 251 context.markResolved(entity); 252 if (IIndexer.SelectionStatus.SELECTED.equals(context.getIndexer().getSelectionStatus(item))) { 253 resolveControl(item, context); 254 } 255 } 256 } 257 return null; 258 } 259 260 @Override 261 protected void visitParts( 262 IAssemblyNodeItem groupOrControlItem, 263 Context context) { 264 // visits all descendant parts 265 CHILD_PART_METAPATH.evaluate(groupOrControlItem).stream() 266 .map(item -> (IAssemblyNodeItem) item) 267 .forEachOrdered(partItem -> { 268 visitPart(ObjectUtils.notNull(partItem), groupOrControlItem, context); 269 }); 270 } 271 272 @Override 273 protected void visitPart( 274 IAssemblyNodeItem item, 275 IAssemblyNodeItem groupOrControlItem, 276 Context context) { 277 assert context != null; 278 279 ControlPart part = ObjectUtils.requireNonNull((ControlPart) item.getValue()); 280 String id = part.getId(); 281 282 boolean resolve; 283 if (id == null) { 284 // always resolve a part without an identifier 285 resolve = true; 286 } else { 287 IEntityItem entity = context.getIndexer().getEntity(IEntityItem.ItemType.PART, id, false); 288 if (entity != null && !context.isResolved(entity)) { 289 // only resolve if not already resolved 290 context.markResolved(entity); 291 resolve = true; 292 } else { 293 resolve = false; 294 } 295 } 296 297 if (resolve) { 298 resolvePart(item, context); 299 } 300 } 301 302 protected void resolveGroup( 303 @NonNull IAssemblyNodeItem item, 304 @NonNull Context context) { 305 if (IIndexer.SelectionStatus.SELECTED.equals(context.getIndexer().getSelectionStatus(item))) { 306 307 // process children 308 item.getModelItemsByName(OscalModelConstants.QNAME_TITLE) 309 .forEach(child -> handleMarkup(ObjectUtils.notNull((IFieldNodeItem) child), context)); 310 item.getModelItemsByName(OscalModelConstants.QNAME_PROP) 311 .forEach(child -> handleProperty(ObjectUtils.notNull((IAssemblyNodeItem) child), context)); 312 item.getModelItemsByName(OscalModelConstants.QNAME_LINK) 313 .forEach(child -> handleLink(ObjectUtils.notNull((IAssemblyNodeItem) child), context)); 314 315 // always visit parts 316 visitParts(item, context); 317 318 // skip parameters for now. These will be processed by a separate pass. 319 } 320 } 321 322 protected void resolveControl( 323 @NonNull IAssemblyNodeItem item, 324 @NonNull Context context) { 325 // process non-control, non-param children 326 item.getModelItemsByName(OscalModelConstants.QNAME_TITLE) 327 .forEach(child -> handleMarkup(ObjectUtils.notNull((IFieldNodeItem) child), context)); 328 item.getModelItemsByName(OscalModelConstants.QNAME_PROP) 329 .forEach(child -> handleProperty(ObjectUtils.notNull((IAssemblyNodeItem) child), context)); 330 item.getModelItemsByName(OscalModelConstants.QNAME_LINK) 331 .forEach(child -> handleLink(ObjectUtils.notNull((IAssemblyNodeItem) child), context)); 332 333 // always visit parts 334 visitParts(item, context); 335 336 // skip parameters for now. These will be processed by a separate pass. 337 } 338 339 private static void resolveRole(@NonNull IEntityItem entity, @NonNull Context context) { 340 IModelNodeItem<?, ?> item = entity.getInstance(); 341 item.getModelItemsByName(OscalModelConstants.QNAME_PROP) 342 .forEach(child -> handleProperty(ObjectUtils.notNull((IAssemblyNodeItem) child), context)); 343 item.getModelItemsByName(OscalModelConstants.QNAME_LINK) 344 .forEach(child -> handleLink(ObjectUtils.notNull((IAssemblyNodeItem) child), context)); 345 ROLE_MARKUP_METAPATH.evaluate(item) 346 .forEach(child -> handleMarkup(ObjectUtils.notNull((IFieldNodeItem) child), context)); 347 } 348 349 private static void resolveParty(@NonNull IEntityItem entity, @NonNull Context context) { 350 IModelNodeItem<?, ?> item = entity.getInstance(); 351 item.getModelItemsByName(OscalModelConstants.QNAME_PROP) 352 .forEach(child -> handleProperty(ObjectUtils.notNull((IAssemblyNodeItem) child), context)); 353 item.getModelItemsByName(OscalModelConstants.QNAME_LINK) 354 .forEach(child -> handleLink(ObjectUtils.notNull((IAssemblyNodeItem) child), context)); 355 PARTY_MARKUP_METAPATH.evaluate(item) 356 .forEach(child -> handleMarkup(ObjectUtils.notNull((IFieldNodeItem) child), context)); 357 } 358 359 public static void resolveLocation(@NonNull IEntityItem entity, @NonNull Context context) { 360 IModelNodeItem<?, ?> item = entity.getInstance(); 361 item.getModelItemsByName(OscalModelConstants.QNAME_PROP) 362 .forEach(child -> handleProperty(ObjectUtils.notNull((IAssemblyNodeItem) child), context)); 363 item.getModelItemsByName(OscalModelConstants.QNAME_LINK) 364 .forEach(child -> handleLink(ObjectUtils.notNull((IAssemblyNodeItem) child), context)); 365 LOCATION_MARKUP_METAPATH.evaluate(item) 366 .forEach(child -> handleMarkup(ObjectUtils.notNull((IFieldNodeItem) child), context)); 367 } 368 369 public static void resolveResource(@NonNull IEntityItem entity, @NonNull Context context) { 370 IModelNodeItem<?, ?> item = entity.getInstance(); 371 372 item.getModelItemsByName(OscalModelConstants.QNAME_PROP) 373 .forEach(child -> handleProperty(ObjectUtils.notNull((IAssemblyNodeItem) child), context)); 374 375 item.getModelItemsByName(OscalModelConstants.QNAME_CITATION).forEach(child -> { 376 if (child != null) { 377 child.getModelItemsByName(OscalModelConstants.QNAME_TEXT) 378 .forEach(citationChild -> handleMarkup(ObjectUtils.notNull((IFieldNodeItem) citationChild), context)); 379 child.getModelItemsByName(OscalModelConstants.QNAME_PROP) 380 .forEach(citationChild -> handleProperty(ObjectUtils.notNull((IAssemblyNodeItem) citationChild), context)); 381 child.getModelItemsByName(OscalModelConstants.QNAME_LINK) 382 .forEach(citationChild -> handleLink(ObjectUtils.notNull((IAssemblyNodeItem) citationChild), context)); 383 } 384 }); 385 386 RESOURCE_MARKUP_METAPATH.evaluate(item) 387 .forEach(child -> handleMarkup(ObjectUtils.notNull((IFieldNodeItem) child), context)); 388 } 389 390 public static void resolveParameter(@NonNull IEntityItem entity, @NonNull Context context) { 391 IModelNodeItem<?, ?> item = entity.getInstance(); 392 393 item.getModelItemsByName(OscalModelConstants.QNAME_PROP) 394 .forEach(child -> handleProperty(ObjectUtils.notNull((IAssemblyNodeItem) child), context)); 395 item.getModelItemsByName(OscalModelConstants.QNAME_LINK) 396 .forEach(child -> handleLink(ObjectUtils.notNull((IAssemblyNodeItem) child), context)); 397 PARAM_MARKUP_METAPATH.evaluate(item) 398 .forEach(child -> handleMarkup(ObjectUtils.notNull((IFieldNodeItem) child), context)); 399 } 400 401 private static void resolvePart( 402 @NonNull IAssemblyNodeItem item, 403 @NonNull Context context) { 404 item.getModelItemsByName(OscalModelConstants.QNAME_TITLE) 405 .forEach(child -> handleMarkup(ObjectUtils.notNull((IFieldNodeItem) child), context)); 406 item.getModelItemsByName(OscalModelConstants.QNAME_PROP) 407 .forEach(child -> handleProperty(ObjectUtils.notNull((IAssemblyNodeItem) child), context)); 408 item.getModelItemsByName(OscalModelConstants.QNAME_LINK) 409 .forEach(child -> handleLink(ObjectUtils.notNull((IAssemblyNodeItem) child), context)); 410 item.getModelItemsByName(OscalModelConstants.QNAME_PROSE) 411 .forEach(child -> handleMarkup(ObjectUtils.notNull((IFieldNodeItem) child), context)); 412 // item.getModelItemsByName("part").forEach(child -> 413 // visitor.visitPart(ObjectUtils.notNull(child), 414 // context)); 415 } 416 417 private static void handleMarkup( 418 @NonNull IFieldNodeItem item, 419 @NonNull Context context) { 420 IMarkupItem markupItem = (IMarkupItem) item.toAtomicItem(); 421 IMarkupString<?> markup = markupItem.asMarkup(); 422 handleMarkup(item, markup, context); 423 } 424 425 private static void handleMarkup( 426 @NonNull IFieldNodeItem contextItem, 427 @NonNull IMarkupString<?> text, 428 @NonNull Context context) { 429 for (Node node : CollectionUtil.toIterable( 430 ObjectUtils.notNull(text.getNodesAsStream().iterator()))) { 431 if (node instanceof InsertAnchorExtension.InsertAnchorNode) { 432 handleInsert(contextItem, (InsertAnchorNode) node, context); 433 } else if (node instanceof InlineLinkNode) { 434 handleAnchor(contextItem, (InlineLinkNode) node, context); 435 } 436 } 437 } 438 439 private static void handleInsert( 440 @NonNull IFieldNodeItem contextItem, 441 @NonNull InsertAnchorExtension.InsertAnchorNode node, 442 @NonNull Context context) { 443 boolean retval = INSERT_POLICY.handleReference(contextItem, node, context); 444 if (LOGGER.isDebugEnabled() && !retval) { 445 LOGGER.atDebug().log("Unsupported insert type '{}' at '{}'", 446 node.getType().toString(), 447 contextItem.toPath(IPathFormatter.METAPATH_PATH_FORMATER)); 448 } 449 } 450 451 private static void handleAnchor( 452 @NonNull IFieldNodeItem contextItem, 453 @NonNull InlineLinkNode node, 454 @NonNull Context context) { 455 boolean result = ANCHOR_POLICY.handleReference(contextItem, node, context); 456 if (LOGGER.isDebugEnabled() && !result) { 457 LOGGER.atDebug().log("Unsupported anchor with href '{}' at '{}'", 458 node.getUrl().toString(), 459 contextItem.toPath(IPathFormatter.METAPATH_PATH_FORMATER)); 460 } 461 } 462 463 private static void handleProperty( 464 @NonNull IAssemblyNodeItem item, 465 @NonNull Context context) { 466 Property property = ObjectUtils.requireNonNull((Property) item.getValue()); 467 IEnhancedQName qname = property.getQName(); 468 469 IReferencePolicy<Property> policy = PROPERTY_POLICIES.get(qname); 470 471 boolean result = policy != null && policy.handleReference(item, property, context); 472 if (LOGGER.isDebugEnabled() && !result) { 473 LOGGER.atDebug().log("Unsupported property '{}' at '{}'", 474 property.getQName(), 475 item.toPath(IPathFormatter.METAPATH_PATH_FORMATER)); 476 } 477 } 478 479 private static void handleLink( 480 @NonNull IAssemblyNodeItem item, 481 @NonNull Context context) { 482 Link link = ObjectUtils.requireNonNull((Link) item.getValue()); 483 IReferencePolicy<Link> policy = null; 484 String rel = link.getRel(); 485 if (rel != null) { 486 policy = LINK_POLICIES.get(rel); 487 } 488 489 boolean result = policy != null && policy.handleReference(item, link, context); 490 if (LOGGER.isDebugEnabled() && !result) { 491 LOGGER.atDebug().log("unsupported link rel '{}' at '{}'", 492 link.getRel(), 493 item.toPath(IPathFormatter.METAPATH_PATH_FORMATER)); 494 } 495 } 496 497 protected static void resolveEntity( 498 @NonNull IEntityItem entity, 499 @NonNull Context context, 500 @NonNull BiConsumer<IEntityItem, Context> handler) { 501 502 if (!context.isResolved(entity)) { 503 context.markResolved(entity); 504 505 if (LOGGER.isDebugEnabled()) { 506 LOGGER.atDebug().log("Resolving {} identified as '{}'", 507 entity.getItemType().name(), 508 entity.getIdentifier()); 509 } 510 511 if (!IIndexer.SelectionStatus.UNSELECTED 512 .equals(context.getIndexer().getSelectionStatus(entity.getInstance()))) { 513 // only resolve selected and unknown entities 514 handler.accept(entity, context); 515 } 516 } 517 } 518 519 public void resolveEntity( 520 @NonNull IEntityItem entity, 521 @NonNull Context context) { 522 resolveEntity(entity, context, (theEntity, theContext) -> entityDispatch( 523 ObjectUtils.notNull(theEntity), 524 ObjectUtils.notNull(theContext))); 525 } 526 527 protected void entityDispatch(@NonNull IEntityItem entity, @NonNull Context context) { 528 IAssemblyNodeItem item = (IAssemblyNodeItem) entity.getInstance(); 529 switch (entity.getItemType()) { 530 case CONTROL: 531 resolveControl(item, context); 532 break; 533 case GROUP: 534 resolveGroup(item, context); 535 break; 536 case LOCATION: 537 resolveLocation(entity, context); 538 break; 539 case PARAMETER: 540 resolveParameter(entity, context); 541 break; 542 case PART: 543 resolvePart(item, context); 544 break; 545 case PARTY: 546 resolveParty(entity, context); 547 break; 548 case RESOURCE: 549 resolveResource(entity, context); 550 break; 551 case ROLE: 552 resolveRole(entity, context); 553 break; 554 default: 555 throw new UnsupportedOperationException(entity.getItemType().name()); 556 } 557 } 558 // 559 // @Override 560 // protected Void newDefaultResult(Object context) { 561 // return null; 562 // } 563 // 564 // @Override 565 // protected Void aggregateResults(Object first, Object second, Object context) 566 // { 567 // return null; 568 // } 569 570 public static final class Context { 571 @NonNull 572 private final IIndexer indexer; 573 @NonNull 574 private final UriResolver resolver; 575 @NonNull 576 private final Set<IEntityItem> resolvedEntities = new HashSet<>(); 577 578 private Context(@NonNull IIndexer indexer, @NonNull UriResolver resolver) { 579 this.indexer = indexer; 580 this.resolver = resolver; 581 } 582 583 @NonNull 584 @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "intending to expose this field") 585 public IIndexer getIndexer() { 586 return indexer; 587 } 588 589 @NonNull 590 public UriResolver getUriResolver() { 591 return resolver; 592 } 593 594 @Nullable 595 public IEntityItem getEntity(@NonNull IEntityItem.ItemType itemType, @NonNull String identifier) { 596 return getIndexer().getEntity(itemType, identifier); 597 } 598 599 public void markResolved(@NonNull IEntityItem entity) { 600 resolvedEntities.add(entity); 601 } 602 603 public boolean isResolved(@NonNull IEntityItem entity) { 604 return resolvedEntities.contains(entity); 605 } 606 607 public void incrementReferenceCount( 608 @NonNull IModelNodeItem<?, ?> contextItem, 609 @NonNull IEntityItem.ItemType type, 610 @NonNull UUID identifier) { 611 incrementReferenceCountInternal( 612 contextItem, 613 type, 614 ObjectUtils.notNull(identifier.toString()), 615 false); 616 } 617 618 public void incrementReferenceCount( 619 @NonNull IModelNodeItem<?, ?> contextItem, 620 @NonNull IEntityItem.ItemType type, 621 @NonNull String identifier) { 622 incrementReferenceCountInternal( 623 contextItem, 624 type, 625 identifier, 626 type.isUuid()); 627 } 628 629 private void incrementReferenceCountInternal( 630 @NonNull IModelNodeItem<?, ?> contextItem, 631 @NonNull IEntityItem.ItemType type, 632 @NonNull String identifier, 633 boolean normalize) { 634 IEntityItem item = getIndexer().getEntity(type, identifier, normalize); 635 if (item == null) { 636 if (LOGGER.isErrorEnabled()) { 637 LOGGER.atError().log("Unknown reference to {} '{}' at '{}'", 638 type.toString().toLowerCase(Locale.ROOT), 639 identifier, 640 contextItem.toPath(IPathFormatter.METAPATH_PATH_FORMATER)); 641 } 642 } else { 643 item.incrementReferenceCount(); 644 } 645 } 646 } 647}