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