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") 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}