1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.oscal.lib.profile.resolver.support;
7   
8   import gov.nist.secauto.metaschema.core.metapath.MetapathExpression;
9   import gov.nist.secauto.metaschema.core.metapath.MetapathExpression.ResultType;
10  import gov.nist.secauto.metaschema.core.metapath.item.node.IModelNodeItem;
11  import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem;
12  import gov.nist.secauto.metaschema.core.util.CustomCollectors;
13  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
14  import gov.nist.secauto.oscal.lib.OscalBindingContext;
15  import gov.nist.secauto.oscal.lib.model.metadata.IProperty;
16  import gov.nist.secauto.oscal.lib.profile.resolver.support.IEntityItem.ItemType;
17  
18  import org.apache.logging.log4j.Level;
19  import org.apache.logging.log4j.LogManager;
20  import org.apache.logging.log4j.Logger;
21  
22  import java.util.Collection;
23  import java.util.HashSet;
24  import java.util.Map;
25  import java.util.Set;
26  import java.util.UUID;
27  import java.util.function.Function;
28  import java.util.function.Predicate;
29  import java.util.stream.Stream;
30  
31  import edu.umd.cs.findbugs.annotations.NonNull;
32  import edu.umd.cs.findbugs.annotations.Nullable;
33  
34  public interface IIndexer {
35    enum SelectionStatus {
36      SELECTED,
37      UNSELECTED,
38      UNKNOWN;
39    }
40  
41    MetapathExpression HAS_PROP_KEEP_METAPATH = MetapathExpression.compile(
42        "prop[@name='keep' and has-oscal-namespace('" + IProperty.OSCAL_NAMESPACE + "')]/@value = 'always'",
43        OscalBindingContext.OSCAL_STATIC_METAPATH_CONTEXT);
44  
45    Predicate<IEntityItem> KEEP_ENTITY_PREDICATE = entity -> entity.getReferenceCount() > 0
46        || (Boolean) ObjectUtils
47            .notNull(HAS_PROP_KEEP_METAPATH.evaluateAs(entity.getInstance(), ResultType.BOOLEAN));
48  
49    static boolean isReferencedEntity(@NonNull IEntityItem entity) {
50      return KEEP_ENTITY_PREDICATE.test(entity);
51    }
52  
53    /**
54     * Keep entities that have a reference count greater than zero or are required
55     * to be kept based on the "keep"="always property.
56     *
57     * @param entities
58     *          the entity items to filter
59     * @return the entities that pass the filter
60     */
61    static Stream<IEntityItem> getReferencedEntitiesAsStream(@NonNull Collection<IEntityItem> entities) {
62      return entities.stream().filter(KEEP_ENTITY_PREDICATE);
63    }
64  
65    /**
66     * Keep entities that have a reference count of zero or are not required to be
67     * kept based on the "keep"="always property.
68     *
69     * @param entities
70     *          the entity items to filter
71     * @return the entities that pass the filter
72     */
73    static Stream<IEntityItem> getUnreferencedEntitiesAsStream(@NonNull Collection<IEntityItem> entities) {
74      return entities.stream().filter(KEEP_ENTITY_PREDICATE.negate());
75    }
76  
77    /**
78     * Generates a stream of distinct items that have a reference count greater than
79     * zero or are required to be kept based on the "keep"="always property.
80     * <p>
81     * Distinct items are determined based on the item's key using the provided
82     * {@code keyMapper}.
83     *
84     * @param <T>
85     *          the item type
86     * @param <K>
87     *          the key type
88     * @param resolvedItems
89     *          a series of previously resolved items to add to prepend to the
90     *          stream
91     * @param importedEntityItems
92     *          a collection of new items to filter then append to the stream
93     * @param keyMapper
94     *          the key mapping function to determine the item's key
95     * @return the resulting series of items with duplicate items with the same key
96     *         removed
97     */
98    // TODO: Is this the right name for this method?
99    static <T, K> Stream<T> filterDistinct(
100       @NonNull Stream<T> resolvedItems,
101       @NonNull Collection<IEntityItem> importedEntityItems,
102       @NonNull Function<? super T, ? extends K> keyMapper) {
103     @SuppressWarnings("unchecked") Stream<T> importedStream = getReferencedEntitiesAsStream(importedEntityItems)
104         .map(entity -> (T) entity.getInstanceValue());
105 
106     return CustomCollectors.distinctByKey(
107         ObjectUtils.notNull(Stream.concat(resolvedItems, importedStream)),
108         keyMapper,
109         (key, value1, value2) -> value2);
110   }
111 
112   static void logIndex(@NonNull IIndexer indexer, @NonNull Level logLevel) {
113     Logger logger = LogManager.getLogger();
114 
115     Set<INodeItem> indexedItems = new HashSet<>();
116     if (logger.isEnabled(logLevel)) {
117       for (ItemType itemType : ItemType.values()) {
118         assert itemType != null;
119         for (IEntityItem item : indexer.getEntitiesByItemType(itemType)) {
120           INodeItem nodeItem = item.getInstance();
121           indexedItems.add(nodeItem);
122           logger.atLevel(logLevel).log("{} {}: selected: {}, reference count: {}",
123               itemType.name(),
124               item.isIdentifierReassigned() ? item.getIdentifier() + "(" + item.getOriginalIdentifier() + ")"
125                   : item.getIdentifier(),
126               indexer.getSelectionStatus(nodeItem),
127               item.getReferenceCount());
128         }
129       }
130     }
131 
132     for (Map.Entry<INodeItem, SelectionStatus> entry : indexer.getSelectionStatusMap().entrySet()) {
133       INodeItem nodeItem = entry.getKey();
134       if (!indexedItems.contains(nodeItem)) {
135         Object value = nodeItem.getValue();
136         logger.atLevel(logLevel).log("{}: {}", value == null ? "(null)" : value.getClass().getName(), entry.getValue());
137       }
138     }
139   }
140 
141   @NonNull
142   IEntityItem addRole(@NonNull IModelNodeItem<?, ?> role);
143 
144   @NonNull
145   IEntityItem addLocation(@NonNull IModelNodeItem<?, ?> location);
146 
147   @NonNull
148   IEntityItem addParty(@NonNull IModelNodeItem<?, ?> party);
149 
150   @Nullable
151   IEntityItem addGroup(@NonNull IModelNodeItem<?, ?> group);
152 
153   @NonNull
154   IEntityItem addControl(@NonNull IModelNodeItem<?, ?> control);
155 
156   @NonNull
157   IEntityItem addParameter(@NonNull IModelNodeItem<?, ?> parameter);
158 
159   @Nullable
160   IEntityItem addPart(@NonNull IModelNodeItem<?, ?> part);
161 
162   @NonNull
163   IEntityItem addResource(@NonNull IModelNodeItem<?, ?> resource);
164 
165   @NonNull
166   Collection<IEntityItem> getEntitiesByItemType(@NonNull IEntityItem.ItemType itemType);
167 
168   @Nullable
169   default IEntityItem getEntity(@NonNull IEntityItem.ItemType itemType, @NonNull UUID identifier) {
170     return getEntity(itemType, ObjectUtils.notNull(identifier.toString()), false);
171   }
172 
173   /**
174    * Lookup an item of the given {@code itemType} having the given
175    * {@code identifier}.
176    * <p>
177    * Will normalize the case of a UUID-based identifier.
178    *
179    * @param itemType
180    *          the type of item to search for
181    * @param identifier
182    *          the identifier to lookup
183    * @return the matching item or {@code null} if no match was found
184    */
185   @Nullable
186   default IEntityItem getEntity(@NonNull IEntityItem.ItemType itemType, @NonNull String identifier) {
187     return getEntity(itemType, identifier, itemType.isUuid());
188   }
189 
190   /**
191    * Lookup an item of the given {@code itemType} having the given
192    * {@code identifier}.
193    * <p>
194    * Will normalize the case of a UUID-based the identifier when requested.
195    *
196    * @param itemType
197    *          the type of item to search for
198    * @param identifier
199    *          the identifier to lookup
200    * @param normalize
201    *          {@code true} if the identifier case should be normalized or
202    *          {@code false} otherwise
203    * @return the matching item or {@code null} if no match was found
204    */
205   @Nullable
206   IEntityItem getEntity(@NonNull IEntityItem.ItemType itemType, @NonNull String identifier, boolean normalize);
207 
208   boolean removeItem(@NonNull IEntityItem entity);
209 
210   boolean isSelected(@NonNull IEntityItem entity);
211 
212   Map<INodeItem, SelectionStatus> getSelectionStatusMap();
213 
214   @NonNull
215   SelectionStatus getSelectionStatus(@NonNull INodeItem item);
216 
217   void setSelectionStatus(@NonNull INodeItem item, @NonNull SelectionStatus selectionStatus);
218 
219   void resetSelectionStatus();
220 
221   void append(@NonNull IIndexer result);
222 
223   /**
224    * Get a copy of the entity map.
225    *
226    * @return the copy
227    */
228   @NonNull
229   Map<ItemType, Map<String, IEntityItem>> getEntities();
230 }