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.merge;
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.Resource;
034import gov.nist.secauto.oscal.lib.model.CatalogGroup;
035import gov.nist.secauto.oscal.lib.model.Control;
036import gov.nist.secauto.oscal.lib.model.ControlPart;
037import gov.nist.secauto.oscal.lib.model.Metadata.Location;
038import gov.nist.secauto.oscal.lib.model.Metadata.Party;
039import gov.nist.secauto.oscal.lib.model.Metadata.Role;
040import gov.nist.secauto.oscal.lib.model.Parameter;
041import gov.nist.secauto.oscal.lib.profile.resolver.policy.ReferenceCountingVisitor;
042import gov.nist.secauto.oscal.lib.profile.resolver.selection.DefaultResult;
043import gov.nist.secauto.oscal.lib.profile.resolver.selection.FilterNonSelectedVisitor;
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 java.util.EnumSet;
051import java.util.UUID;
052
053import edu.umd.cs.findbugs.annotations.NonNull;
054
055public class FlatteningStructuringVisitor
056    extends AbstractCatalogEntityVisitor<IIndexer, Void> {
057  private static final FlatteningStructuringVisitor SINGLETON = new FlatteningStructuringVisitor();
058
059  public static FlatteningStructuringVisitor instance() {
060    return SINGLETON;
061  }
062
063  public FlatteningStructuringVisitor() {
064    super(ObjectUtils.notNull(EnumSet.of(ItemType.GROUP, ItemType.CONTROL)));
065  }
066
067  @Override
068  protected Void newDefaultResult(IIndexer state) {
069    // do nothing
070    return null;
071  }
072
073  @Override
074  protected Void aggregateResults(Void first, Void second, IIndexer state) {
075    // do nothing
076    return null;
077  }
078
079  @Override
080  public Void visitCatalog(@NonNull IDocumentNodeItem catalogItem, IIndexer index) {
081    index.resetSelectionStatus();
082
083    index.setSelectionStatus(catalogItem, SelectionStatus.SELECTED);
084    super.visitCatalog(catalogItem, index);
085
086    for (ItemType itemType : ItemType.values()) {
087      assert itemType != null;
088      for (IEntityItem item : index.getEntitiesByItemType(itemType)) {
089        item.resetReferenceCount();
090      }
091    }
092
093    // process references, looking for orphaned links to groups
094    ReferenceCountingVisitor.instance().visitCatalog(catalogItem, index, catalogItem.getDocumentUri());
095
096    FlatteningFilterNonSelectedVisitor.instance().visitCatalog(catalogItem, index);
097    return null;
098  }
099
100  @Override
101  public Void visitGroup(IRequiredValueModelNodeItem item, Void childResult, IIndexer index) {
102    CatalogGroup group = (CatalogGroup) item.getValue();
103    String id = group.getId();
104    if (id != null) {
105      IEntityItem entity = index.getEntity(ItemType.GROUP, id);
106      assert entity != null;
107      // refresh the instance
108      entity.setInstance(item);
109    }
110
111    index.setSelectionStatus(item, SelectionStatus.UNSELECTED);
112    handlePartSelection(item, index, SelectionStatus.UNSELECTED);
113    return super.visitGroup(item, childResult, index);
114  }
115
116  @Override
117  public Void visitControl(IRequiredValueModelNodeItem item, Void childResult, IIndexer index) {
118    Control control = (Control) item.getValue();
119    String id = ObjectUtils.requireNonNull(control.getId());
120    IEntityItem entity = index.getEntity(ItemType.CONTROL, id);
121    assert entity != null;
122    // refresh the instance
123    entity.setInstance(item);
124
125    index.setSelectionStatus(item, SelectionStatus.SELECTED);
126    handlePartSelection(item, index, SelectionStatus.SELECTED);
127    return null;
128  }
129
130  @Override
131  protected Void visitParameter(IRequiredValueModelNodeItem item,
132      IRequiredValueModelNodeItem catalogOrGroupOrControl, IIndexer index) {
133    Parameter parameter = (Parameter) item.getValue();
134    String id = ObjectUtils.requireNonNull(parameter.getId());
135    IEntityItem entity = index.getEntity(ItemType.PARAMETER, id);
136    assert entity != null;
137    // refresh the instance
138    entity.setInstance(item);
139
140    return null;
141  }
142
143  @Override
144  protected void visitRole(IRequiredValueModelNodeItem item, IRequiredValueModelNodeItem metadataItem,
145      IIndexer index) {
146    Role role = (Role) item.getValue();
147    String id = ObjectUtils.requireNonNull(role.getId());
148    IEntityItem entity = index.getEntity(ItemType.ROLE, id);
149    assert entity != null;
150    // refresh the instance
151    entity.setInstance(item);
152  }
153
154  @Override
155  protected void visitLocation(IRequiredValueModelNodeItem item, IRequiredValueModelNodeItem metadataItem,
156      IIndexer index) {
157    Location location = (Location) item.getValue();
158    UUID uuid = ObjectUtils.requireNonNull(location.getUuid());
159    IEntityItem entity = index.getEntity(ItemType.LOCATION, uuid);
160    assert entity != null;
161    // refresh the instance
162    entity.setInstance(item);
163  }
164
165  @Override
166  protected void visitParty(IRequiredValueModelNodeItem item, IRequiredValueModelNodeItem metadataItem,
167      IIndexer index) {
168    Party location = (Party) item.getValue();
169    UUID uuid = ObjectUtils.requireNonNull(location.getUuid());
170    IEntityItem entity = index.getEntity(ItemType.PARTY, uuid);
171    assert entity != null;
172    // refresh the instance
173    entity.setInstance(item);
174  }
175
176  @Override
177  protected void visitResource(IRequiredValueModelNodeItem item, IRootAssemblyNodeItem rootItem,
178      IIndexer index) {
179    Resource location = (Resource) item.getValue();
180    UUID uuid = ObjectUtils.requireNonNull(location.getUuid());
181    IEntityItem entity = index.getEntity(ItemType.RESOURCE, uuid);
182    assert entity != null;
183    // refresh the instance
184    entity.setInstance(item);
185  }
186
187  private static void handlePartSelection(
188      @NonNull IRequiredValueModelNodeItem groupOrControlItem,
189      @NonNull IIndexer index,
190      @NonNull SelectionStatus selectionStatus) {
191    CHILD_PART_METAPATH.evaluate(groupOrControlItem).asStream()
192        .map(item -> (IRequiredValueModelNodeItem) item)
193        .forEachOrdered(partItem -> {
194          index.setSelectionStatus(ObjectUtils.requireNonNull(partItem), selectionStatus);
195
196          ControlPart part = (ControlPart) partItem.getValue();
197          String id = part.getId();
198          if (id != null) {
199            IEntityItem entity = index.getEntity(ItemType.PART, id);
200            assert entity != null;
201            // refresh the instance
202            entity.setInstance(partItem);
203          }
204        });
205  }
206
207  private static final class FlatteningFilterNonSelectedVisitor
208      extends FilterNonSelectedVisitor {
209    private static final FlatteningFilterNonSelectedVisitor SINGLETON = new FlatteningFilterNonSelectedVisitor();
210
211    public static FlatteningFilterNonSelectedVisitor instance() {
212      return SINGLETON;
213    }
214
215    @Override
216    public DefaultResult visitControl(IRequiredValueModelNodeItem item, DefaultResult childResult,
217        Context context) {
218      assert childResult != null;
219
220      Control control = (Control) item.getValue();
221      IIndexer index = context.getIndexer();
222      // this control should always be found in the index
223      IEntityItem entity = ObjectUtils.requireNonNull(
224          index.getEntity(ItemType.CONTROL, ObjectUtils.requireNonNull(control.getId()), false));
225
226      IRequiredValueModelNodeItem parent = ObjectUtils.notNull(item.getParentContentNodeItem());
227      DefaultResult retval = new DefaultResult();
228      if (SelectionStatus.SELECTED.equals(index.getSelectionStatus(item))) {
229        // keep this control
230
231        // always promote the control and any children
232        retval.promoteControl(control);
233
234        retval.appendPromoted(childResult);
235        childResult.applyRemovesTo(control);
236
237        if (parent.getValue() instanceof Control && SelectionStatus.SELECTED.equals(index.getSelectionStatus(parent))) {
238          retval.removeControl(control);
239        }
240      } else {
241        // remove this control and promote any needed children
242
243        if (SelectionStatus.SELECTED.equals(index.getSelectionStatus(parent))) {
244          retval.removeControl(control);
245        }
246        retval.appendPromoted(ObjectUtils.notNull(childResult));
247        index.removeItem(entity);
248
249        // remove any associated parts from the index
250        removePartsFromIndex(item, index);
251      }
252      return retval;
253    }
254  }
255}