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}