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}