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}