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}