001/* 002 * Portions of this software was developed by employees of the National Institute 003 * of Standards and Technology (NIST), an agency of the Federal Government and is 004 * being made available as a public service. Pursuant to title 17 United States 005 * Code Section 105, works of NIST employees are not subject to copyright 006 * protection in the United States. This software may be subject to foreign 007 * copyright. Permission in the United States and in foreign countries, to the 008 * extent that NIST may hold copyright, to use, copy, modify, create derivative 009 * works, and distribute this software and its documentation without fee is hereby 010 * granted on a non-exclusive basis, provided that this notice and disclaimer 011 * of warranty appears in all copies. 012 * 013 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER 014 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY 015 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF 016 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM 017 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE 018 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE. IN NO EVENT 019 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT, 020 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM, 021 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY, 022 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR 023 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT 024 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER. 025 */ 026 027package gov.nist.secauto.oscal.lib.profile.resolver.support; 028 029import gov.nist.secauto.metaschema.model.common.metapath.MetapathExpression; 030import gov.nist.secauto.metaschema.model.common.metapath.MetapathExpression.ResultType; 031import gov.nist.secauto.metaschema.model.common.metapath.item.INodeItem; 032import gov.nist.secauto.metaschema.model.common.metapath.item.IRequiredValueModelNodeItem; 033import gov.nist.secauto.metaschema.model.common.util.CustomCollectors; 034import gov.nist.secauto.metaschema.model.common.util.ObjectUtils; 035import gov.nist.secauto.oscal.lib.profile.resolver.support.IEntityItem.ItemType; 036 037import org.apache.logging.log4j.Level; 038import org.apache.logging.log4j.LogManager; 039import org.apache.logging.log4j.Logger; 040 041import java.util.Collection; 042import java.util.HashSet; 043import java.util.Map; 044import java.util.Set; 045import java.util.UUID; 046import java.util.function.Function; 047import java.util.function.Predicate; 048import java.util.stream.Stream; 049 050import edu.umd.cs.findbugs.annotations.NonNull; 051import edu.umd.cs.findbugs.annotations.Nullable; 052 053public interface IIndexer { 054 enum SelectionStatus { 055 SELECTED, 056 UNSELECTED, 057 UNKNOWN; 058 } 059 060 MetapathExpression HAS_PROP_KEEP_METAPATH = MetapathExpression 061 .compile("prop[@name='keep' and has-oscal-namespace('http://csrc.nist.gov/ns/oscal')]/@value = 'always'"); 062 063 Predicate<IEntityItem> KEEP_ENTITY_PREDICATE = new Predicate<>() { 064 065 @Override 066 public boolean test(IEntityItem entity) { 067 return entity.getReferenceCount() > 0 068 || (Boolean) ObjectUtils 069 .notNull(IIndexer.HAS_PROP_KEEP_METAPATH.evaluateAs(entity.getInstance(), ResultType.BOOLEAN)); 070 } 071 072 }; 073 074 static boolean isReferencedEntity(@NonNull IEntityItem entity) { 075 return KEEP_ENTITY_PREDICATE.test(entity); 076 } 077 078 /** 079 * Keep entities that have a reference count greater than zero or are required 080 * to be kept based on the "keep"="always property. 081 * 082 * @param entities 083 * the entity items to filter 084 * @return the entities that pass the filter 085 */ 086 static Stream<IEntityItem> getReferencedEntitiesAsStream(@NonNull Collection<IEntityItem> entities) { 087 return entities.stream().filter(KEEP_ENTITY_PREDICATE); 088 } 089 090 /** 091 * Keep entities that have a reference count of zero or are not required to be 092 * kept based on the "keep"="always property. 093 * 094 * @param entities 095 * the entity items to filter 096 * @return the entities that pass the filter 097 */ 098 static Stream<IEntityItem> getUnreferencedEntitiesAsStream(@NonNull Collection<IEntityItem> entities) { 099 return entities.stream().filter(KEEP_ENTITY_PREDICATE.negate()); 100 } 101 102 /** 103 * Generates a stream of distinct items that have a reference count greater than 104 * zero or are required to be kept based on the "keep"="always property. 105 * <p> 106 * Distinct items are determined based on the item's key using the provided 107 * {@code keyMapper}. 108 * 109 * @param <T> 110 * the item type 111 * @param <K> 112 * the key type 113 * @param resolvedItems 114 * a series of previously resolved items to add to prepend to the 115 * stream 116 * @param importedEntityItems 117 * a collection of new items to filter then append to the stream 118 * @param keyMapper 119 * the key mapping function to determine the item's key 120 * @return the resulting series of items with duplicate items with the same key 121 * removed 122 */ 123 // TODO: Is this the right name for this method? 124 static <T, K> Stream<T> filterDistinct( 125 @NonNull Stream<T> resolvedItems, 126 @NonNull Collection<IEntityItem> importedEntityItems, 127 @NonNull Function<? super T, ? extends K> keyMapper) { 128 @SuppressWarnings("unchecked") Stream<T> importedStream = getReferencedEntitiesAsStream(importedEntityItems) 129 .map(entity -> (T) entity.getInstanceValue()); 130 131 return CustomCollectors.distinctByKey( 132 ObjectUtils.notNull(Stream.concat(resolvedItems, importedStream)), 133 keyMapper, 134 (key, value1, value2) -> value2); 135 } 136 137 static void logIndex(@NonNull IIndexer indexer, @NonNull Level logLevel) { 138 Logger logger = LogManager.getLogger(); 139 140 Set<INodeItem> indexedItems = new HashSet<>(); 141 if (logger.isEnabled(logLevel)) { 142 for (ItemType itemType : ItemType.values()) { 143 assert itemType != null; 144 for (IEntityItem item : indexer.getEntitiesByItemType(itemType)) { 145 INodeItem nodeItem = item.getInstance(); 146 indexedItems.add(nodeItem); 147 logger.atLevel(logLevel).log("{} {}: selected: {}, reference count: {}", 148 itemType.name(), 149 item.isIdentifierReassigned() ? item.getIdentifier() + "(" + item.getOriginalIdentifier() + ")" 150 : item.getIdentifier(), 151 indexer.getSelectionStatus(nodeItem), 152 item.getReferenceCount()); 153 } 154 } 155 } 156 157 for (Map.Entry<INodeItem, SelectionStatus> entry : indexer.getSelectionStatusMap().entrySet()) { 158 INodeItem nodeItem = entry.getKey(); 159 if (!indexedItems.contains(nodeItem)) { 160 Object value = nodeItem.getValue(); 161 logger.atLevel(logLevel).log("{}: {}", value == null ? "(null)" : value.getClass().getName(), entry.getValue()); 162 } 163 } 164 } 165 166 @NonNull 167 IEntityItem addRole(@NonNull IRequiredValueModelNodeItem role); 168 169 @NonNull 170 IEntityItem addLocation(@NonNull IRequiredValueModelNodeItem location); 171 172 @NonNull 173 IEntityItem addParty(@NonNull IRequiredValueModelNodeItem party); 174 175 @Nullable 176 IEntityItem addGroup(@NonNull IRequiredValueModelNodeItem group); 177 178 @NonNull 179 IEntityItem addControl(@NonNull IRequiredValueModelNodeItem control); 180 181 @NonNull 182 IEntityItem addParameter(@NonNull IRequiredValueModelNodeItem parameter); 183 184 @Nullable 185 IEntityItem addPart(@NonNull IRequiredValueModelNodeItem part); 186 187 @NonNull 188 IEntityItem addResource(@NonNull IRequiredValueModelNodeItem resource); 189 190 @NonNull 191 Collection<IEntityItem> getEntitiesByItemType(@NonNull IEntityItem.ItemType itemType); 192 193 @Nullable 194 default IEntityItem getEntity(@NonNull IEntityItem.ItemType itemType, @NonNull UUID identifier) { 195 return getEntity(itemType, ObjectUtils.notNull(identifier.toString()), false); 196 } 197 198 /** 199 * Lookup an item of the given {@code itemType} having the given 200 * {@code identifier}. 201 * <p> 202 * Will normalize the case of a UUID-based identifier. 203 * 204 * @param itemType 205 * the type of item to search for 206 * @param identifier 207 * the identifier to lookup 208 * @return the matching item or {@code null} if no match was found 209 */ 210 @Nullable 211 default IEntityItem getEntity(@NonNull IEntityItem.ItemType itemType, @NonNull String identifier) { 212 return getEntity(itemType, identifier, itemType.isUuid()); 213 } 214 215 /** 216 * Lookup an item of the given {@code itemType} having the given 217 * {@code identifier}. 218 * <p> 219 * Will normalize the case of a UUID-based the identifier when requested. 220 * 221 * @param itemType 222 * the type of item to search for 223 * @param identifier 224 * the identifier to lookup 225 * @param normalize 226 * {@code true} if the identifier case should be normalized or 227 * {@code false} otherwise 228 * @return the matching item or {@code null} if no match was found 229 */ 230 @Nullable 231 IEntityItem getEntity(@NonNull IEntityItem.ItemType itemType, @NonNull String identifier, boolean normalize); 232 233 boolean removeItem(@NonNull IEntityItem entity); 234 235 boolean isSelected(@NonNull IEntityItem entity); 236 237 Map<INodeItem, SelectionStatus> getSelectionStatusMap(); 238 239 @NonNull 240 SelectionStatus getSelectionStatus(@NonNull INodeItem item); 241 242 void setSelectionStatus(@NonNull INodeItem item, @NonNull SelectionStatus selectionStatus); 243 244 void resetSelectionStatus(); 245 246 void append(@NonNull IIndexer result); 247 248 /** 249 * Get a copy of the entity map. 250 * 251 * @return the copy 252 */ 253 @NonNull 254 Map<ItemType, Map<String, IEntityItem>> getEntities(); 255}