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