View Javadoc
1   /*
2    * Portions of this software was developed by employees of the National Institute
3    * of Standards and Technology (NIST), an agency of the Federal Government and is
4    * being made available as a public service. Pursuant to title 17 United States
5    * Code Section 105, works of NIST employees are not subject to copyright
6    * protection in the United States. This software may be subject to foreign
7    * copyright. Permission in the United States and in foreign countries, to the
8    * extent that NIST may hold copyright, to use, copy, modify, create derivative
9    * works, and distribute this software and its documentation without fee is hereby
10   * granted on a non-exclusive basis, provided that this notice and disclaimer
11   * of warranty appears in all copies.
12   *
13   * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
14   * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
15   * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
16   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
17   * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
18   * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
19   * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
20   * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
21   * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
22   * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
23   * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
24   * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
25   */
26  
27  package gov.nist.secauto.oscal.lib.profile.resolver.selection;
28  
29  import gov.nist.secauto.metaschema.model.common.metapath.item.IDocumentNodeItem;
30  import gov.nist.secauto.metaschema.model.common.metapath.item.IRequiredValueModelNodeItem;
31  import gov.nist.secauto.metaschema.model.common.metapath.item.IRootAssemblyNodeItem;
32  import gov.nist.secauto.metaschema.model.common.util.ObjectUtils;
33  import gov.nist.secauto.oscal.lib.model.BackMatter;
34  import gov.nist.secauto.oscal.lib.model.BackMatter.Resource;
35  import gov.nist.secauto.oscal.lib.model.Catalog;
36  import gov.nist.secauto.oscal.lib.model.CatalogGroup;
37  import gov.nist.secauto.oscal.lib.model.Control;
38  import gov.nist.secauto.oscal.lib.model.ControlPart;
39  import gov.nist.secauto.oscal.lib.model.Metadata;
40  import gov.nist.secauto.oscal.lib.model.Metadata.Location;
41  import gov.nist.secauto.oscal.lib.model.Metadata.Party;
42  import gov.nist.secauto.oscal.lib.model.Metadata.Role;
43  import gov.nist.secauto.oscal.lib.model.Parameter;
44  import gov.nist.secauto.oscal.lib.profile.resolver.support.AbstractCatalogEntityVisitor;
45  import gov.nist.secauto.oscal.lib.profile.resolver.support.IEntityItem;
46  import gov.nist.secauto.oscal.lib.profile.resolver.support.IEntityItem.ItemType;
47  import gov.nist.secauto.oscal.lib.profile.resolver.support.IIndexer;
48  import gov.nist.secauto.oscal.lib.profile.resolver.support.IIndexer.SelectionStatus;
49  
50  import org.apache.logging.log4j.LogManager;
51  import org.apache.logging.log4j.Logger;
52  
53  import java.util.EnumSet;
54  import java.util.stream.Collectors;
55  
56  import edu.umd.cs.findbugs.annotations.NonNull;
57  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
58  
59  public class FilterNonSelectedVisitor
60      extends AbstractCatalogEntityVisitor<FilterNonSelectedVisitor.Context, DefaultResult> {
61    private static final Logger LOGGER = LogManager.getLogger(FilterNonSelectedVisitor.class);
62    private static final FilterNonSelectedVisitor SINGLETON = new FilterNonSelectedVisitor();
63  
64    public static FilterNonSelectedVisitor instance() {
65      return SINGLETON;
66    }
67  
68    @SuppressWarnings("null")
69    protected FilterNonSelectedVisitor() {
70      // all other entity types are handled in a special way by this visitor
71      super(EnumSet.of(IEntityItem.ItemType.GROUP, IEntityItem.ItemType.CONTROL, IEntityItem.ItemType.PARAMETER));
72    }
73  
74    public void visitCatalog(@NonNull IDocumentNodeItem catalogItem, @NonNull IIndexer indexer) {
75      Context context = new Context(indexer);
76      IResult result = visitCatalog(catalogItem, context);
77  
78      IRootAssemblyNodeItem root = catalogItem.getRootAssemblyNodeItem();
79  
80      Catalog catalog = (Catalog) catalogItem.getValue();
81      result.applyTo(catalog);
82  
83      root.getModelItemsByName("metadata").forEach(child -> {
84        assert child != null;
85        visitMetadata(child, context);
86      });
87  
88      root.getModelItemsByName("back-matter").forEach(child -> {
89        assert child != null;
90        visitBackMatter(child, context);
91      });
92    }
93  
94    @Override
95    protected DefaultResult newDefaultResult(Context state) {
96      return new DefaultResult();
97    }
98  
99    @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 }