View Javadoc
1   /*
2    * Portions of this software was developed by employees of the National Institute
3    * of Standards and Technology (NIST), an agency of the Federal Government and is
4    * being made available as a public service. Pursuant to title 17 United States
5    * Code Section 105, works of NIST employees are not subject to copyright
6    * protection in the United States. This software may be subject to foreign
7    * copyright. Permission in the United States and in foreign countries, to the
8    * extent that NIST may hold copyright, to use, copy, modify, create derivative
9    * works, and distribute this software and its documentation without fee is hereby
10   * granted on a non-exclusive basis, provided that this notice and disclaimer
11   * of warranty appears in all copies.
12   *
13   * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
14   * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
15   * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
16   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
17   * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
18   * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
19   * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
20   * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
21   * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
22   * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
23   * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
24   * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
25   */
26  
27  package gov.nist.secauto.oscal.lib.profile.resolver.policy;
28  
29  import com.vladsch.flexmark.ast.InlineLinkNode;
30  import com.vladsch.flexmark.util.ast.Node;
31  
32  import gov.nist.secauto.metaschema.model.common.datatype.markup.IMarkupString;
33  import gov.nist.secauto.metaschema.model.common.datatype.markup.flexmark.InsertAnchorExtension.InsertAnchorNode;
34  import gov.nist.secauto.metaschema.model.common.metapath.MetapathExpression;
35  import gov.nist.secauto.metaschema.model.common.metapath.format.IPathFormatter;
36  import gov.nist.secauto.metaschema.model.common.metapath.function.library.FnData;
37  import gov.nist.secauto.metaschema.model.common.metapath.item.IDocumentNodeItem;
38  import gov.nist.secauto.metaschema.model.common.metapath.item.IMarkupItem;
39  import gov.nist.secauto.metaschema.model.common.metapath.item.IRequiredValueModelNodeItem;
40  import gov.nist.secauto.metaschema.model.common.util.CollectionUtil;
41  import gov.nist.secauto.metaschema.model.common.util.ObjectUtils;
42  import gov.nist.secauto.oscal.lib.model.CatalogGroup;
43  import gov.nist.secauto.oscal.lib.model.Control;
44  import gov.nist.secauto.oscal.lib.model.ControlPart;
45  import gov.nist.secauto.oscal.lib.model.Link;
46  import gov.nist.secauto.oscal.lib.model.Property;
47  import gov.nist.secauto.oscal.lib.model.metadata.AbstractProperty;
48  import gov.nist.secauto.oscal.lib.model.metadata.IProperty;
49  import gov.nist.secauto.oscal.lib.profile.resolver.support.AbstractCatalogEntityVisitor;
50  import gov.nist.secauto.oscal.lib.profile.resolver.support.IEntityItem;
51  import gov.nist.secauto.oscal.lib.profile.resolver.support.IIndexer;
52  
53  import org.apache.logging.log4j.LogManager;
54  import org.apache.logging.log4j.Logger;
55  
56  import java.net.URI;
57  import java.util.EnumSet;
58  import java.util.HashMap;
59  import java.util.HashSet;
60  import java.util.Locale;
61  import java.util.Map;
62  import java.util.Set;
63  import java.util.UUID;
64  import java.util.function.BiConsumer;
65  
66  import javax.xml.namespace.QName;
67  
68  import edu.umd.cs.findbugs.annotations.NonNull;
69  import edu.umd.cs.findbugs.annotations.Nullable;
70  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
71  
72  public class ReferenceCountingVisitor
73      extends AbstractCatalogEntityVisitor<ReferenceCountingVisitor.Context, Void>
74      implements IReferenceVisitor<ReferenceCountingVisitor.Context> {
75    private static final Logger LOGGER = LogManager.getLogger(ReferenceCountingVisitor.class);
76  
77    private static final ReferenceCountingVisitor SINGLETON = new ReferenceCountingVisitor();
78  
79    @NonNull
80    private static final MetapathExpression PARAM_MARKUP_METAPATH
81        = MetapathExpression
82            .compile("label|usage|constraint/(description|tests/remarks)|guideline/prose|select/choice|remarks");
83    @NonNull
84    private static final MetapathExpression ROLE_MARKUP_METAPATH
85        = MetapathExpression.compile("title|description|remarks");
86    @NonNull
87    private static final MetapathExpression LOCATION_MARKUP_METAPATH
88        = MetapathExpression.compile("title|remarks");
89    @NonNull
90    private static final MetapathExpression PARTY_MARKUP_METAPATH
91        = MetapathExpression.compile("title|remarks");
92    @NonNull
93    private static final MetapathExpression RESOURCE_MARKUP_METAPATH
94        = MetapathExpression.compile("title|description|remarks");
95  
96    @NonNull
97    private static final IReferencePolicy<Property> PROPERTY_POLICY_IGNORE = IReferencePolicy.ignore();
98    @NonNull
99    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 }