001/*
002 * SPDX-FileCopyrightText: none
003 * SPDX-License-Identifier: CC0-1.0
004 */
005
006package gov.nist.secauto.oscal.lib.profile.resolver.selection;
007
008import gov.nist.secauto.metaschema.core.metapath.item.node.IAssemblyNodeItem;
009import gov.nist.secauto.metaschema.core.metapath.item.node.IDocumentNodeItem;
010import gov.nist.secauto.metaschema.core.metapath.item.node.IRootAssemblyNodeItem;
011import gov.nist.secauto.metaschema.core.util.ObjectUtils;
012import gov.nist.secauto.oscal.lib.model.Catalog;
013import gov.nist.secauto.oscal.lib.model.CatalogGroup;
014import gov.nist.secauto.oscal.lib.model.Control;
015import gov.nist.secauto.oscal.lib.model.ControlPart;
016import gov.nist.secauto.oscal.lib.profile.resolver.support.AbstractIndexingVisitor;
017import gov.nist.secauto.oscal.lib.profile.resolver.support.IEntityItem;
018import gov.nist.secauto.oscal.lib.profile.resolver.support.IIndexer;
019import gov.nist.secauto.oscal.lib.profile.resolver.support.IIndexer.SelectionStatus;
020
021import org.apache.logging.log4j.LogManager;
022import org.apache.logging.log4j.Logger;
023
024import edu.umd.cs.findbugs.annotations.NonNull;
025import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
026
027/**
028 * Walks a {@link Catalog} indexing all nodes that can be referenced.
029 * <p>
030 * For each {@link CatalogGroup}, {@link Control}, and {@link ControlPart},
031 * determine if that object is {@link SelectionStatus#SELECTED} or
032 * {@link SelectionStatus#UNSELECTED}.
033 * <p>
034 * A {@link Control} is {@link SelectionStatus#SELECTED} if it matches the
035 * configured {@link IControlFilter}, otherwise it is
036 * {@link SelectionStatus#UNSELECTED}.
037 * <p>
038 * A {@link CatalogGroup} is {@link SelectionStatus#SELECTED} if it contains a
039 * {@link SelectionStatus#SELECTED} descendant {@link Control}, otherwise it is
040 * {@link SelectionStatus#UNSELECTED}.
041 * <p>
042 * A {@link ControlPart} is {@link SelectionStatus#SELECTED} if its containing
043 * control is {@link SelectionStatus#SELECTED}.
044 * <p>
045 * All other indexed nodes will have the {@link SelectionStatus#UNKNOWN}, since
046 * these nodes require reference counting to determine if they are to be kept or
047 * not.
048 */
049public final class ControlSelectionVisitor
050    extends AbstractIndexingVisitor<IControlSelectionState, Boolean> {
051  private static final Logger LOGGER = LogManager.getLogger(ControlSelectionVisitor.class);
052
053  private static final ControlSelectionVisitor SINGLETON = new ControlSelectionVisitor();
054
055  @SuppressFBWarnings(value = "SING_SINGLETON_GETTER_NOT_SYNCHRONIZED", justification = "class initialization")
056  public static ControlSelectionVisitor instance() {
057    return SINGLETON;
058  }
059
060  private ControlSelectionVisitor() {
061    // disable construction
062  }
063
064  @Override
065  protected IIndexer getIndexer(IControlSelectionState state) {
066    return state.getIndex();
067  }
068
069  @Override
070  protected Boolean newDefaultResult(IControlSelectionState state) {
071    return false;
072  }
073
074  @Override
075  protected Boolean aggregateResults(Boolean first, Boolean second, IControlSelectionState state) {
076    return first || second;
077  }
078
079  public void visit(@NonNull IDocumentNodeItem catalogDocument, @NonNull IControlSelectionState state) {
080    visitCatalog(catalogDocument, state);
081  }
082
083  public void visitProfile(
084      @NonNull IDocumentNodeItem catalogDocument,
085      @NonNull IDocumentNodeItem profileDocument,
086      @NonNull IControlSelectionState state) {
087    visit(catalogDocument, state);
088
089    profileDocument.modelItems().forEachOrdered(item -> {
090      IRootAssemblyNodeItem root = ObjectUtils.requireNonNull((IRootAssemblyNodeItem) item);
091
092      visitMetadata(root, state);
093      visitBackMatter(root, state);
094    });
095  }
096
097  @Override
098  public Boolean visitCatalog(IDocumentNodeItem catalogDocument, IControlSelectionState state) {
099    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}