View Javadoc
1   /*
2    * Portions of this software was developed by employees of the National Institute
3    * of Standards and Technology (NIST), an agency of the Federal Government and is
4    * being made available as a public service. Pursuant to title 17 United States
5    * Code Section 105, works of NIST employees are not subject to copyright
6    * protection in the United States. This software may be subject to foreign
7    * copyright. Permission in the United States and in foreign countries, to the
8    * extent that NIST may hold copyright, to use, copy, modify, create derivative
9    * works, and distribute this software and its documentation without fee is hereby
10   * granted on a non-exclusive basis, provided that this notice and disclaimer
11   * of warranty appears in all copies.
12   *
13   * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
14   * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
15   * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
16   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
17   * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
18   * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
19   * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
20   * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
21   * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
22   * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
23   * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
24   * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
25   */
26  
27  package gov.nist.secauto.oscal.lib.profile.resolver.selection;
28  
29  import gov.nist.secauto.metaschema.model.common.metapath.MetapathExpression;
30  import gov.nist.secauto.metaschema.model.common.metapath.format.IPathFormatter;
31  import gov.nist.secauto.metaschema.model.common.metapath.item.IRequiredValueAssemblyNodeItem;
32  import gov.nist.secauto.metaschema.model.common.metapath.item.IRequiredValueModelNodeItem;
33  import gov.nist.secauto.metaschema.model.common.util.ObjectUtils;
34  import gov.nist.secauto.oscal.lib.model.CatalogGroup;
35  import gov.nist.secauto.oscal.lib.model.Control;
36  import gov.nist.secauto.oscal.lib.profile.resolver.support.IIndexer;
37  
38  import org.apache.commons.lang3.tuple.Pair;
39  
40  import java.util.Map;
41  import java.util.concurrent.ConcurrentHashMap;
42  
43  import edu.umd.cs.findbugs.annotations.NonNull;
44  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
45  
46  public class ControlSelectionState implements IControlSelectionState {
47    private static final MetapathExpression GROUP_CHILDREN = MetapathExpression.compile("group|descendant::control");
48  
49    @NonNull
50    private final IIndexer index;
51    @NonNull
52    private final IControlFilter filter;
53    @NonNull
54    private final Map<IRequiredValueModelNodeItem, SelectionState> itemSelectionState = new ConcurrentHashMap<>();
55  
56    @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "provides intentional access to index state")
57    public ControlSelectionState(@NonNull IIndexer index, @NonNull IControlFilter filter) {
58      this.index = index;
59      this.filter = filter;
60    }
61  
62    @Override
63    @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "provides intentional access to index state")
64    public IIndexer getIndex() {
65      return index;
66    }
67  
68    @NonNull
69    public IControlFilter getFilter() {
70      return filter;
71    }
72  
73    @Override
74    public boolean isSelected(@NonNull IRequiredValueModelNodeItem item) {
75      return getSelectionState(item).isSelected();
76    }
77  
78    @NonNull
79    protected SelectionState getSelectionState(@NonNull IRequiredValueModelNodeItem item) {
80      SelectionState retval = itemSelectionState.get(item);
81      if (retval == null) {
82        Object itemValue = ObjectUtils.requireNonNull(item.getValue());
83  
84        if (itemValue instanceof Control) {
85          Control control = (Control) itemValue;
86  
87          // get the parent control if the parent is a control
88          IRequiredValueAssemblyNodeItem parentItem = ObjectUtils.requireNonNull(item.getParentContentNodeItem());
89          Object parentValue = parentItem.getValue();
90          Control parentControl = parentValue instanceof Control ? (Control) parentValue : null;
91  
92          boolean defaultMatch = false;
93          if (parentControl != null) {
94            SelectionState parentSelectionState = getSelectionState(parentItem);
95            defaultMatch = parentSelectionState.isSelected() && parentSelectionState.isWithChildren();
96          }
97  
98          Pair<Boolean, Boolean> matchResult = getFilter().match(control, defaultMatch);
99          boolean selected = matchResult.getLeft();
100         boolean withChildren = matchResult.getRight();
101 
102         retval = new SelectionState(selected, withChildren);
103 
104       } else if (itemValue instanceof CatalogGroup) {
105         // get control selection status
106         boolean selected = GROUP_CHILDREN.evaluate(item).asStream()
107             .map(child -> {
108               return getSelectionState((IRequiredValueModelNodeItem) ObjectUtils.requireNonNull(child)).isSelected();
109             })
110             .reduce(false, (first, second) -> first || second);
111 
112         retval = new SelectionState(selected, false);
113       } else {
114         throw new IllegalStateException(
115             String.format("Selection not supported for type '%s' at path '%s'",
116                 itemValue.getClass().getName(),
117                 item.toPath(IPathFormatter.METAPATH_PATH_FORMATER)));
118       }
119       itemSelectionState.put(item, retval);
120     }
121     return retval;
122   }
123 
124   private static final class SelectionState {
125     private final boolean selected;
126     private final boolean withChildren;
127 
128     private SelectionState(boolean selected, boolean withChildren) {
129       this.selected = selected;
130       this.withChildren = withChildren;
131     }
132 
133     public boolean isSelected() {
134       return selected;
135     }
136 
137     public boolean isWithChildren() {
138       return selected && withChildren;
139     }
140   }
141 }