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}