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.alter;
28
29 import gov.nist.secauto.metaschema.model.common.util.CollectionUtil;
30 import gov.nist.secauto.metaschema.model.common.util.ObjectUtils;
31 import gov.nist.secauto.oscal.lib.model.Catalog;
32 import gov.nist.secauto.oscal.lib.model.CatalogGroup;
33 import gov.nist.secauto.oscal.lib.model.Control;
34 import gov.nist.secauto.oscal.lib.model.ControlPart;
35 import gov.nist.secauto.oscal.lib.model.Link;
36 import gov.nist.secauto.oscal.lib.model.Parameter;
37 import gov.nist.secauto.oscal.lib.model.Property;
38 import gov.nist.secauto.oscal.lib.model.control.catalog.ICatalogVisitor;
39 import gov.nist.secauto.oscal.lib.model.metadata.IProperty;
40 import gov.nist.secauto.oscal.lib.profile.resolver.ProfileResolutionEvaluationException;
41
42 import java.util.Collection;
43 import java.util.Collections;
44 import java.util.EnumMap;
45 import java.util.EnumSet;
46 import java.util.Iterator;
47 import java.util.Locale;
48 import java.util.Map;
49 import java.util.Set;
50 import java.util.concurrent.ConcurrentHashMap;
51 import java.util.function.Function;
52 import java.util.function.Supplier;
53
54 import edu.umd.cs.findbugs.annotations.NonNull;
55 import edu.umd.cs.findbugs.annotations.Nullable;
56
57 public class RemoveVisitor implements ICatalogVisitor<Boolean, RemoveVisitor.Context> {
58 public enum TargetType {
59 PARAM("param", Parameter.class),
60 PROP("prop", Property.class),
61 LINK("link", Link.class),
62 PART("part", ControlPart.class);
63
64 @NonNull
65 private static final Map<Class<?>, TargetType> CLASS_TO_TYPE;
66 @NonNull
67 private static final Map<String, TargetType> NAME_TO_TYPE;
68 @NonNull
69 private final String fieldName;
70 @NonNull
71 private final Class<?> clazz;
72
73 static {
74 {
75 Map<Class<?>, TargetType> map = new ConcurrentHashMap<>();
76 for (TargetType type : TargetType.values()) {
77 map.put(type.getClazz(), type);
78 }
79 CLASS_TO_TYPE = CollectionUtil.unmodifiableMap(map);
80 }
81
82 {
83 Map<String, TargetType> map = new ConcurrentHashMap<>();
84 for (TargetType type : TargetType.values()) {
85 map.put(type.fieldName(), type);
86 }
87 NAME_TO_TYPE = CollectionUtil.unmodifiableMap(map);
88 }
89 }
90
91
92
93
94
95
96
97
98
99 @Nullable
100 public static TargetType forClass(@NonNull Class<?> clazz) {
101 Class<?> target = clazz;
102 TargetType retval;
103
104 do {
105 retval = CLASS_TO_TYPE.get(target);
106 } while (retval == null && (target = target.getSuperclass()) != null);
107 return retval;
108 }
109
110
111
112
113
114
115
116
117
118 @Nullable
119 public static TargetType forFieldName(@Nullable String name) {
120 return name == null ? null : NAME_TO_TYPE.get(name);
121 }
122
123 TargetType(@NonNull String fieldName, @NonNull Class<?> clazz) {
124 this.fieldName = fieldName;
125 this.clazz = clazz;
126 }
127
128
129
130
131
132
133 public String fieldName() {
134 return fieldName;
135 }
136
137
138
139
140
141
142 public Class<?> getClazz() {
143 return clazz;
144 }
145
146 }
147
148 @NonNull
149 private static final RemoveVisitor INSTANCE = new RemoveVisitor();
150
151 private static final Map<TargetType, Set<TargetType>> APPLICABLE_TARGETS;
152
153 static {
154 APPLICABLE_TARGETS = new EnumMap<>(TargetType.class);
155 APPLICABLE_TARGETS.put(TargetType.PARAM, Set.of(TargetType.PROP, TargetType.LINK));
156 APPLICABLE_TARGETS.put(TargetType.PART, Set.of(TargetType.PART, TargetType.PROP, TargetType.LINK));
157 }
158
159 private static Set<TargetType> getApplicableTypes(@NonNull TargetType type) {
160 return APPLICABLE_TARGETS.getOrDefault(type, CollectionUtil.emptySet());
161 }
162
163 private static <T> boolean handle(
164 @NonNull TargetType itemType,
165 @NonNull Supplier<? extends Collection<T>> supplier,
166 @Nullable Function<T, Boolean> handler,
167 @NonNull Context context) {
168
169 boolean handleChildren = !Collections.disjoint(context.getTargetItemTypes(), getApplicableTypes(itemType));
170 boolean retval = false;
171 if (context.isMatchingType(itemType)) {
172
173 Iterator<T> iter = supplier.get().iterator();
174 while (iter.hasNext()) {
175 T item = iter.next();
176
177 if (item == null || context.isApplicableTo(item)) {
178 iter.remove();
179 retval = true;
180
181 } else if (handler != null && handleChildren) {
182
183 retval = retval || handler.apply(item);
184 }
185 }
186 } else if (handleChildren && handler != null) {
187
188
189 Iterator<T> iter = supplier.get().iterator();
190 while (iter.hasNext()) {
191 T item = iter.next();
192 if (item != null) {
193 retval = retval || handler.apply(item);
194 }
195 }
196 }
197 return retval;
198 }
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219 public static boolean remove(
220 @NonNull Control control,
221 @Nullable String objectName,
222 @Nullable String objectClass,
223 @Nullable String objectId,
224 @Nullable String objectNamespace,
225 @Nullable TargetType itemType) {
226 return INSTANCE.visitControl(
227 control,
228 new Context(objectName, objectClass, objectId, objectNamespace, itemType));
229 }
230
231 @Override
232 public Boolean visitCatalog(Catalog catalog, Context context) {
233
234 throw new UnsupportedOperationException("not needed");
235 }
236
237 @Override
238 public Boolean visitGroup(CatalogGroup group, Context context) {
239
240 throw new UnsupportedOperationException("not needed");
241 }
242
243 @Override
244 public Boolean visitControl(Control control, Context context) {
245 assert context != null;
246
247
248 boolean retval = handle(
249 TargetType.PARAM,
250 () -> CollectionUtil.listOrEmpty(control.getParams()),
251 child -> visitParameter(ObjectUtils.notNull(child), context),
252 context);
253
254
255 retval = retval || handle(
256 TargetType.PROP,
257 () -> CollectionUtil.listOrEmpty(control.getProps()),
258 null,
259 context);
260
261
262 retval = retval || handle(
263 TargetType.LINK,
264 () -> CollectionUtil.listOrEmpty(control.getLinks()),
265 null,
266 context);
267
268
269 retval = retval || handle(
270 TargetType.PART,
271 () -> CollectionUtil.listOrEmpty(control.getParts()),
272 child -> visitPart(child, context),
273 context);
274
275 return retval;
276 }
277
278 @Override
279 public Boolean visitParameter(Parameter parameter, Context context) {
280 assert context != null;
281
282
283 boolean retval = handle(
284 TargetType.PROP,
285 () -> CollectionUtil.listOrEmpty(parameter.getProps()),
286 null,
287 context);
288
289
290 retval = retval || handle(
291 TargetType.LINK,
292 () -> CollectionUtil.listOrEmpty(parameter.getLinks()),
293 null,
294 context);
295 return retval;
296 }
297
298
299
300
301
302
303
304
305
306
307 public boolean visitPart(ControlPart part, Context context) {
308 assert context != null;
309
310
311 boolean retval = handle(
312 TargetType.PROP,
313 () -> CollectionUtil.listOrEmpty(part.getProps()),
314 null,
315 context);
316
317
318 retval = retval || handle(
319 TargetType.LINK,
320 () -> CollectionUtil.listOrEmpty(part.getLinks()),
321 null,
322 context);
323
324
325 retval = retval || handle(
326 TargetType.PART,
327 () -> CollectionUtil.listOrEmpty(part.getParts()),
328 child -> visitPart(child, context),
329 context);
330 return retval;
331 }
332
333 static final class Context {
334
335
336
337 @NonNull
338 private static final Set<TargetType> NAME_TYPES = ObjectUtils.notNull(
339 Set.of(TargetType.PART, TargetType.PROP));
340
341
342
343 @NonNull
344 private static final Set<TargetType> CLASS_TYPES = ObjectUtils.notNull(
345 Set.of(TargetType.PARAM, TargetType.PART, TargetType.PROP));
346
347
348
349 @NonNull
350 private static final Set<TargetType> ID_TYPES = ObjectUtils.notNull(
351 Set.of(TargetType.PARAM, TargetType.PART));
352
353
354
355 @NonNull
356 private static final Set<TargetType> NAMESPACE_TYPES = ObjectUtils.notNull(
357 Set.of(TargetType.PART, TargetType.PROP));
358
359 @Nullable
360 private final String objectName;
361 @Nullable
362 private final String objectClass;
363 @Nullable
364 private final String objectId;
365 @Nullable
366 private final String objectNamespace;
367 @NonNull
368 private final Set<TargetType> targetItemTypes;
369
370 private static boolean filterTypes(
371 @NonNull Set<TargetType> effectiveTypes,
372 @NonNull String criteria,
373 @NonNull Set<TargetType> allowedTypes,
374 @Nullable String value,
375 @Nullable TargetType itemType) {
376 boolean retval = false;
377 if (value != null) {
378 retval = effectiveTypes.retainAll(allowedTypes);
379 if (itemType != null && !allowedTypes.contains(itemType)) {
380 throw new ProfileResolutionEvaluationException(
381 String.format("%s='%s' is not supported for items of type '%s'",
382 criteria,
383 value,
384 itemType.fieldName()));
385 }
386 }
387 return retval;
388 }
389
390 private Context(
391 @Nullable String objectName,
392 @Nullable String objectClass,
393 @Nullable String objectId,
394 @Nullable String objectNamespace,
395 @Nullable TargetType itemType) {
396
397
398
399
400 @NonNull Set<TargetType> targetItemTypes = ObjectUtils.notNull(EnumSet.allOf(TargetType.class));
401 filterTypes(targetItemTypes, "by-name", NAME_TYPES, objectName, itemType);
402 filterTypes(targetItemTypes, "by-class", CLASS_TYPES, objectClass, itemType);
403 filterTypes(targetItemTypes, "by-id", ID_TYPES, objectId, itemType);
404 filterTypes(targetItemTypes, "by-ns", NAMESPACE_TYPES, objectNamespace, itemType);
405
406 if (itemType != null) {
407 targetItemTypes.retainAll(Set.of(itemType));
408 }
409
410 if (targetItemTypes.isEmpty()) {
411 throw new ProfileResolutionEvaluationException("The filter matches no available item types");
412 }
413
414 this.objectName = objectName;
415 this.objectClass = objectClass;
416 this.objectId = objectId;
417 this.objectNamespace = objectNamespace;
418 this.targetItemTypes = CollectionUtil.unmodifiableSet(targetItemTypes);
419 }
420
421 @Nullable
422 public String getObjectName() {
423 return objectName;
424 }
425
426 @Nullable
427 public String getObjectClass() {
428 return objectClass;
429 }
430
431 @Nullable
432 public String getObjectId() {
433 return objectId;
434 }
435
436 @NonNull
437 public Set<TargetType> getTargetItemTypes() {
438 return targetItemTypes;
439 }
440
441 public boolean isMatchingType(@NonNull TargetType type) {
442 return getTargetItemTypes().contains(type);
443 }
444
445 @Nullable
446 public String getObjectNamespace() {
447 return objectNamespace;
448 }
449
450 private static boolean checkValue(@Nullable String actual, @Nullable String expected) {
451 return expected == null || expected.equals(actual);
452 }
453
454 public boolean isApplicableTo(@NonNull Object obj) {
455 TargetType objectType = TargetType.forClass(obj.getClass());
456
457 boolean retval = objectType != null && getTargetItemTypes().contains(objectType);
458 if (retval) {
459 assert objectType != null;
460
461
462 String actualName = null;
463 String actualClass = null;
464 String actualId = null;
465 String actualNamespace = null;
466
467 switch (objectType) {
468 case PARAM: {
469 Parameter param = (Parameter) obj;
470 actualClass = param.getClazz();
471 actualId = param.getId();
472 break;
473 }
474 case PROP: {
475 Property prop = (Property) obj;
476 actualName = prop.getName();
477 actualClass = prop.getClazz();
478 actualNamespace = prop.getNs() == null ? IProperty.OSCAL_NAMESPACE.toString() : prop.getNs().toString();
479 break;
480 }
481 case PART: {
482 ControlPart part = (ControlPart) obj;
483 actualName = part.getName();
484 actualClass = part.getClazz();
485 actualId = part.getId() == null ? null : part.getId().toString();
486 actualNamespace = part.getNs() == null ? IProperty.OSCAL_NAMESPACE.toString() : part.getNs().toString();
487 break;
488 }
489 case LINK:
490
491 break;
492 default:
493 throw new UnsupportedOperationException(objectType.name().toLowerCase(Locale.ROOT));
494 }
495
496 retval = checkValue(actualName, getObjectName())
497 && checkValue(actualClass, getObjectClass())
498 && checkValue(actualId, getObjectId())
499 && checkValue(actualNamespace, getObjectNamespace());
500 }
501 return retval;
502 }
503 }
504 }