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