1
2
3
4
5
6 package gov.nist.secauto.oscal.lib.profile.resolver.support;
7
8 import gov.nist.secauto.metaschema.core.datatype.adapter.UuidAdapter;
9 import gov.nist.secauto.metaschema.core.metapath.MetapathExpression;
10 import gov.nist.secauto.metaschema.core.metapath.MetapathExpression.ResultType;
11 import gov.nist.secauto.metaschema.core.metapath.item.node.IModelNodeItem;
12 import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem;
13 import gov.nist.secauto.metaschema.core.util.CollectionUtil;
14 import gov.nist.secauto.metaschema.core.util.ObjectUtils;
15 import gov.nist.secauto.oscal.lib.OscalBindingContext;
16 import gov.nist.secauto.oscal.lib.model.BackMatter.Resource;
17 import gov.nist.secauto.oscal.lib.model.CatalogGroup;
18 import gov.nist.secauto.oscal.lib.model.Control;
19 import gov.nist.secauto.oscal.lib.model.ControlPart;
20 import gov.nist.secauto.oscal.lib.model.Metadata.Location;
21 import gov.nist.secauto.oscal.lib.model.Metadata.Party;
22 import gov.nist.secauto.oscal.lib.model.Metadata.Role;
23 import gov.nist.secauto.oscal.lib.model.Parameter;
24 import gov.nist.secauto.oscal.lib.profile.resolver.ProfileResolver;
25 import gov.nist.secauto.oscal.lib.profile.resolver.support.IEntityItem.ItemType;
26
27 import org.apache.logging.log4j.LogManager;
28 import org.apache.logging.log4j.Logger;
29
30 import java.util.Collection;
31 import java.util.Collections;
32 import java.util.EnumMap;
33 import java.util.LinkedHashMap;
34 import java.util.Locale;
35 import java.util.Map;
36 import java.util.UUID;
37 import java.util.concurrent.ConcurrentHashMap;
38 import java.util.stream.Collectors;
39
40 import edu.umd.cs.findbugs.annotations.NonNull;
41
42 public class BasicIndexer implements IIndexer {
43 private static final Logger LOGGER = LogManager.getLogger(ProfileResolver.class);
44 private static final MetapathExpression CONTAINER_METAPATH
45 = MetapathExpression.compile("(ancestor::control|ancestor::group)[1]",
46 OscalBindingContext.OSCAL_STATIC_METAPATH_CONTEXT);
47
48 @NonNull
49 private final Map<IEntityItem.ItemType, Map<String, IEntityItem>> entityTypeToIdentifierToEntityMap;
50 @NonNull
51 private Map<INodeItem, SelectionStatus> nodeItemToSelectionStatusMap;
52
53 @Override
54 public void append(@NonNull IIndexer other) {
55 for (ItemType itemType : ItemType.values()) {
56 assert itemType != null;
57 for (IEntityItem entity : other.getEntitiesByItemType(itemType)) {
58 assert entity != null;
59 addItem(entity);
60 }
61 }
62
63 this.nodeItemToSelectionStatusMap.putAll(other.getSelectionStatusMap());
64 }
65
66 public BasicIndexer() {
67 this.entityTypeToIdentifierToEntityMap = new EnumMap<>(IEntityItem.ItemType.class);
68 this.nodeItemToSelectionStatusMap = new ConcurrentHashMap<>();
69 }
70
71 @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
72 public BasicIndexer(IIndexer other) {
73
74 this.entityTypeToIdentifierToEntityMap = other.getEntities();
75
76
77 this.nodeItemToSelectionStatusMap = new ConcurrentHashMap<>(other.getSelectionStatusMap());
78 }
79
80 @Override
81 public void setSelectionStatus(@NonNull INodeItem item, @NonNull SelectionStatus selectionStatus) {
82 nodeItemToSelectionStatusMap.put(item, selectionStatus);
83 }
84
85 @Override
86 public Map<INodeItem, SelectionStatus> getSelectionStatusMap() {
87 return CollectionUtil.unmodifiableMap(nodeItemToSelectionStatusMap);
88 }
89
90 @Override
91 public SelectionStatus getSelectionStatus(@NonNull INodeItem item) {
92 SelectionStatus retval = nodeItemToSelectionStatusMap.get(item);
93 return retval == null ? SelectionStatus.UNKNOWN : retval;
94 }
95
96 @Override
97 public void resetSelectionStatus() {
98 nodeItemToSelectionStatusMap = new ConcurrentHashMap<>();
99 }
100
101 @Override
102 public boolean isSelected(@NonNull IEntityItem entity) {
103 boolean retval;
104 switch (entity.getItemType()) {
105 case CONTROL:
106 case GROUP:
107 retval = IIndexer.SelectionStatus.SELECTED.equals(getSelectionStatus(entity.getInstance()));
108 break;
109 case PART: {
110 IModelNodeItem<?, ?> instance = entity.getInstance();
111 IIndexer.SelectionStatus status = getSelectionStatus(instance);
112 if (IIndexer.SelectionStatus.UNKNOWN.equals(status)) {
113
114 IModelNodeItem<?, ?> containerItem = CONTAINER_METAPATH.evaluateAs(instance, ResultType.ITEM);
115 assert containerItem != null;
116 status = getSelectionStatus(containerItem);
117
118
119 setSelectionStatus(instance, status);
120 }
121 retval = IIndexer.SelectionStatus.SELECTED.equals(status);
122 break;
123 }
124 case PARAMETER:
125 case LOCATION:
126 case PARTY:
127 case RESOURCE:
128 case ROLE:
129
130 retval = true;
131 break;
132 default:
133 throw new UnsupportedOperationException(entity.getItemType().name());
134 }
135 return retval;
136 }
137
138 @Override
139 public Map<ItemType, Map<String, IEntityItem>> getEntities() {
140
141 Map<ItemType, Map<String, IEntityItem>> copy = entityTypeToIdentifierToEntityMap.entrySet().stream()
142 .map(entry -> {
143 ItemType key = entry.getKey();
144 Map<String, IEntityItem> oldMap = entry.getValue();
145
146 Map<String, IEntityItem> newMap = oldMap.entrySet().stream()
147 .collect(Collectors.toMap(
148 Map.Entry::getKey,
149 Map.Entry::getValue,
150 (key1, key2) -> key1,
151 LinkedHashMap::new));
152 assert newMap != null;
153
154 return Map.entry(key, Collections.synchronizedMap(newMap));
155 })
156 .collect(Collectors.toMap(
157 Map.Entry::getKey,
158 Map.Entry::getValue,
159 (key1, key2) -> key1,
160 ConcurrentHashMap::new));
161
162 assert copy != null;
163 return copy;
164 }
165
166 @Override
167 @NonNull
168
169 public Collection<IEntityItem> getEntitiesByItemType(@NonNull IEntityItem.ItemType itemType) {
170 Map<String, IEntityItem> entityGroup = entityTypeToIdentifierToEntityMap.get(itemType);
171 return entityGroup == null ? CollectionUtil.emptyList() : ObjectUtils.notNull(entityGroup.values());
172 }
173
174
175
176
177
178
179
180
181
182
183
184
185 @Override
186 public IEntityItem getEntity(@NonNull ItemType itemType, @NonNull String identifier, boolean normalize) {
187 Map<String, IEntityItem> entityGroup = entityTypeToIdentifierToEntityMap.get(itemType);
188 String normalizedIdentifier = normalize ? normalizeIdentifier(identifier) : identifier;
189 return entityGroup == null ? null : entityGroup.get(normalizedIdentifier);
190 }
191
192 protected IEntityItem addItem(@NonNull IEntityItem item) {
193 IEntityItem.ItemType type = item.getItemType();
194
195 @SuppressWarnings("PMD.UseConcurrentHashMap")
196 Map<String, IEntityItem> entityGroup = entityTypeToIdentifierToEntityMap.computeIfAbsent(
197 type,
198 key -> Collections.synchronizedMap(new LinkedHashMap<>()));
199 IEntityItem oldEntity = entityGroup.put(item.getIdentifier(), item);
200
201 if (oldEntity != null && LOGGER.isWarnEnabled()) {
202 LOGGER.atWarn().log("Duplicate {} found with identifier {} in index.",
203 oldEntity.getItemType().name().toLowerCase(Locale.ROOT),
204 oldEntity.getIdentifier());
205 }
206 return oldEntity;
207 }
208
209 @NonNull
210 protected IEntityItem addItem(@NonNull AbstractEntityItem.Builder builder) {
211 IEntityItem retval = builder.build();
212 addItem(retval);
213 return retval;
214 }
215
216 @Override
217 public boolean removeItem(@NonNull IEntityItem entity) {
218 IEntityItem.ItemType type = entity.getItemType();
219 Map<String, IEntityItem> entityGroup = entityTypeToIdentifierToEntityMap.get(type);
220
221 boolean retval = false;
222 if (entityGroup != null) {
223 retval = entityGroup.remove(entity.getIdentifier(), entity);
224
225
226 nodeItemToSelectionStatusMap.remove(entity.getInstance());
227
228 if (retval) {
229 if (LOGGER.isDebugEnabled()) {
230 LOGGER.atDebug().log("Removing {} '{}' from index.", type.name(), entity.getIdentifier());
231 }
232 } else if (LOGGER.isDebugEnabled()) {
233 LOGGER.atDebug().log("The {} entity '{}' was not found in the index to remove.",
234 type.name(),
235 entity.getIdentifier());
236 }
237 }
238 return retval;
239 }
240
241 @Override
242 public IEntityItem addRole(IModelNodeItem<?, ?> item) {
243 Role role = ObjectUtils.requireNonNull((Role) item.getValue());
244 String identifier = ObjectUtils.requireNonNull(role.getId());
245
246 return addItem(newBuilder(item, ItemType.ROLE, identifier));
247 }
248
249 @Override
250 public IEntityItem addLocation(IModelNodeItem<?, ?> item) {
251 Location location = ObjectUtils.requireNonNull((Location) item.getValue());
252 UUID identifier = ObjectUtils.requireNonNull(location.getUuid());
253
254 return addItem(newBuilder(item, ItemType.LOCATION, identifier));
255 }
256
257 @Override
258 public IEntityItem addParty(IModelNodeItem<?, ?> item) {
259 Party party = ObjectUtils.requireNonNull((Party) item.getValue());
260 UUID identifier = ObjectUtils.requireNonNull(party.getUuid());
261
262 return addItem(newBuilder(item, ItemType.PARTY, identifier));
263 }
264
265 @Override
266 public IEntityItem addGroup(IModelNodeItem<?, ?> item) {
267 CatalogGroup group = ObjectUtils.requireNonNull((CatalogGroup) item.getValue());
268 String identifier = group.getId();
269 return identifier == null ? null : addItem(newBuilder(item, ItemType.GROUP, identifier));
270 }
271
272 @Override
273 public IEntityItem addControl(IModelNodeItem<?, ?> item) {
274 Control control = ObjectUtils.requireNonNull((Control) item.getValue());
275 String identifier = ObjectUtils.requireNonNull(control.getId());
276 return addItem(newBuilder(item, ItemType.CONTROL, identifier));
277 }
278
279 @Override
280 public IEntityItem addParameter(IModelNodeItem<?, ?> item) {
281 Parameter parameter = ObjectUtils.requireNonNull((Parameter) item.getValue());
282 String identifier = ObjectUtils.requireNonNull(parameter.getId());
283
284 return addItem(newBuilder(item, ItemType.PARAMETER, identifier));
285 }
286
287 @Override
288 public IEntityItem addPart(IModelNodeItem<?, ?> item) {
289 ControlPart part = ObjectUtils.requireNonNull((ControlPart) item.getValue());
290 String identifier = part.getId();
291
292 return identifier == null ? null : addItem(newBuilder(item, ItemType.PART, identifier));
293 }
294
295 @Override
296 public IEntityItem addResource(IModelNodeItem<?, ?> item) {
297 Resource resource = ObjectUtils.requireNonNull((Resource) item.getValue());
298 UUID identifier = ObjectUtils.requireNonNull(resource.getUuid());
299
300 return addItem(newBuilder(item, ItemType.RESOURCE, identifier));
301 }
302
303 @NonNull
304 protected final AbstractEntityItem.Builder newBuilder(
305 @NonNull IModelNodeItem<?, ?> item,
306 @NonNull ItemType itemType,
307 @NonNull UUID identifier) {
308 return newBuilder(item, itemType, ObjectUtils.notNull(identifier.toString()));
309 }
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328 @NonNull
329 protected AbstractEntityItem.Builder newBuilder(
330 @NonNull IModelNodeItem<?, ?> item,
331 @NonNull ItemType itemType,
332 @NonNull String identifier) {
333 return new AbstractEntityItem.Builder()
334 .instance(item, itemType)
335 .originalIdentifier(identifier)
336 .source(ObjectUtils.requireNonNull(item.getBaseUri(), "item must have an associated URI"));
337 }
338
339
340
341
342
343
344
345
346 @NonNull
347 public String normalizeIdentifier(@NonNull String identifier) {
348 return UuidAdapter.UUID_PATTERN.matcher(identifier).matches()
349 ? ObjectUtils.notNull(identifier.toLowerCase(Locale.ROOT))
350 : identifier;
351 }
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378 }