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}