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