1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.oscal.lib.profile.resolver.merge;
7   
8   import gov.nist.secauto.metaschema.core.metapath.item.node.IAssemblyNodeItem;
9   import gov.nist.secauto.metaschema.core.metapath.item.node.IDocumentNodeItem;
10  import gov.nist.secauto.metaschema.core.metapath.item.node.IRootAssemblyNodeItem;
11  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
12  import gov.nist.secauto.oscal.lib.model.BackMatter.Resource;
13  import gov.nist.secauto.oscal.lib.model.CatalogGroup;
14  import gov.nist.secauto.oscal.lib.model.Control;
15  import gov.nist.secauto.oscal.lib.model.ControlPart;
16  import gov.nist.secauto.oscal.lib.model.Metadata.Location;
17  import gov.nist.secauto.oscal.lib.model.Metadata.Party;
18  import gov.nist.secauto.oscal.lib.model.Metadata.Role;
19  import gov.nist.secauto.oscal.lib.model.Parameter;
20  import gov.nist.secauto.oscal.lib.profile.resolver.policy.ReferenceCountingVisitor;
21  import gov.nist.secauto.oscal.lib.profile.resolver.selection.DefaultResult;
22  import gov.nist.secauto.oscal.lib.profile.resolver.selection.FilterNonSelectedVisitor;
23  import gov.nist.secauto.oscal.lib.profile.resolver.support.AbstractCatalogEntityVisitor;
24  import gov.nist.secauto.oscal.lib.profile.resolver.support.IEntityItem;
25  import gov.nist.secauto.oscal.lib.profile.resolver.support.IEntityItem.ItemType;
26  import gov.nist.secauto.oscal.lib.profile.resolver.support.IIndexer;
27  import gov.nist.secauto.oscal.lib.profile.resolver.support.IIndexer.SelectionStatus;
28  
29  import java.net.URI;
30  import java.util.EnumSet;
31  import java.util.UUID;
32  
33  import edu.umd.cs.findbugs.annotations.NonNull;
34  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
35  
36  public class FlatteningStructuringVisitor
37      extends AbstractCatalogEntityVisitor<IIndexer, Void> {
38    private static final FlatteningStructuringVisitor SINGLETON = new FlatteningStructuringVisitor();
39  
40    @SuppressFBWarnings(value = "SING_SINGLETON_GETTER_NOT_SYNCHRONIZED", justification = "class initialization")
41    public static FlatteningStructuringVisitor instance() {
42      return SINGLETON;
43    }
44  
45    @SuppressFBWarnings(value = "SING_SINGLETON_HAS_NONPRIVATE_CONSTRUCTOR",
46        justification = "public constructor allows for extension usecases")
47    public FlatteningStructuringVisitor() {
48      super(ObjectUtils.notNull(EnumSet.of(ItemType.GROUP, ItemType.CONTROL)));
49    }
50  
51    @Override
52    protected Void newDefaultResult(IIndexer state) {
53      // do nothing
54      return null;
55    }
56  
57    @Override
58    protected Void aggregateResults(Void first, Void second, IIndexer state) {
59      // do nothing
60      return null;
61    }
62  
63    @Override
64    public Void visitCatalog(@NonNull IDocumentNodeItem catalogItem, IIndexer index) {
65      index.resetSelectionStatus();
66  
67      index.setSelectionStatus(catalogItem, SelectionStatus.SELECTED);
68      super.visitCatalog(catalogItem, index);
69  
70      for (ItemType itemType : ItemType.values()) {
71        assert itemType != null;
72        for (IEntityItem item : index.getEntitiesByItemType(itemType)) {
73          item.resetReferenceCount();
74        }
75      }
76  
77      // process references, looking for orphaned links to groups
78      URI catalogUri = ObjectUtils.requireNonNull(catalogItem.getDocumentUri());
79      ReferenceCountingVisitor.instance().visitCatalog(catalogItem, index, catalogUri);
80  
81      FlatteningFilterNonSelectedVisitor.instance().visitCatalog(catalogItem, index);
82      return null;
83    }
84  
85    @Override
86    public Void visitGroup(
87        IAssemblyNodeItem item,
88        Void childResult,
89        IIndexer index) {
90      CatalogGroup group = ObjectUtils.requireNonNull((CatalogGroup) item.getValue());
91      String id = group.getId();
92      if (id != null) {
93        IEntityItem entity = index.getEntity(ItemType.GROUP, id);
94        assert entity != null;
95        // refresh the instance
96        entity.setInstance(item);
97      }
98  
99      index.setSelectionStatus(item, SelectionStatus.UNSELECTED);
100     handlePartSelection(item, index, SelectionStatus.UNSELECTED);
101     return super.visitGroup(item, childResult, index);
102   }
103 
104   @Override
105   public Void visitControl(
106       IAssemblyNodeItem item,
107       Void childResult,
108       IIndexer index) {
109     Control control = ObjectUtils.requireNonNull((Control) item.getValue());
110     String id = ObjectUtils.requireNonNull(control.getId());
111     IEntityItem entity = index.getEntity(ItemType.CONTROL, id);
112     assert entity != null;
113     // refresh the instance
114     entity.setInstance(item);
115 
116     index.setSelectionStatus(item, SelectionStatus.SELECTED);
117     handlePartSelection(item, index, SelectionStatus.SELECTED);
118     return null;
119   }
120 
121   @Override
122   protected Void visitParameter(
123       IAssemblyNodeItem item,
124       IAssemblyNodeItem catalogOrGroupOrControl,
125       IIndexer index) {
126     Parameter parameter = ObjectUtils.requireNonNull((Parameter) item.getValue());
127     String id = ObjectUtils.requireNonNull(parameter.getId());
128     IEntityItem entity = index.getEntity(ItemType.PARAMETER, id);
129     assert entity != null;
130     // refresh the instance
131     entity.setInstance(item);
132 
133     return null;
134   }
135 
136   @Override
137   protected void visitRole(
138       IAssemblyNodeItem item,
139       IAssemblyNodeItem metadataItem,
140       IIndexer index) {
141     Role role = ObjectUtils.requireNonNull((Role) item.getValue());
142     String id = ObjectUtils.requireNonNull(role.getId());
143     IEntityItem entity = index.getEntity(ItemType.ROLE, id);
144     assert entity != null;
145     // refresh the instance
146     entity.setInstance(item);
147   }
148 
149   @Override
150   protected void visitLocation(
151       IAssemblyNodeItem item,
152       IAssemblyNodeItem metadataItem,
153       IIndexer index) {
154     Location location = ObjectUtils.requireNonNull((Location) item.getValue());
155     UUID uuid = ObjectUtils.requireNonNull(location.getUuid());
156     IEntityItem entity = index.getEntity(ItemType.LOCATION, uuid);
157     assert entity != null;
158     // refresh the instance
159     entity.setInstance(item);
160   }
161 
162   @Override
163   protected void visitParty(
164       IAssemblyNodeItem item,
165       IAssemblyNodeItem metadataItem,
166       IIndexer index) {
167     Party location = ObjectUtils.requireNonNull((Party) item.getValue());
168     UUID uuid = ObjectUtils.requireNonNull(location.getUuid());
169     IEntityItem entity = index.getEntity(ItemType.PARTY, uuid);
170     assert entity != null;
171     // refresh the instance
172     entity.setInstance(item);
173   }
174 
175   @Override
176   protected void visitResource(
177       IAssemblyNodeItem item,
178       IRootAssemblyNodeItem rootItem,
179       IIndexer index) {
180     Resource location = ObjectUtils.requireNonNull((Resource) item.getValue());
181     UUID uuid = ObjectUtils.requireNonNull(location.getUuid());
182     IEntityItem entity = index.getEntity(ItemType.RESOURCE, uuid);
183     assert entity != null;
184     // refresh the instance
185     entity.setInstance(item);
186   }
187 
188   private static void handlePartSelection(
189       @NonNull IAssemblyNodeItem groupOrControlItem,
190       @NonNull IIndexer index,
191       @NonNull SelectionStatus selectionStatus) {
192     CHILD_PART_METAPATH.evaluate(groupOrControlItem).stream()
193         .map(item -> (IAssemblyNodeItem) item)
194         .forEachOrdered(partItem -> {
195           index.setSelectionStatus(ObjectUtils.requireNonNull(partItem), selectionStatus);
196 
197           ControlPart part = ObjectUtils.requireNonNull((ControlPart) partItem.getValue());
198           String id = part.getId();
199           if (id != null) {
200             IEntityItem entity = index.getEntity(ItemType.PART, id);
201             assert entity != null;
202             // refresh the instance
203             entity.setInstance(partItem);
204           }
205         });
206   }
207 
208   private static final class FlatteningFilterNonSelectedVisitor
209       extends FilterNonSelectedVisitor {
210     private static final FlatteningFilterNonSelectedVisitor SINGLETON = new FlatteningFilterNonSelectedVisitor();
211 
212     @SuppressFBWarnings(value = "SING_SINGLETON_GETTER_NOT_SYNCHRONIZED", justification = "class initialization")
213     public static FlatteningFilterNonSelectedVisitor instance() {
214       return SINGLETON;
215     }
216 
217     @Override
218     public DefaultResult visitControl(IAssemblyNodeItem item, DefaultResult childResult,
219         Context context) {
220       assert childResult != null;
221 
222       Control control = ObjectUtils.requireNonNull((Control) item.getValue());
223       IIndexer index = context.getIndexer();
224       // this control should always be found in the index
225       IEntityItem entity = ObjectUtils.requireNonNull(
226           index.getEntity(ItemType.CONTROL, ObjectUtils.requireNonNull(control.getId()), false));
227 
228       IAssemblyNodeItem parent = ObjectUtils.notNull(item.getParentContentNodeItem());
229       DefaultResult retval = new DefaultResult();
230       if (SelectionStatus.SELECTED.equals(index.getSelectionStatus(item))) {
231         // keep this control
232 
233         // always promote the control and any children
234         retval.promoteControl(control);
235 
236         retval.appendPromoted(childResult);
237         childResult.applyRemovesTo(control);
238 
239         if (parent.getValue() instanceof Control && SelectionStatus.SELECTED.equals(index.getSelectionStatus(parent))) {
240           retval.removeControl(control);
241         }
242       } else {
243         // remove this control and promote any needed children
244 
245         if (SelectionStatus.SELECTED.equals(index.getSelectionStatus(parent))) {
246           retval.removeControl(control);
247         }
248         retval.appendPromoted(ObjectUtils.notNull(childResult));
249         index.removeItem(entity);
250 
251         // remove any associated parts from the index
252         removePartsFromIndex(item, index);
253       }
254       return retval;
255     }
256   }
257 }