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