1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package dev.metaschema.oscal.lib.profile.resolver.support;
7   
8   import java.util.Collections;
9   import java.util.EnumSet;
10  import java.util.Set;
11  
12  import dev.metaschema.core.metapath.IMetapathExpression;
13  import dev.metaschema.core.metapath.item.node.IAssemblyNodeItem;
14  import dev.metaschema.core.metapath.item.node.IDocumentNodeItem;
15  import dev.metaschema.core.metapath.item.node.IRootAssemblyNodeItem;
16  import dev.metaschema.core.util.CollectionUtil;
17  import dev.metaschema.core.util.ObjectUtils;
18  import dev.metaschema.oscal.lib.OscalBindingContext;
19  import dev.metaschema.oscal.lib.OscalModelConstants;
20  import edu.umd.cs.findbugs.annotations.NonNull;
21  
22  /**
23   * Visits a catalog document and its children as designated.
24   * <p>
25   * This implementation is stateless. The {@code T} parameter can be used to
26   * convey state as needed.
27   *
28   * @param <T>
29   *          the state type
30   * @param <R>
31   *          the result type
32   */
33  public abstract class AbstractCatalogEntityVisitor<T, R>
34      extends AbstractCatalogVisitor<T, R> {
35    @NonNull
36    public static final IMetapathExpression CHILD_PART_METAPATH
37        = IMetapathExpression.compile("part|part//part",
38            OscalBindingContext.OSCAL_STATIC_METAPATH_CONTEXT);
39    @NonNull
40    private static final IMetapathExpression BACK_MATTER_RESOURCES_METAPATH
41        = IMetapathExpression.compile("back-matter/resource",
42            OscalBindingContext.OSCAL_STATIC_METAPATH_CONTEXT);
43    @NonNull
44    private static final Set<IEntityItem.ItemType> GROUP_CONTAINER_TYPES
45        = ObjectUtils.notNull(EnumSet.of(
46            IEntityItem.ItemType.GROUP,
47            IEntityItem.ItemType.CONTROL,
48            IEntityItem.ItemType.PARAMETER,
49            IEntityItem.ItemType.PART));
50    @NonNull
51    private static final Set<IEntityItem.ItemType> CONTROL_CONTAINER_TYPES
52        = ObjectUtils.notNull(EnumSet.of(
53            IEntityItem.ItemType.CONTROL,
54            IEntityItem.ItemType.PARAMETER,
55            IEntityItem.ItemType.PART));
56    @NonNull
57    private final Set<IEntityItem.ItemType> itemTypesToVisit;
58  
59    /**
60     * Create a new visitor that will visit the item types identified by
61     * {@code itemTypesToVisit}.
62     *
63     * @param itemTypesToVisit
64     *          the item type the visitor will visit
65     */
66    public AbstractCatalogEntityVisitor(@NonNull Set<IEntityItem.ItemType> itemTypesToVisit) {
67      this.itemTypesToVisit = CollectionUtil.unmodifiableSet(itemTypesToVisit);
68    }
69  
70    public Set<IEntityItem.ItemType> getItemTypesToVisit() {
71      return CollectionUtil.unmodifiableSet(itemTypesToVisit);
72    }
73  
74    protected boolean isVisitedItemType(@NonNull IEntityItem.ItemType type) {
75      return itemTypesToVisit.contains(type);
76    }
77  
78    @Override
79    public R visitCatalog(IDocumentNodeItem catalogDocument, T state) {
80      R result = super.visitCatalog(catalogDocument, state);
81  
82      catalogDocument.modelItems().forEachOrdered(item -> {
83        IRootAssemblyNodeItem root = ObjectUtils.requireNonNull((IRootAssemblyNodeItem) item);
84        visitMetadata(root, state);
85        visitBackMatter(root, state);
86      });
87      return result;
88    }
89  
90    @Override
91    protected R visitGroupContainer(IAssemblyNodeItem catalogOrGroup, R initialResult, T state) {
92      R retval;
93      if (Collections.disjoint(getItemTypesToVisit(), GROUP_CONTAINER_TYPES)) {
94        retval = initialResult;
95      } else {
96        retval = super.visitGroupContainer(catalogOrGroup, initialResult, state);
97      }
98      return retval;
99    }
100 
101   @Override
102   protected R visitControlContainer(IAssemblyNodeItem catalogOrGroupOrControl, R initialResult, T state) {
103     R retval;
104     if (Collections.disjoint(getItemTypesToVisit(), CONTROL_CONTAINER_TYPES)) {
105       retval = initialResult;
106     } else {
107       // first descend to all control container children
108       retval = super.visitControlContainer(catalogOrGroupOrControl, initialResult, state);
109 
110       // handle parameters
111       if (isVisitedItemType(IEntityItem.ItemType.PARAMETER)) {
112         retval = catalogOrGroupOrControl.getModelItemsByName(OscalModelConstants.QNAME_PARAM).stream()
113             .map(paramItem -> visitParameter(
114                 ObjectUtils.requireNonNull((IAssemblyNodeItem) paramItem),
115                 catalogOrGroupOrControl,
116                 state))
117             .reduce(retval, (first, second) -> aggregateResults(first, second, state));
118       }
119     }
120     return retval;
121   }
122 
123   protected void visitParts(@NonNull IAssemblyNodeItem groupOrControlItem, T state) {
124     // handle parts
125     if (isVisitedItemType(IEntityItem.ItemType.PART)) {
126       CHILD_PART_METAPATH.evaluate(groupOrControlItem).stream()
127           .map(item -> (IAssemblyNodeItem) item)
128           .forEachOrdered(partItem -> {
129             visitPart(ObjectUtils.requireNonNull(partItem), groupOrControlItem, state);
130           });
131     }
132   }
133 
134   @Override
135   protected R visitGroupInternal(@NonNull IAssemblyNodeItem item, R childResult, T state) {
136     if (isVisitedItemType(IEntityItem.ItemType.PART)) {
137       visitParts(item, state);
138     }
139 
140     R retval = childResult;
141     if (isVisitedItemType(IEntityItem.ItemType.GROUP)) {
142       retval = visitGroup(item, retval, state);
143     }
144     return retval;
145   }
146 
147   @Override
148   protected R visitControlInternal(IAssemblyNodeItem item, R childResult, T state) {
149     if (isVisitedItemType(IEntityItem.ItemType.PART)) {
150       visitParts(item, state);
151     }
152 
153     R retval = childResult;
154     if (isVisitedItemType(IEntityItem.ItemType.CONTROL)) {
155       retval = visitControl(item, retval, state);
156     }
157     return retval;
158   }
159 
160   /**
161    * Called when visiting a parameter.
162    * <p>
163    * Can be overridden by classes extending this interface to support processing
164    * of the visited object.
165    *
166    * @param item
167    *          the Metapath item for the parameter
168    * @param catalogOrGroupOrControl
169    *          the parameter's parent Metapath item
170    * @param state
171    *          the calling context information
172    * @return a meaningful result of the given type
173    */
174   protected R visitParameter(
175       @NonNull IAssemblyNodeItem item,
176       @NonNull IAssemblyNodeItem catalogOrGroupOrControl,
177       T state) {
178     // do nothing
179     return newDefaultResult(state);
180   }
181 
182   /**
183    * Called when visiting a part.
184    * <p>
185    * Can be overridden by classes extending this interface to support processing
186    * of the visited object.
187    *
188    * @param item
189    *          the Metapath item for the part
190    * @param groupOrControl
191    *          the part's parent Metapath item
192    * @param state
193    *          the calling context information
194    */
195   protected void visitPart( // NOPMD noop default
196       @NonNull IAssemblyNodeItem item,
197       @NonNull IAssemblyNodeItem groupOrControl,
198       T state) {
199     // do nothing
200   }
201 
202   /**
203    * Called when visiting the "metadata" section of an OSCAL document.
204    * <p>
205    * Visits each contained role, location, and party.
206    *
207    * @param rootItem
208    *          the root Module node item containing the "metadata" node
209    * @param state
210    *          the calling context information
211    */
212   protected void visitMetadata(@NonNull IRootAssemblyNodeItem rootItem, T state) {
213     rootItem.getModelItemsByName(OscalModelConstants.QNAME_METADATA).stream()
214         .map(metadataItem -> (IAssemblyNodeItem) metadataItem)
215         .forEach(metadataItem -> {
216           if (isVisitedItemType(IEntityItem.ItemType.ROLE)) {
217             metadataItem.getModelItemsByName(OscalModelConstants.QNAME_ROLE).stream()
218                 .map(roleItem -> (IAssemblyNodeItem) roleItem)
219                 .forEachOrdered(roleItem -> {
220                   visitRole(ObjectUtils.requireNonNull(roleItem), metadataItem, state);
221                 });
222           }
223 
224           if (isVisitedItemType(IEntityItem.ItemType.LOCATION)) {
225             metadataItem.getModelItemsByName(OscalModelConstants.QNAME_LOCATION).stream()
226                 .map(locationItem -> (IAssemblyNodeItem) locationItem)
227                 .forEachOrdered(locationItem -> {
228                   visitLocation(ObjectUtils.requireNonNull(locationItem), metadataItem, state);
229                 });
230           }
231 
232           if (isVisitedItemType(IEntityItem.ItemType.PARTY)) {
233             metadataItem.getModelItemsByName(OscalModelConstants.QNAME_PARTY).stream()
234                 .map(partyItem -> (IAssemblyNodeItem) partyItem)
235                 .forEachOrdered(partyItem -> {
236                   visitParty(ObjectUtils.requireNonNull(partyItem), metadataItem, state);
237                 });
238           }
239         });
240   }
241 
242   /**
243    * Called when visiting a role in the "metadata" section of an OSCAL document.
244    * <p>
245    * Can be overridden by classes extending this interface to support processing
246    * of the visited object.
247    *
248    * @param item
249    *          the role Module node item which is a child of the "metadata" node
250    * @param metadataItem
251    *          the "metadata" Module node item containing the role
252    * @param state
253    *          the calling context information
254    */
255   protected void visitRole( // NOPMD noop default
256       @NonNull IAssemblyNodeItem item,
257       @NonNull IAssemblyNodeItem metadataItem,
258       T state) {
259     // do nothing
260   }
261 
262   /**
263    * Called when visiting a location in the "metadata" section of an OSCAL
264    * document.
265    * <p>
266    * Can be overridden by classes extending this interface to support processing
267    * of the visited object.
268    *
269    * @param item
270    *          the location Module node item which is a child of the "metadata"
271    *          node
272    * @param metadataItem
273    *          the "metadata" Module node item containing the location
274    * @param state
275    *          the calling context information
276    */
277   protected void visitLocation( // NOPMD noop default
278       @NonNull IAssemblyNodeItem item,
279       @NonNull IAssemblyNodeItem metadataItem,
280       T state) {
281     // do nothing
282   }
283 
284   /**
285    * Called when visiting a party in the "metadata" section of an OSCAL document.
286    * <p>
287    * Can be overridden by classes extending this interface to support processing
288    * of the visited object.
289    *
290    * @param item
291    *          the party Module node item which is a child of the "metadata" node
292    * @param metadataItem
293    *          the "metadata" Module node item containing the party
294    * @param state
295    *          the calling context information
296    */
297   protected void visitParty( // NOPMD noop default
298       @NonNull IAssemblyNodeItem item,
299       @NonNull IAssemblyNodeItem metadataItem,
300       T state) {
301     // do nothing
302   }
303 
304   /**
305    * Called when visiting the "back-matter" section of an OSCAL document.
306    * <p>
307    * Visits each contained resource.
308    *
309    * @param rootItem
310    *          the root Module node item containing the "back-matter" node
311    * @param state
312    *          the calling context information
313    */
314   protected void visitBackMatter(@NonNull IRootAssemblyNodeItem rootItem, T state) {
315     if (isVisitedItemType(IEntityItem.ItemType.RESOURCE)) {
316       BACK_MATTER_RESOURCES_METAPATH.evaluate(rootItem).stream()
317           .map(item -> (IAssemblyNodeItem) item)
318           .forEachOrdered(resourceItem -> {
319             visitResource(ObjectUtils.requireNonNull(resourceItem), rootItem, state);
320           });
321     }
322   }
323 
324   /**
325    * Called when visiting a resource in the "back-matter" section of an OSCAL
326    * document.
327    * <p>
328    * Can be overridden by classes extending this interface to support processing
329    * of the visited object.
330    *
331    * @param resource
332    *          the resource Module node item which is a child of the "metadata"
333    *          node
334    * @param backMatter
335    *          the resource Module node item containing the party
336    * @param state
337    *          the calling context information
338    */
339   protected void visitResource( // NOPMD noop default
340       @NonNull IAssemblyNodeItem resource,
341       @NonNull IRootAssemblyNodeItem backMatter,
342       T state) {
343     // do nothing
344   }
345 }