001/* 002 * Portions of this software was developed by employees of the National Institute 003 * of Standards and Technology (NIST), an agency of the Federal Government and is 004 * being made available as a public service. Pursuant to title 17 United States 005 * Code Section 105, works of NIST employees are not subject to copyright 006 * protection in the United States. This software may be subject to foreign 007 * copyright. Permission in the United States and in foreign countries, to the 008 * extent that NIST may hold copyright, to use, copy, modify, create derivative 009 * works, and distribute this software and its documentation without fee is hereby 010 * granted on a non-exclusive basis, provided that this notice and disclaimer 011 * of warranty appears in all copies. 012 * 013 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER 014 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY 015 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF 016 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM 017 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE 018 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE. IN NO EVENT 019 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT, 020 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM, 021 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY, 022 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR 023 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT 024 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER. 025 */ 026 027package gov.nist.secauto.oscal.lib.profile.resolver.selection; 028 029import gov.nist.secauto.metaschema.model.common.metapath.item.IDocumentNodeItem; 030import gov.nist.secauto.metaschema.model.common.metapath.item.IRequiredValueModelNodeItem; 031import gov.nist.secauto.metaschema.model.common.metapath.item.IRootAssemblyNodeItem; 032import gov.nist.secauto.metaschema.model.common.util.ObjectUtils; 033import gov.nist.secauto.oscal.lib.model.BackMatter; 034import gov.nist.secauto.oscal.lib.model.BackMatter.Resource; 035import gov.nist.secauto.oscal.lib.model.Catalog; 036import gov.nist.secauto.oscal.lib.model.CatalogGroup; 037import gov.nist.secauto.oscal.lib.model.Control; 038import gov.nist.secauto.oscal.lib.model.ControlPart; 039import gov.nist.secauto.oscal.lib.model.Metadata; 040import gov.nist.secauto.oscal.lib.model.Metadata.Location; 041import gov.nist.secauto.oscal.lib.model.Metadata.Party; 042import gov.nist.secauto.oscal.lib.model.Metadata.Role; 043import gov.nist.secauto.oscal.lib.model.Parameter; 044import gov.nist.secauto.oscal.lib.profile.resolver.support.AbstractCatalogEntityVisitor; 045import gov.nist.secauto.oscal.lib.profile.resolver.support.IEntityItem; 046import gov.nist.secauto.oscal.lib.profile.resolver.support.IEntityItem.ItemType; 047import gov.nist.secauto.oscal.lib.profile.resolver.support.IIndexer; 048import gov.nist.secauto.oscal.lib.profile.resolver.support.IIndexer.SelectionStatus; 049 050import org.apache.logging.log4j.LogManager; 051import org.apache.logging.log4j.Logger; 052 053import java.util.EnumSet; 054import java.util.stream.Collectors; 055 056import edu.umd.cs.findbugs.annotations.NonNull; 057import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 058 059public class FilterNonSelectedVisitor 060 extends AbstractCatalogEntityVisitor<FilterNonSelectedVisitor.Context, DefaultResult> { 061 private static final Logger LOGGER = LogManager.getLogger(FilterNonSelectedVisitor.class); 062 private static final FilterNonSelectedVisitor SINGLETON = new FilterNonSelectedVisitor(); 063 064 public static FilterNonSelectedVisitor instance() { 065 return SINGLETON; 066 } 067 068 @SuppressWarnings("null") 069 protected FilterNonSelectedVisitor() { 070 // all other entity types are handled in a special way by this visitor 071 super(EnumSet.of(IEntityItem.ItemType.GROUP, IEntityItem.ItemType.CONTROL, IEntityItem.ItemType.PARAMETER)); 072 } 073 074 public void visitCatalog(@NonNull IDocumentNodeItem catalogItem, @NonNull IIndexer indexer) { 075 Context context = new Context(indexer); 076 IResult result = visitCatalog(catalogItem, context); 077 078 IRootAssemblyNodeItem root = catalogItem.getRootAssemblyNodeItem(); 079 080 Catalog catalog = (Catalog) catalogItem.getValue(); 081 result.applyTo(catalog); 082 083 root.getModelItemsByName("metadata").forEach(child -> { 084 assert child != null; 085 visitMetadata(child, context); 086 }); 087 088 root.getModelItemsByName("back-matter").forEach(child -> { 089 assert child != null; 090 visitBackMatter(child, context); 091 }); 092 } 093 094 @Override 095 protected DefaultResult newDefaultResult(Context state) { 096 return new DefaultResult(); 097 } 098 099 @Override 100 protected DefaultResult aggregateResults(DefaultResult first, DefaultResult second, Context state) { 101 return first.append(ObjectUtils.notNull(second)); 102 } 103 104 protected void visitMetadata(@NonNull IRequiredValueModelNodeItem metadataItem, Context context) { 105 Metadata metadata = (Metadata) metadataItem.getValue(); 106 107 IIndexer index = context.getIndexer(); 108 // prune roles, parties, and locations 109 // keep entries with prop name:keep and any referenced 110 for (IEntityItem entity : IIndexer.getUnreferencedEntitiesAsStream(index.getEntitiesByItemType(ItemType.ROLE)) 111 .collect(Collectors.toList())) { 112 Role role = entity.getInstanceValue(); 113 if (LOGGER.isDebugEnabled()) { 114 LOGGER.atDebug().log("Removing role '{}'", role.getId()); 115 } 116 metadata.removeRole(role); 117 index.removeItem(entity); 118 } 119 120 for (IEntityItem entity : IIndexer.getUnreferencedEntitiesAsStream(index.getEntitiesByItemType(ItemType.PARTY)) 121 .collect(Collectors.toList())) { 122 Party party = entity.getInstanceValue(); 123 if (LOGGER.isDebugEnabled()) { 124 LOGGER.atDebug().log("Removing party '{}'", party.getUuid()); 125 } 126 metadata.removeParty(party); 127 index.removeItem(entity); 128 } 129 130 for (IEntityItem entity : IIndexer.getUnreferencedEntitiesAsStream(index.getEntitiesByItemType(ItemType.LOCATION)) 131 .collect(Collectors.toList())) { 132 Location location = entity.getInstanceValue(); 133 if (LOGGER.isDebugEnabled()) { 134 LOGGER.atDebug().log("Removing location '{}'", location.getUuid()); 135 } 136 metadata.removeLocation(location); 137 index.removeItem(entity); 138 } 139 } 140 141 @SuppressWarnings("static-method") 142 private void visitBackMatter(@NonNull IRequiredValueModelNodeItem backMatterItem, Context context) { 143 BackMatter backMatter = (BackMatter) backMatterItem.getValue(); 144 145 IIndexer index = context.getIndexer(); 146 for (IEntityItem entity : IIndexer.getUnreferencedEntitiesAsStream(index.getEntitiesByItemType(ItemType.RESOURCE)) 147 .collect(Collectors.toList())) { 148 Resource resource = entity.getInstanceValue(); 149 if (LOGGER.isDebugEnabled()) { 150 LOGGER.atDebug().log("Removing resource '{}'", resource.getUuid()); 151 } 152 backMatter.removeResource(resource); 153 index.removeItem(entity); 154 } 155 } 156 157 @Override 158 public DefaultResult visitGroup( 159 IRequiredValueModelNodeItem item, 160 DefaultResult childResult, 161 Context context) { 162 CatalogGroup group = (CatalogGroup) item.getValue(); 163 164 IIndexer index = context.getIndexer(); 165 String groupId = group.getId(); 166 DefaultResult retval = new DefaultResult(); 167 if (SelectionStatus.SELECTED.equals(index.getSelectionStatus(item))) { 168 if (groupId != null) { 169 // this group should always be found in the index 170 IEntityItem entity = ObjectUtils.requireNonNull(index.getEntity(ItemType.GROUP, groupId, false)); 171 // update the id 172 group.setId(entity.getIdentifier()); 173 } 174 childResult.applyTo(group); 175 } else { 176 retval.removeGroup(group); 177 retval.appendPromoted(ObjectUtils.notNull(childResult)); 178 179 if (groupId != null) { 180 // this group should always be found in the index 181 IEntityItem entity = ObjectUtils.requireNonNull(index.getEntity(ItemType.GROUP, groupId, false)); 182 index.removeItem(entity); 183 } 184 185 // remove any associated parts from the index 186 removePartsFromIndex(item, index); 187 } 188 return retval; 189 } 190 191 @Override 192 public DefaultResult visitControl( 193 IRequiredValueModelNodeItem item, 194 DefaultResult childResult, 195 Context context) { 196 Control control = (Control) item.getValue(); 197 IIndexer index = context.getIndexer(); 198 // this control should always be found in the index 199 IEntityItem entity = ObjectUtils.requireNonNull( 200 index.getEntity(ItemType.CONTROL, ObjectUtils.requireNonNull(control.getId()), false)); 201 202 IRequiredValueModelNodeItem parent = ObjectUtils.notNull(item.getParentContentNodeItem()); 203 DefaultResult retval = new DefaultResult(); 204 if (SelectionStatus.SELECTED.equals(index.getSelectionStatus(item))) { 205 // keep this control 206 // update the id 207 control.setId(entity.getIdentifier()); 208 209 if (!SelectionStatus.SELECTED.equals(index.getSelectionStatus(parent))) { 210 // promote this control 211 retval.promoteControl(control); 212 } 213 childResult.applyTo(control); 214 } else { 215 // remove this control and promote any needed children 216 217 if (SelectionStatus.SELECTED.equals(index.getSelectionStatus(parent))) { 218 retval.removeControl(control); 219 } 220 retval.appendPromoted(ObjectUtils.notNull(childResult)); 221 index.removeItem(entity); 222 223 // remove any associated parts from the index 224 removePartsFromIndex(item, index); 225 } 226 return retval; 227 } 228 229 protected static void removePartsFromIndex(@NonNull IRequiredValueModelNodeItem groupOrControlItem, 230 @NonNull IIndexer index) { 231 CHILD_PART_METAPATH.evaluate(groupOrControlItem).asStream() 232 .map(item -> (IRequiredValueModelNodeItem) item) 233 .forEachOrdered(partItem -> { 234 ControlPart part = (ControlPart) partItem.getValue(); 235 String id = part.getId(); 236 if (id != null) { 237 IEntityItem entity = index.getEntity(IEntityItem.ItemType.PART, id); 238 if (entity != null) { 239 index.removeItem(entity); 240 } 241 } 242 }); 243 } 244 245 @Override 246 protected DefaultResult visitParameter(IRequiredValueModelNodeItem item, IRequiredValueModelNodeItem parent, 247 Context context) { 248 Parameter param = (Parameter) item.getValue(); 249 IIndexer index = context.getIndexer(); 250 // this parameter should always be found in the index 251 IEntityItem entity = ObjectUtils.requireNonNull( 252 index.getEntity(ItemType.PARAMETER, ObjectUtils.requireNonNull(param.getId()), false)); 253 254 DefaultResult retval = new DefaultResult(); 255 if (IIndexer.isReferencedEntity(entity)) { 256 // keep the parameter 257 // update the id 258 param.setId(entity.getIdentifier()); 259 260 // a parameter is selected if it has a reference count greater than 0 261 index.setSelectionStatus(item, SelectionStatus.SELECTED); 262 263 // promote this parameter 264 if (SelectionStatus.UNSELECTED.equals(index.getSelectionStatus(parent))) { 265 retval.promoteParameter(param); 266 } 267 } else { 268 // don't keep the parameter 269 if (SelectionStatus.SELECTED.equals(index.getSelectionStatus(parent))) { 270 retval.removeParameter(param); 271 } 272 index.removeItem(entity); 273 } 274 return retval; 275 } 276 277 protected static final class Context { 278 279 @NonNull 280 private final IIndexer indexer; 281 282 private Context(@NonNull IIndexer indexer) { 283 super(); 284 this.indexer = indexer; 285 } 286 287 @NonNull 288 @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "provides intentional access to index state") 289 public IIndexer getIndexer() { 290 return indexer; 291 } 292 } 293}