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}