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