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")
104     Stream<T> importedStream = getReferencedEntitiesAsStream(importedEntityItems)
105         .map(entity -> (T) entity.getInstanceValue());
106 
107     return CustomCollectors.distinctByKey(
108         ObjectUtils.notNull(Stream.concat(resolvedItems, importedStream)),
109         keyMapper,
110         (key, value1, value2) -> value2);
111   }
112 
113   static void logIndex(@NonNull IIndexer indexer, @NonNull Level logLevel) {
114     Logger logger = LogManager.getLogger();
115 
116     Set<INodeItem> indexedItems = new HashSet<>();
117     if (logger.isEnabled(logLevel)) {
118       for (ItemType itemType : ItemType.values()) {
119         assert itemType != null;
120         for (IEntityItem item : indexer.getEntitiesByItemType(itemType)) {
121           INodeItem nodeItem = item.getInstance();
122           indexedItems.add(nodeItem);
123           logger.atLevel(logLevel).log("{} {}: selected: {}, reference count: {}",
124               itemType.name(),
125               item.isIdentifierReassigned() ? item.getIdentifier() + "(" + item.getOriginalIdentifier() + ")"
126                   : item.getIdentifier(),
127               indexer.getSelectionStatus(nodeItem),
128               item.getReferenceCount());
129         }
130       }
131     }
132 
133     for (Map.Entry<INodeItem, SelectionStatus> entry : indexer.getSelectionStatusMap().entrySet()) {
134       INodeItem nodeItem = entry.getKey();
135       if (!indexedItems.contains(nodeItem)) {
136         Object value = nodeItem.getValue();
137         logger.atLevel(logLevel).log("{}: {}", value == null ? "(null)" : value.getClass().getName(), entry.getValue());
138       }
139     }
140   }
141 
142   @NonNull
143   IEntityItem addRole(@NonNull IModelNodeItem<?, ?> role);
144 
145   @NonNull
146   IEntityItem addLocation(@NonNull IModelNodeItem<?, ?> location);
147 
148   @NonNull
149   IEntityItem addParty(@NonNull IModelNodeItem<?, ?> party);
150 
151   @Nullable
152   IEntityItem addGroup(@NonNull IModelNodeItem<?, ?> group);
153 
154   @NonNull
155   IEntityItem addControl(@NonNull IModelNodeItem<?, ?> control);
156 
157   @NonNull
158   IEntityItem addParameter(@NonNull IModelNodeItem<?, ?> parameter);
159 
160   @Nullable
161   IEntityItem addPart(@NonNull IModelNodeItem<?, ?> part);
162 
163   @NonNull
164   IEntityItem addResource(@NonNull IModelNodeItem<?, ?> resource);
165 
166   @NonNull
167   Collection<IEntityItem> getEntitiesByItemType(@NonNull IEntityItem.ItemType itemType);
168 
169   @Nullable
170   default IEntityItem getEntity(@NonNull IEntityItem.ItemType itemType, @NonNull UUID identifier) {
171     return getEntity(itemType, ObjectUtils.notNull(identifier.toString()), false);
172   }
173 
174   /**
175    * Lookup an item of the given {@code itemType} having the given
176    * {@code identifier}.
177    * <p>
178    * Will normalize the case of a UUID-based identifier.
179    *
180    * @param itemType
181    *          the type of item to search for
182    * @param identifier
183    *          the identifier to lookup
184    * @return the matching item or {@code null} if no match was found
185    */
186   @Nullable
187   default IEntityItem getEntity(@NonNull IEntityItem.ItemType itemType, @NonNull String identifier) {
188     return getEntity(itemType, identifier, itemType.isUuid());
189   }
190 
191   /**
192    * Lookup an item of the given {@code itemType} having the given
193    * {@code identifier}.
194    * <p>
195    * Will normalize the case of a UUID-based the identifier when requested.
196    *
197    * @param itemType
198    *          the type of item to search for
199    * @param identifier
200    *          the identifier to lookup
201    * @param normalize
202    *          {@code true} if the identifier case should be normalized or
203    *          {@code false} otherwise
204    * @return the matching item or {@code null} if no match was found
205    */
206   @Nullable
207   IEntityItem getEntity(@NonNull IEntityItem.ItemType itemType, @NonNull String identifier, boolean normalize);
208 
209   boolean removeItem(@NonNull IEntityItem entity);
210 
211   boolean isSelected(@NonNull IEntityItem entity);
212 
213   Map<INodeItem, SelectionStatus> getSelectionStatusMap();
214 
215   @NonNull
216   SelectionStatus getSelectionStatus(@NonNull INodeItem item);
217 
218   void setSelectionStatus(@NonNull INodeItem item, @NonNull SelectionStatus selectionStatus);
219 
220   void resetSelectionStatus();
221 
222   void append(@NonNull IIndexer result);
223 
224   /**
225    * Get a copy of the entity map.
226    *
227    * @return the copy
228    */
229   @NonNull
230   Map<ItemType, Map<String, IEntityItem>> getEntities();
231 }