001/*
002 * SPDX-FileCopyrightText: none
003 * SPDX-License-Identifier: CC0-1.0
004 */
005
006package dev.metaschema.oscal.lib.profile.resolver.selection;
007
008import org.apache.logging.log4j.LogManager;
009import org.apache.logging.log4j.Logger;
010
011import dev.metaschema.core.metapath.item.node.IAssemblyNodeItem;
012import dev.metaschema.core.metapath.item.node.IDocumentNodeItem;
013import dev.metaschema.core.metapath.item.node.IRootAssemblyNodeItem;
014import dev.metaschema.core.util.ObjectUtils;
015import dev.metaschema.oscal.lib.model.Catalog;
016import dev.metaschema.oscal.lib.model.CatalogGroup;
017import dev.metaschema.oscal.lib.model.Control;
018import dev.metaschema.oscal.lib.model.ControlPart;
019import dev.metaschema.oscal.lib.profile.resolver.support.AbstractIndexingVisitor;
020import dev.metaschema.oscal.lib.profile.resolver.support.IEntityItem;
021import dev.metaschema.oscal.lib.profile.resolver.support.IIndexer;
022import dev.metaschema.oscal.lib.profile.resolver.support.IIndexer.SelectionStatus;
023import edu.umd.cs.findbugs.annotations.NonNull;
024import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
025
026/**
027 * Walks a {@link Catalog} indexing all nodes that can be referenced.
028 * <p>
029 * For each {@link CatalogGroup}, {@link Control}, and {@link ControlPart},
030 * determine if that object is {@link SelectionStatus#SELECTED} or
031 * {@link SelectionStatus#UNSELECTED}.
032 * <p>
033 * A {@link Control} is {@link SelectionStatus#SELECTED} if it matches the
034 * configured {@link IControlFilter}, otherwise it is
035 * {@link SelectionStatus#UNSELECTED}.
036 * <p>
037 * A {@link CatalogGroup} is {@link SelectionStatus#SELECTED} if it contains a
038 * {@link SelectionStatus#SELECTED} descendant {@link Control}, otherwise it is
039 * {@link SelectionStatus#UNSELECTED}.
040 * <p>
041 * A {@link ControlPart} is {@link SelectionStatus#SELECTED} if its containing
042 * control is {@link SelectionStatus#SELECTED}.
043 * <p>
044 * All other indexed nodes will have the {@link SelectionStatus#UNKNOWN}, since
045 * these nodes require reference counting to determine if they are to be kept or
046 * not.
047 */
048public final class ControlSelectionVisitor
049    extends AbstractIndexingVisitor<IControlSelectionState, Boolean> {
050  private static final Logger LOGGER = LogManager.getLogger(ControlSelectionVisitor.class);
051
052  private static final ControlSelectionVisitor SINGLETON = new ControlSelectionVisitor();
053
054  @SuppressFBWarnings(value = "SING_SINGLETON_GETTER_NOT_SYNCHRONIZED", justification = "class initialization")
055  public static ControlSelectionVisitor instance() {
056    return SINGLETON;
057  }
058
059  private ControlSelectionVisitor() {
060    // disable construction
061  }
062
063  @Override
064  protected IIndexer getIndexer(IControlSelectionState state) {
065    return state.getIndex();
066  }
067
068  @Override
069  protected Boolean newDefaultResult(IControlSelectionState state) {
070    return false;
071  }
072
073  @Override
074  protected Boolean aggregateResults(Boolean first, Boolean second, IControlSelectionState state) {
075    return first || second;
076  }
077
078  public void visit(@NonNull IDocumentNodeItem catalogDocument, @NonNull IControlSelectionState state) {
079    visitCatalog(catalogDocument, state);
080  }
081
082  public void visitProfile(
083      @NonNull IDocumentNodeItem catalogDocument,
084      @NonNull IDocumentNodeItem profileDocument,
085      @NonNull IControlSelectionState state) {
086    visit(catalogDocument, state);
087
088    profileDocument.modelItems().forEachOrdered(item -> {
089      IRootAssemblyNodeItem root = ObjectUtils.requireNonNull((IRootAssemblyNodeItem) item);
090
091      visitMetadata(root, state);
092      visitBackMatter(root, state);
093    });
094  }
095
096  @Override
097  public Boolean visitCatalog(IDocumentNodeItem catalogDocument, IControlSelectionState state) {
098    getIndexer(state).setSelectionStatus(catalogDocument, SelectionStatus.SELECTED);
099    return super.visitCatalog(catalogDocument, state);
100  }
101
102  @Override
103  public Boolean visitGroup(IAssemblyNodeItem groupItem, Boolean childSelected,
104      IControlSelectionState state) {
105    super.visitGroup(groupItem, childSelected, state);
106    if (LOGGER.isTraceEnabled()) {
107      CatalogGroup group = ObjectUtils.requireNonNull((CatalogGroup) groupItem.getValue());
108      LOGGER.atTrace().log("Selecting group '{}'. match={}", group.getId(), childSelected);
109    }
110
111    // these should agree
112    assert state.isSelected(groupItem) == childSelected;
113
114    if (childSelected) {
115      getIndexer(state).setSelectionStatus(groupItem, SelectionStatus.SELECTED);
116    } else {
117      getIndexer(state).setSelectionStatus(groupItem, SelectionStatus.UNSELECTED);
118    }
119
120    handlePartSelection(groupItem, childSelected, state);
121    return childSelected;
122  }
123
124  private void handlePartSelection(
125      @NonNull IAssemblyNodeItem groupOrControlItem,
126      boolean selected,
127      IControlSelectionState state) {
128    if (isVisitedItemType(IEntityItem.ItemType.PART)) {
129      SelectionStatus selectionStatus = selected ? SelectionStatus.SELECTED : SelectionStatus.UNSELECTED;
130
131      IIndexer index = getIndexer(state);
132      CHILD_PART_METAPATH.evaluate(groupOrControlItem).stream()
133          .map(item -> (IAssemblyNodeItem) item)
134          .forEachOrdered(partItem -> {
135            index.setSelectionStatus(ObjectUtils.requireNonNull(partItem), selectionStatus);
136          });
137    }
138  }
139
140  @Override
141  public Boolean visitControl(
142      IAssemblyNodeItem controlItem,
143      Boolean childResult,
144      IControlSelectionState state) {
145    super.visitControl(controlItem, childResult, state);
146
147    boolean selected = state.isSelected(controlItem);
148    if (selected) {
149      getIndexer(state).setSelectionStatus(controlItem, SelectionStatus.SELECTED);
150    } else {
151      getIndexer(state).setSelectionStatus(controlItem, SelectionStatus.UNSELECTED);
152    }
153
154    handlePartSelection(controlItem, selected, state);
155    return selected;
156  }
157}