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