001/*
002 * SPDX-FileCopyrightText: none
003 * SPDX-License-Identifier: CC0-1.0
004 */
005
006package gov.nist.secauto.oscal.lib.profile.resolver.support;
007
008import gov.nist.secauto.metaschema.core.metapath.MetapathExpression;
009import gov.nist.secauto.metaschema.core.metapath.MetapathExpression.ResultType;
010import gov.nist.secauto.metaschema.core.metapath.item.node.IModelNodeItem;
011import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem;
012import gov.nist.secauto.metaschema.core.util.CustomCollectors;
013import gov.nist.secauto.metaschema.core.util.ObjectUtils;
014import gov.nist.secauto.oscal.lib.OscalBindingContext;
015import gov.nist.secauto.oscal.lib.model.metadata.IProperty;
016import gov.nist.secauto.oscal.lib.profile.resolver.support.IEntityItem.ItemType;
017
018import org.apache.logging.log4j.Level;
019import org.apache.logging.log4j.LogManager;
020import org.apache.logging.log4j.Logger;
021
022import java.util.Collection;
023import java.util.HashSet;
024import java.util.Map;
025import java.util.Set;
026import java.util.UUID;
027import java.util.function.Function;
028import java.util.function.Predicate;
029import java.util.stream.Stream;
030
031import edu.umd.cs.findbugs.annotations.NonNull;
032import edu.umd.cs.findbugs.annotations.Nullable;
033
034public interface IIndexer {
035  enum SelectionStatus {
036    SELECTED,
037    UNSELECTED,
038    UNKNOWN;
039  }
040
041  MetapathExpression HAS_PROP_KEEP_METAPATH = MetapathExpression.compile(
042      "prop[@name='keep' and has-oscal-namespace('" + IProperty.OSCAL_NAMESPACE + "')]/@value = 'always'",
043      OscalBindingContext.OSCAL_STATIC_METAPATH_CONTEXT);
044
045  Predicate<IEntityItem> KEEP_ENTITY_PREDICATE = entity -> entity.getReferenceCount() > 0
046      || (Boolean) ObjectUtils
047          .notNull(HAS_PROP_KEEP_METAPATH.evaluateAs(entity.getInstance(), ResultType.BOOLEAN));
048
049  static boolean isReferencedEntity(@NonNull IEntityItem entity) {
050    return KEEP_ENTITY_PREDICATE.test(entity);
051  }
052
053  /**
054   * Keep entities that have a reference count greater than zero or are required
055   * to be kept based on the "keep"="always property.
056   *
057   * @param entities
058   *          the entity items to filter
059   * @return the entities that pass the filter
060   */
061  static Stream<IEntityItem> getReferencedEntitiesAsStream(@NonNull Collection<IEntityItem> entities) {
062    return entities.stream().filter(KEEP_ENTITY_PREDICATE);
063  }
064
065  /**
066   * Keep entities that have a reference count of zero or are not required to be
067   * kept based on the "keep"="always property.
068   *
069   * @param entities
070   *          the entity items to filter
071   * @return the entities that pass the filter
072   */
073  static Stream<IEntityItem> getUnreferencedEntitiesAsStream(@NonNull Collection<IEntityItem> entities) {
074    return entities.stream().filter(KEEP_ENTITY_PREDICATE.negate());
075  }
076
077  /**
078   * Generates a stream of distinct items that have a reference count greater than
079   * zero or are required to be kept based on the "keep"="always property.
080   * <p>
081   * Distinct items are determined based on the item's key using the provided
082   * {@code keyMapper}.
083   *
084   * @param <T>
085   *          the item type
086   * @param <K>
087   *          the key type
088   * @param resolvedItems
089   *          a series of previously resolved items to add to prepend to the
090   *          stream
091   * @param importedEntityItems
092   *          a collection of new items to filter then append to the stream
093   * @param keyMapper
094   *          the key mapping function to determine the item's key
095   * @return the resulting series of items with duplicate items with the same key
096   *         removed
097   */
098  // TODO: Is this the right name for this method?
099  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}