1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package dev.metaschema.oscal.lib.profile.resolver.merge;
7   
8   import java.util.EnumSet;
9   import java.util.UUID;
10  
11  import dev.metaschema.core.metapath.item.node.IAssemblyNodeItem;
12  import dev.metaschema.core.metapath.item.node.IDocumentNodeItem;
13  import dev.metaschema.core.metapath.item.node.IRootAssemblyNodeItem;
14  import dev.metaschema.core.util.ObjectUtils;
15  import dev.metaschema.oscal.lib.model.BackMatter.Resource;
16  import dev.metaschema.oscal.lib.model.CatalogGroup;
17  import dev.metaschema.oscal.lib.model.Control;
18  import dev.metaschema.oscal.lib.model.ControlPart;
19  import dev.metaschema.oscal.lib.model.Metadata.Location;
20  import dev.metaschema.oscal.lib.model.Metadata.Party;
21  import dev.metaschema.oscal.lib.model.Metadata.Role;
22  import dev.metaschema.oscal.lib.model.Parameter;
23  import dev.metaschema.oscal.lib.profile.resolver.ProfileResolver;
24  import dev.metaschema.oscal.lib.profile.resolver.policy.ReferenceCountingVisitor;
25  import dev.metaschema.oscal.lib.profile.resolver.selection.DefaultResult;
26  import dev.metaschema.oscal.lib.profile.resolver.selection.FilterNonSelectedVisitor;
27  import dev.metaschema.oscal.lib.profile.resolver.support.AbstractCatalogEntityVisitor;
28  import dev.metaschema.oscal.lib.profile.resolver.support.IEntityItem;
29  import dev.metaschema.oscal.lib.profile.resolver.support.IEntityItem.ItemType;
30  import dev.metaschema.oscal.lib.profile.resolver.support.IIndexer;
31  import dev.metaschema.oscal.lib.profile.resolver.support.IIndexer.SelectionStatus;
32  import edu.umd.cs.findbugs.annotations.NonNull;
33  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
34  
35  public class FlatteningStructuringVisitor
36      extends AbstractCatalogEntityVisitor<IIndexer, Void> {
37    @NonNull
38    private final ProfileResolver.UriResolver uriResolver;
39  
40    public FlatteningStructuringVisitor(@NonNull ProfileResolver.UriResolver uriResolver) {
41      super(ObjectUtils.notNull(EnumSet.of(ItemType.GROUP, ItemType.CONTROL)));
42      this.uriResolver = uriResolver;
43    }
44  
45    @Override
46    protected Void newDefaultResult(IIndexer state) {
47      // do nothing
48      return null;
49    }
50  
51    @Override
52    protected Void aggregateResults(Void first, Void second, IIndexer state) {
53      // do nothing
54      return null;
55    }
56  
57    @Override
58    public Void visitCatalog(@NonNull IDocumentNodeItem catalogItem, IIndexer index) {
59      index.resetSelectionStatus();
60  
61      index.setSelectionStatus(catalogItem, SelectionStatus.SELECTED);
62      super.visitCatalog(catalogItem, index);
63  
64      for (ItemType itemType : ItemType.values()) {
65        assert itemType != null;
66        for (IEntityItem item : index.getEntitiesByItemType(itemType)) {
67          item.resetReferenceCount();
68        }
69      }
70  
71      // process references, looking for orphaned links to groups
72      ReferenceCountingVisitor.instance().visitCatalog(catalogItem, index, uriResolver);
73  
74      FlatteningFilterNonSelectedVisitor.instance().visitCatalog(catalogItem, index);
75      return null;
76    }
77  
78    @Override
79    public Void visitGroup(
80        IAssemblyNodeItem item,
81        Void childResult,
82        IIndexer index) {
83      CatalogGroup group = ObjectUtils.requireNonNull((CatalogGroup) item.getValue());
84      String id = group.getId();
85      if (id != null) {
86        IEntityItem entity = index.getEntity(ItemType.GROUP, id);
87        assert entity != null;
88        // refresh the instance
89        entity.setInstance(item);
90      }
91  
92      index.setSelectionStatus(item, SelectionStatus.UNSELECTED);
93      handlePartSelection(item, index, SelectionStatus.UNSELECTED);
94      return super.visitGroup(item, childResult, index);
95    }
96  
97    @Override
98    public Void visitControl(
99        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 }