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