1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.oscal.lib.profile.resolver.selection;
7   
8   import gov.nist.secauto.metaschema.core.metapath.item.node.IAssemblyNodeItem;
9   import gov.nist.secauto.metaschema.core.metapath.item.node.IDocumentNodeItem;
10  import gov.nist.secauto.metaschema.core.metapath.item.node.IRootAssemblyNodeItem;
11  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
12  import gov.nist.secauto.oscal.lib.model.Catalog;
13  import gov.nist.secauto.oscal.lib.model.CatalogGroup;
14  import gov.nist.secauto.oscal.lib.model.Control;
15  import gov.nist.secauto.oscal.lib.model.ControlPart;
16  import gov.nist.secauto.oscal.lib.profile.resolver.support.AbstractIndexingVisitor;
17  import gov.nist.secauto.oscal.lib.profile.resolver.support.IEntityItem;
18  import gov.nist.secauto.oscal.lib.profile.resolver.support.IIndexer;
19  import gov.nist.secauto.oscal.lib.profile.resolver.support.IIndexer.SelectionStatus;
20  
21  import org.apache.logging.log4j.LogManager;
22  import org.apache.logging.log4j.Logger;
23  
24  import edu.umd.cs.findbugs.annotations.NonNull;
25  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
26  
27  /**
28   * Walks a {@link Catalog} indexing all nodes that can be referenced.
29   * <p>
30   * For each {@link CatalogGroup}, {@link Control}, and {@link ControlPart},
31   * determine if that object is {@link SelectionStatus#SELECTED} or
32   * {@link SelectionStatus#UNSELECTED}.
33   * <p>
34   * A {@link Control} is {@link SelectionStatus#SELECTED} if it matches the
35   * configured {@link IControlFilter}, otherwise it is
36   * {@link SelectionStatus#UNSELECTED}.
37   * <p>
38   * A {@link CatalogGroup} is {@link SelectionStatus#SELECTED} if it contains a
39   * {@link SelectionStatus#SELECTED} descendant {@link Control}, otherwise it is
40   * {@link SelectionStatus#UNSELECTED}.
41   * <p>
42   * A {@link ControlPart} is {@link SelectionStatus#SELECTED} if its containing
43   * control is {@link SelectionStatus#SELECTED}.
44   * <p>
45   * All other indexed nodes will have the {@link SelectionStatus#UNKNOWN}, since
46   * these nodes require reference counting to determine if they are to be kept or
47   * not.
48   */
49  public final class ControlSelectionVisitor
50      extends AbstractIndexingVisitor<IControlSelectionState, Boolean> {
51    private static final Logger LOGGER = LogManager.getLogger(ControlSelectionVisitor.class);
52  
53    private static final ControlSelectionVisitor SINGLETON = new ControlSelectionVisitor();
54  
55    @SuppressFBWarnings(value = "SING_SINGLETON_GETTER_NOT_SYNCHRONIZED", justification = "class initialization")
56    public static ControlSelectionVisitor instance() {
57      return SINGLETON;
58    }
59  
60    private ControlSelectionVisitor() {
61      // disable construction
62    }
63  
64    @Override
65    protected IIndexer getIndexer(IControlSelectionState state) {
66      return state.getIndex();
67    }
68  
69    @Override
70    protected Boolean newDefaultResult(IControlSelectionState state) {
71      return false;
72    }
73  
74    @Override
75    protected Boolean aggregateResults(Boolean first, Boolean second, IControlSelectionState state) {
76      return first || second;
77    }
78  
79    public void visit(@NonNull IDocumentNodeItem catalogDocument, @NonNull IControlSelectionState state) {
80      visitCatalog(catalogDocument, state);
81    }
82  
83    public void visitProfile(
84        @NonNull IDocumentNodeItem catalogDocument,
85        @NonNull IDocumentNodeItem profileDocument,
86        @NonNull IControlSelectionState state) {
87      visit(catalogDocument, state);
88  
89      profileDocument.modelItems().forEachOrdered(item -> {
90        IRootAssemblyNodeItem root = ObjectUtils.requireNonNull((IRootAssemblyNodeItem) item);
91  
92        visitMetadata(root, state);
93        visitBackMatter(root, state);
94      });
95    }
96  
97    @Override
98    public Boolean visitCatalog(IDocumentNodeItem catalogDocument, IControlSelectionState state) {
99      getIndexer(state).setSelectionStatus(catalogDocument, SelectionStatus.SELECTED);
100     return super.visitCatalog(catalogDocument, state);
101   }
102 
103   @Override
104   public Boolean visitGroup(IAssemblyNodeItem groupItem, Boolean childSelected,
105       IControlSelectionState state) {
106     super.visitGroup(groupItem, childSelected, state);
107     if (LOGGER.isTraceEnabled()) {
108       CatalogGroup group = ObjectUtils.requireNonNull((CatalogGroup) groupItem.getValue());
109       LOGGER.atTrace().log("Selecting group '{}'. match={}", group.getId(), childSelected);
110     }
111 
112     // these should agree
113     assert state.isSelected(groupItem) == childSelected;
114 
115     if (childSelected) {
116       getIndexer(state).setSelectionStatus(groupItem, SelectionStatus.SELECTED);
117     } else {
118       getIndexer(state).setSelectionStatus(groupItem, SelectionStatus.UNSELECTED);
119     }
120 
121     handlePartSelection(groupItem, childSelected, state);
122     return childSelected;
123   }
124 
125   private void handlePartSelection(
126       @NonNull IAssemblyNodeItem groupOrControlItem,
127       boolean selected,
128       IControlSelectionState state) {
129     if (isVisitedItemType(IEntityItem.ItemType.PART)) {
130       SelectionStatus selectionStatus = selected ? SelectionStatus.SELECTED : SelectionStatus.UNSELECTED;
131 
132       IIndexer index = getIndexer(state);
133       CHILD_PART_METAPATH.evaluate(groupOrControlItem).stream()
134           .map(item -> (IAssemblyNodeItem) item)
135           .forEachOrdered(partItem -> {
136             index.setSelectionStatus(ObjectUtils.requireNonNull(partItem), selectionStatus);
137           });
138     }
139   }
140 
141   @Override
142   public Boolean visitControl(
143       IAssemblyNodeItem controlItem,
144       Boolean childResult,
145       IControlSelectionState state) {
146     super.visitControl(controlItem, childResult, state);
147 
148     boolean selected = state.isSelected(controlItem);
149     if (selected) {
150       getIndexer(state).setSelectionStatus(controlItem, SelectionStatus.SELECTED);
151     } else {
152       getIndexer(state).setSelectionStatus(controlItem, SelectionStatus.UNSELECTED);
153     }
154 
155     handlePartSelection(controlItem, selected, state);
156     return selected;
157   }
158 }