1
2
3
4
5
6 package gov.nist.secauto.oscal.lib.profile.resolver;
7
8 import gov.nist.secauto.metaschema.core.metapath.DynamicContext;
9 import gov.nist.secauto.metaschema.core.metapath.IDocumentLoader;
10 import gov.nist.secauto.metaschema.core.metapath.ISequence;
11 import gov.nist.secauto.metaschema.core.metapath.MetapathExpression;
12 import gov.nist.secauto.metaschema.core.metapath.StaticContext;
13 import gov.nist.secauto.metaschema.core.metapath.format.IPathFormatter;
14 import gov.nist.secauto.metaschema.core.metapath.function.FunctionUtils;
15 import gov.nist.secauto.metaschema.core.metapath.item.IItem;
16 import gov.nist.secauto.metaschema.core.metapath.item.node.IAssemblyNodeItem;
17 import gov.nist.secauto.metaschema.core.metapath.item.node.IDocumentNodeItem;
18 import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem;
19 import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItemFactory;
20 import gov.nist.secauto.metaschema.core.metapath.item.node.IRootAssemblyNodeItem;
21 import gov.nist.secauto.metaschema.core.model.IBoundObject;
22 import gov.nist.secauto.metaschema.core.qname.IEnhancedQName;
23 import gov.nist.secauto.metaschema.core.util.CollectionUtil;
24 import gov.nist.secauto.metaschema.core.util.ObjectUtils;
25 import gov.nist.secauto.metaschema.databind.io.BindingException;
26 import gov.nist.secauto.metaschema.databind.io.DeserializationFeature;
27 import gov.nist.secauto.metaschema.databind.io.IBoundLoader;
28 import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelAssembly;
29 import gov.nist.secauto.oscal.lib.OscalBindingContext;
30 import gov.nist.secauto.oscal.lib.OscalModelConstants;
31 import gov.nist.secauto.oscal.lib.OscalUtils;
32 import gov.nist.secauto.oscal.lib.model.BackMatter;
33 import gov.nist.secauto.oscal.lib.model.BackMatter.Resource;
34 import gov.nist.secauto.oscal.lib.model.BackMatter.Resource.Base64;
35 import gov.nist.secauto.oscal.lib.model.BackMatter.Resource.Rlink;
36 import gov.nist.secauto.oscal.lib.model.Catalog;
37 import gov.nist.secauto.oscal.lib.model.Control;
38 import gov.nist.secauto.oscal.lib.model.Merge;
39 import gov.nist.secauto.oscal.lib.model.Metadata;
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.Modify;
44 import gov.nist.secauto.oscal.lib.model.Modify.ProfileSetParameter;
45 import gov.nist.secauto.oscal.lib.model.Parameter;
46 import gov.nist.secauto.oscal.lib.model.Profile;
47 import gov.nist.secauto.oscal.lib.model.ProfileImport;
48 import gov.nist.secauto.oscal.lib.model.Property;
49 import gov.nist.secauto.oscal.lib.model.metadata.AbstractLink;
50 import gov.nist.secauto.oscal.lib.model.metadata.AbstractProperty;
51 import gov.nist.secauto.oscal.lib.profile.resolver.alter.AddVisitor;
52 import gov.nist.secauto.oscal.lib.profile.resolver.alter.RemoveVisitor;
53 import gov.nist.secauto.oscal.lib.profile.resolver.merge.FlatteningStructuringVisitor;
54 import gov.nist.secauto.oscal.lib.profile.resolver.selection.Import;
55 import gov.nist.secauto.oscal.lib.profile.resolver.selection.ImportCycleException;
56 import gov.nist.secauto.oscal.lib.profile.resolver.support.BasicIndexer;
57 import gov.nist.secauto.oscal.lib.profile.resolver.support.ControlIndexingVisitor;
58 import gov.nist.secauto.oscal.lib.profile.resolver.support.IEntityItem;
59 import gov.nist.secauto.oscal.lib.profile.resolver.support.IEntityItem.ItemType;
60 import gov.nist.secauto.oscal.lib.profile.resolver.support.IIndexer;
61
62 import org.apache.logging.log4j.LogManager;
63 import org.apache.logging.log4j.Logger;
64
65 import java.io.File;
66 import java.io.IOException;
67 import java.net.URI;
68 import java.net.URISyntaxException;
69 import java.net.URL;
70 import java.nio.ByteBuffer;
71 import java.nio.file.Path;
72 import java.time.ZoneOffset;
73 import java.time.ZonedDateTime;
74 import java.util.EnumSet;
75 import java.util.LinkedList;
76 import java.util.List;
77 import java.util.Stack;
78 import java.util.UUID;
79 import java.util.stream.Collectors;
80
81 import edu.umd.cs.findbugs.annotations.NonNull;
82 import edu.umd.cs.findbugs.annotations.Nullable;
83
84 public class ProfileResolver {
85
86 public enum StructuringDirective {
87 FLAT,
88 AS_IS,
89 CUSTOM;
90 }
91
92 private static final Logger LOGGER = LogManager.getLogger(ProfileResolver.class);
93 @NonNull
94 private static final IEnhancedQName IMPORT_QNAME = IEnhancedQName.of(OscalModelConstants.NS_OSCAL, "import");
95
96 @NonNull
97 private static final MetapathExpression METAPATH_SET_PARAMETER
98 = MetapathExpression.compile("modify/set-parameter",
99 OscalBindingContext.OSCAL_STATIC_METAPATH_CONTEXT);
100 @NonNull
101 private static final MetapathExpression METAPATH_ALTER
102 = MetapathExpression.compile("modify/alter",
103 OscalBindingContext.OSCAL_STATIC_METAPATH_CONTEXT);
104 @NonNull
105 private static final MetapathExpression METAPATH_ALTER_REMOVE
106 = MetapathExpression.compile("remove",
107 OscalBindingContext.OSCAL_STATIC_METAPATH_CONTEXT);
108 @NonNull
109 private static final MetapathExpression METAPATH_ALTER_ADD
110 = MetapathExpression.compile("add",
111 OscalBindingContext.OSCAL_STATIC_METAPATH_CONTEXT);
112 @NonNull
113 private static final MetapathExpression CATALOG_OR_PROFILE
114 = MetapathExpression.compile("/(catalog|profile)",
115 OscalBindingContext.OSCAL_STATIC_METAPATH_CONTEXT);
116 @NonNull
117 private static final MetapathExpression CATALOG
118 = MetapathExpression.compile("/catalog",
119 OscalBindingContext.OSCAL_STATIC_METAPATH_CONTEXT);
120
121 @NonNull
122 private final DynamicContext dynamicContext;
123
124 public ProfileResolver() {
125 this(newDynamicContext());
126 }
127
128 public ProfileResolver(@NonNull DynamicContext dynamicContext) {
129 this.dynamicContext = dynamicContext;
130 }
131
132 @NonNull
133 private static DynamicContext newDynamicContext() {
134 IBoundLoader loader = OscalBindingContext.instance().newBoundLoader();
135 loader.disableFeature(DeserializationFeature.DESERIALIZE_VALIDATE_CONSTRAINTS);
136
137 DynamicContext retval = new DynamicContext(StaticContext.builder()
138 .defaultModelNamespace(OscalModelConstants.NS_OSCAL)
139 .build());
140 retval.setDocumentLoader(loader);
141 return retval;
142 }
143
144
145
146
147
148
149
150
151 @NonNull
152 public IDocumentLoader getDocumentLoader() {
153 return getDynamicContext().getDocumentLoader();
154 }
155
156 @NonNull
157 public DynamicContext getDynamicContext() {
158 return dynamicContext;
159 }
160
161 @Nullable
162 private static IRootAssemblyNodeItem getRoot(
163 @NonNull IDocumentNodeItem document,
164 @NonNull MetapathExpression rootPath) {
165 ISequence<?> result = rootPath.evaluate(document);
166 IItem item = result.getFirstItem(false);
167
168 return item == null ? null : FunctionUtils.asType(item);
169 }
170
171 @NonNull
172 public IDocumentNodeItem resolve(@NonNull URL url)
173 throws URISyntaxException, IOException, ProfileResolutionException {
174 IDocumentLoader loader = getDocumentLoader();
175 IDocumentNodeItem catalogOrProfile = loader.loadAsNodeItem(url);
176 return resolve(catalogOrProfile, new Stack<>());
177 }
178
179 @NonNull
180 public IDocumentNodeItem resolve(@NonNull File file) throws IOException, ProfileResolutionException {
181 return resolve(ObjectUtils.notNull(file.toPath()));
182 }
183
184 @NonNull
185 public IDocumentNodeItem resolve(@NonNull Path path) throws IOException, ProfileResolutionException {
186 IDocumentLoader loader = getDocumentLoader();
187 IDocumentNodeItem catalogOrProfile = loader.loadAsNodeItem(path);
188 return resolve(catalogOrProfile, new Stack<>());
189 }
190
191 @NonNull
192 public IDocumentNodeItem resolve(
193 @NonNull IDocumentNodeItem profileOrCatalogDocument)
194 throws IOException, ProfileResolutionException {
195 return resolve(profileOrCatalogDocument, new Stack<>());
196 }
197
198 @NonNull
199 public IDocumentNodeItem resolve(
200 @NonNull IDocumentNodeItem profileOrCatalogDocument,
201 @NonNull Stack<URI> importHistory)
202 throws IOException, ProfileResolutionException {
203 IRootAssemblyNodeItem profileOrCatalog = getRoot(
204 profileOrCatalogDocument,
205 CATALOG_OR_PROFILE);
206 if (profileOrCatalog == null) {
207 throw new ProfileResolutionException(
208 String.format("The provided document '%s' does not contain a catalog or profile.",
209 profileOrCatalogDocument.getDocumentUri()));
210 }
211 return resolve(profileOrCatalog, importHistory);
212 }
213
214 @NonNull
215 public IDocumentNodeItem resolve(
216 @NonNull IRootAssemblyNodeItem profileOrCatalog,
217 @NonNull Stack<URI> importHistory)
218 throws IOException, ProfileResolutionException {
219 Object profileObject = profileOrCatalog.getValue();
220
221 IDocumentNodeItem retval;
222 if (profileObject instanceof Catalog) {
223
224 retval = profileOrCatalog.getParentNodeItem();
225 } else {
226
227 retval = resolveProfile(profileOrCatalog, importHistory);
228 }
229 return retval;
230 }
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245 @NonNull
246 protected IDocumentNodeItem resolveProfile(
247 @NonNull IRootAssemblyNodeItem profileItem,
248 @NonNull Stack<URI> importHistory) throws IOException, ProfileResolutionException {
249 Catalog resolvedCatalog = new Catalog();
250
251 generateMetadata(resolvedCatalog, profileItem);
252
253 IIndexer index = resolveImports(resolvedCatalog, profileItem, importHistory);
254 handleReferences(resolvedCatalog, profileItem, index);
255 handleMerge(resolvedCatalog, profileItem, index);
256 handleModify(resolvedCatalog, profileItem);
257
258 return INodeItemFactory.instance().newDocumentNodeItem(
259 ObjectUtils.requireNonNull(
260 (IBoundDefinitionModelAssembly) OscalBindingContext.instance().getBoundDefinitionForClass(Catalog.class)),
261 ObjectUtils.requireNonNull(profileItem.getBaseUri()),
262 resolvedCatalog);
263 }
264
265 @NonNull
266 private static Profile toProfile(@NonNull IRootAssemblyNodeItem profileItem) {
267 Object object = profileItem.getValue();
268 assert object != null;
269
270 return (Profile) object;
271 }
272
273 private static void generateMetadata(
274 @NonNull Catalog resolvedCatalog,
275 @NonNull IRootAssemblyNodeItem profileItem) {
276 resolvedCatalog.setUuid(UUID.randomUUID());
277
278 Profile profile = toProfile(profileItem);
279 Metadata profileMetadata = profile.getMetadata();
280
281 Metadata resolvedMetadata = new Metadata();
282 resolvedMetadata.setTitle(profileMetadata.getTitle());
283
284 if (profileMetadata.getVersion() != null) {
285 resolvedMetadata.setVersion(profileMetadata.getVersion());
286 }
287
288
289 resolvedMetadata.setOscalVersion(profileMetadata.getOscalVersion());
290
291 resolvedMetadata.setLastModified(ZonedDateTime.now(ZoneOffset.UTC));
292
293 resolvedMetadata.addProp(AbstractProperty.builder("resolution-tool").value("libOSCAL-Java").build());
294
295 URI profileUri = ObjectUtils.requireNonNull(profileItem.getParentNodeItem().getDocumentUri());
296 resolvedMetadata.addLink(AbstractLink.builder(profileUri).relation("source-profile").build());
297
298 resolvedCatalog.setMetadata(resolvedMetadata);
299 }
300
301 @NonNull
302 private IIndexer resolveImports(
303 @NonNull Catalog resolvedCatalog,
304 @NonNull IRootAssemblyNodeItem profileItem,
305 @NonNull Stack<URI> importHistory)
306 throws IOException, ProfileResolutionException {
307
308
309 @SuppressWarnings("unchecked")
310 List<IAssemblyNodeItem> profileImports
311 = (List<IAssemblyNodeItem>) profileItem.getModelItemsByName(IMPORT_QNAME);
312 if (profileImports.isEmpty()) {
313 throw new ProfileResolutionException(String.format("Profile '%s' has no imports", profileItem.getBaseUri()));
314 }
315
316
317 IIndexer retval = new BasicIndexer();
318 for (IAssemblyNodeItem profileImportItem : profileImports) {
319 IIndexer result = resolveImport(
320 ObjectUtils.notNull(profileImportItem),
321 profileItem,
322 importHistory,
323 resolvedCatalog);
324 retval.append(result);
325 }
326 return retval;
327 }
328
329 @NonNull
330 protected IIndexer resolveImport(
331 @NonNull IAssemblyNodeItem profileImportItem,
332 @NonNull IRootAssemblyNodeItem profileItem,
333 @NonNull Stack<URI> importHistory,
334 @NonNull Catalog resolvedCatalog) throws IOException, ProfileResolutionException {
335 ProfileImport profileImport = ObjectUtils.requireNonNull((ProfileImport) profileImportItem.getValue());
336
337 URI importUri = profileImport.getHref();
338 if (importUri == null) {
339 throw new ProfileResolutionException("profileImport.getHref() must return a non-null URI");
340 }
341
342 if (LOGGER.isDebugEnabled()) {
343 LOGGER.atDebug().log("resolving profile import '{}'", importUri);
344 }
345
346 IDocumentNodeItem importedDocument = getImport(importUri, profileItem);
347 URI importedUri = importedDocument.getDocumentUri();
348 assert importedUri != null;
349
350
351
352
353
354
355 try {
356 requireNonCycle(
357 importedUri,
358 importHistory);
359 } catch (ImportCycleException ex) {
360 throw new IOException(ex);
361 }
362
363
364 importHistory.push(importedUri);
365 try {
366 IDocumentNodeItem importedCatalog = resolve(importedDocument, importHistory);
367
368
369
370
371 try {
372 IRootAssemblyNodeItem importedCatalogRoot = ObjectUtils.requireNonNull(getRoot(importedCatalog, CATALOG));
373 Catalog catalogCopy = (Catalog) OscalBindingContext.instance().deepCopy(
374 (IBoundObject) ObjectUtils.requireNonNull(importedCatalogRoot).getValue(), null);
375
376 importedCatalog = INodeItemFactory.instance().newDocumentNodeItem(
377 importedCatalogRoot.getDefinition(),
378 ObjectUtils.requireNonNull(importedCatalog.getDocumentUri()),
379 catalogCopy);
380
381 return new Import(profileItem, profileImportItem).resolve(importedCatalog, resolvedCatalog);
382 } catch (BindingException ex) {
383 throw new IOException(ex);
384 }
385 } finally {
386
387 URI poppedUri = ObjectUtils.notNull(importHistory.pop());
388 assert importedUri.equals(poppedUri);
389 }
390 }
391
392 private IDocumentNodeItem getImport(
393 @NonNull URI importUri,
394 @NonNull IRootAssemblyNodeItem importingProfile) throws IOException {
395
396 URI importingDocumentUri = ObjectUtils.requireNonNull(importingProfile.getParentNodeItem().getDocumentUri());
397
398 IDocumentNodeItem retval;
399 if (OscalUtils.isInternalReference(importUri)) {
400
401 String uuid = OscalUtils.internalReferenceFragmentToId(importUri);
402
403 Profile profile = INodeItem.toValue(importingProfile);
404 Resource resource = profile.getResourceByUuid(ObjectUtils.notNull(UUID.fromString(uuid)));
405 if (resource == null) {
406 throw new IOException(
407 String.format("unable to find the resource identified by '%s' used in profile import", importUri));
408 }
409
410 retval = getImport(resource, importingDocumentUri);
411 } else {
412 URI uri = importingDocumentUri.resolve(importUri);
413 assert uri != null;
414
415 retval = getDynamicContext().getDocumentLoader().loadAsNodeItem(uri);
416 }
417 return retval;
418 }
419
420 @Nullable
421 private IDocumentNodeItem getImport(
422 @NonNull Resource resource,
423 @NonNull URI baseUri) throws IOException {
424
425 IDocumentLoader loader = getDynamicContext().getDocumentLoader();
426
427 IDocumentNodeItem retval = null;
428
429 Base64 base64 = resource.getBase64();
430 ByteBuffer buffer = base64 == null ? null : base64.getValue();
431 if (buffer != null) {
432 URI resourceUri = baseUri.resolve("#" + resource.getUuid());
433 assert resourceUri != null;
434 retval = loader.loadAsNodeItem(resourceUri);
435 }
436
437 if (retval == null) {
438 Rlink rlink = OscalUtils.findMatchingRLink(resource, null);
439 URI uri = rlink == null ? null : rlink.getHref();
440
441 if (uri == null) {
442 throw new IOException(String.format("unable to determine URI for resource '%s'", resource.getUuid()));
443 }
444
445 uri = baseUri.resolve(uri);
446 assert uri != null;
447 retval = loader.loadAsNodeItem(uri);
448 }
449 return retval;
450 }
451
452 private static void requireNonCycle(@NonNull URI uri, @NonNull Stack<URI> importHistory)
453 throws ImportCycleException {
454 List<URI> cycle = checkCycle(uri, importHistory);
455 if (!cycle.isEmpty()) {
456 throw new ImportCycleException(String.format("Importing resource '%s' would result in the import cycle: %s", uri,
457 cycle.stream().map(URI::toString).collect(Collectors.joining(" -> ", " -> ", ""))));
458 }
459 }
460
461 @NonNull
462 private static List<URI> checkCycle(@NonNull URI uri, @NonNull Stack<URI> importHistory) {
463 int index = importHistory.indexOf(uri);
464
465 List<URI> retval;
466 if (index == -1) {
467 retval = CollectionUtil.emptyList();
468 } else {
469 retval = CollectionUtil.unmodifiableList(
470 ObjectUtils.notNull(importHistory.subList(0, index + 1)));
471 }
472 return retval;
473 }
474
475
476 private static StructuringDirective getStructuringDirective(Profile profile) {
477 Merge merge = profile.getMerge();
478
479 StructuringDirective retval;
480 if (merge == null) {
481 retval = StructuringDirective.FLAT;
482 } else if (merge.getAsIs() != null && merge.getAsIs()) {
483 retval = StructuringDirective.AS_IS;
484 } else if (merge.getCustom() != null) {
485 retval = StructuringDirective.CUSTOM;
486 } else {
487 retval = StructuringDirective.FLAT;
488 }
489 return retval;
490 }
491
492 protected void handleMerge(
493 @NonNull Catalog resolvedCatalog,
494 @NonNull IRootAssemblyNodeItem profileItem,
495 @NonNull IIndexer importIndex) {
496
497
498
499 switch (getStructuringDirective(toProfile(profileItem))) {
500 case AS_IS:
501
502 break;
503 case CUSTOM:
504 throw new UnsupportedOperationException("custom structuring");
505 case FLAT:
506 default:
507 structureFlat(resolvedCatalog, profileItem, importIndex);
508 break;
509 }
510
511 }
512
513 protected void structureFlat(@NonNull Catalog resolvedCatalog, @NonNull IRootAssemblyNodeItem profileItem,
514 @NonNull IIndexer importIndex) {
515 if (LOGGER.isDebugEnabled()) {
516 LOGGER.debug("applying flat structuring directive");
517 }
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539 IDocumentNodeItem resolvedCatalogItem = INodeItemFactory.instance().newDocumentNodeItem(
540 ObjectUtils.requireNonNull(
541 (IBoundDefinitionModelAssembly) OscalBindingContext.instance().getBoundDefinitionForClass(Catalog.class)),
542 ObjectUtils.requireNonNull(profileItem.getBaseUri()),
543 resolvedCatalog);
544
545 FlatteningStructuringVisitor.instance().visitCatalog(resolvedCatalogItem, importIndex);
546 }
547
548 @SuppressWarnings("PMD.ExceptionAsFlowControl")
549 protected void handleModify(@NonNull Catalog resolvedCatalog, @NonNull IRootAssemblyNodeItem profileItem)
550 throws ProfileResolutionException {
551 IDocumentNodeItem resolvedCatalogDocument = INodeItemFactory.instance().newDocumentNodeItem(
552 ObjectUtils.requireNonNull(
553 (IBoundDefinitionModelAssembly) OscalBindingContext.instance().getBoundDefinitionForClass(Catalog.class)),
554 ObjectUtils.requireNonNull(profileItem.getBaseUri()),
555 resolvedCatalog);
556
557 try {
558 IIndexer indexer = new BasicIndexer();
559 ControlIndexingVisitor visitor = new ControlIndexingVisitor(
560 ObjectUtils.notNull(EnumSet.of(IEntityItem.ItemType.CONTROL, IEntityItem.ItemType.PARAMETER)));
561 visitor.visitCatalog(resolvedCatalogDocument, indexer);
562
563 METAPATH_SET_PARAMETER.evaluate(profileItem)
564 .forEach(item -> {
565 IAssemblyNodeItem setParameter = (IAssemblyNodeItem) item;
566 try {
567 handleSetParameter(setParameter, indexer);
568 } catch (ProfileResolutionEvaluationException ex) {
569 throw new ProfileResolutionEvaluationException(
570 String.format("Unable to apply the set-parameter at '%s'. %s",
571 setParameter.toPath(IPathFormatter.METAPATH_PATH_FORMATER),
572 ex.getLocalizedMessage()),
573 ex);
574 }
575 });
576
577 METAPATH_ALTER.evaluate(profileItem)
578 .forEach(item -> {
579 handleAlter((IAssemblyNodeItem) item, indexer);
580 });
581 } catch (ProfileResolutionEvaluationException ex) {
582 throw new ProfileResolutionException(ex.getLocalizedMessage(), ex);
583 }
584 }
585
586 protected void handleSetParameter(IAssemblyNodeItem item, IIndexer indexer) {
587 ProfileSetParameter setParameter = ObjectUtils.requireNonNull((Modify.ProfileSetParameter) item.getValue());
588 String paramId = ObjectUtils.requireNonNull(setParameter.getParamId());
589 IEntityItem entity = indexer.getEntity(IEntityItem.ItemType.PARAMETER, paramId, false);
590 if (entity == null) {
591 throw new ProfileResolutionEvaluationException(
592 String.format(
593 "The parameter '%s' does not exist in the resolved catalog.",
594 paramId));
595 }
596
597 Parameter param = entity.getInstanceValue();
598
599
600 param.setClazz(ModifyPhaseUtils.mergeItem(param.getClazz(), setParameter.getClazz()));
601 param.setProps(ModifyPhaseUtils.merge(param.getProps(), setParameter.getProps(),
602 ModifyPhaseUtils.identifierKey(Property::getUuid)));
603 param.setLinks(ModifyPhaseUtils.merge(param.getLinks(), setParameter.getLinks(), ModifyPhaseUtils.identityKey()));
604 param.setLabel(ModifyPhaseUtils.mergeItem(param.getLabel(), setParameter.getLabel()));
605 param.setUsage(ModifyPhaseUtils.mergeItem(param.getUsage(), setParameter.getUsage()));
606 param.setConstraints(
607 ModifyPhaseUtils.merge(param.getConstraints(), setParameter.getConstraints(), ModifyPhaseUtils.identityKey()));
608 param.setGuidelines(
609 ModifyPhaseUtils.merge(param.getGuidelines(), setParameter.getGuidelines(), ModifyPhaseUtils.identityKey()));
610 param.setValues(new LinkedList<>(setParameter.getValues()));
611 param.setSelect(setParameter.getSelect());
612 }
613
614 @SuppressWarnings("PMD.ExceptionAsFlowControl")
615 protected void handleAlter(IAssemblyNodeItem item, IIndexer indexer) {
616 Modify.Alter alter = ObjectUtils.requireNonNull((Modify.Alter) item.getValue());
617 String controlId = ObjectUtils.requireNonNull(alter.getControlId());
618 IEntityItem entity = indexer.getEntity(IEntityItem.ItemType.CONTROL, controlId, false);
619 if (entity == null) {
620 throw new ProfileResolutionEvaluationException(
621 String.format(
622 "Unable to apply the alter targeting control '%s' at '%s'."
623 + " The control does not exist in the resolved catalog.",
624 controlId,
625 item.toPath(IPathFormatter.METAPATH_PATH_FORMATER)));
626 }
627 Control control = entity.getInstanceValue();
628
629 METAPATH_ALTER_REMOVE.evaluate(item)
630 .forEach(nodeItem -> {
631 INodeItem removeItem = (INodeItem) nodeItem;
632 Modify.Alter.Remove remove = ObjectUtils.notNull((Modify.Alter.Remove) removeItem.getValue());
633
634 try {
635 if (!RemoveVisitor.remove(
636 control,
637 remove.getByName(),
638 remove.getByClass(),
639 remove.getById(),
640 remove.getByNs(),
641 RemoveVisitor.TargetType.forFieldName(remove.getByItemName()))) {
642 throw new ProfileResolutionEvaluationException(
643 String.format("The remove did not match a valid target"));
644 }
645 } catch (ProfileResolutionEvaluationException ex) {
646 throw new ProfileResolutionEvaluationException(
647 String.format("Unable to apply the remove targeting control '%s' at '%s'. %s",
648 control.getId(),
649 removeItem.toPath(IPathFormatter.METAPATH_PATH_FORMATER),
650 ex.getLocalizedMessage()),
651 ex);
652 }
653 });
654 METAPATH_ALTER_ADD.evaluate(item)
655 .forEach(nodeItem -> {
656 INodeItem addItem = (INodeItem) nodeItem;
657 Modify.Alter.Add add = ObjectUtils.notNull((Modify.Alter.Add) addItem.getValue());
658 String byId = add.getById();
659 try {
660 if (!AddVisitor.add(
661 control,
662 AddVisitor.Position.forName(add.getPosition()),
663 byId,
664 add.getTitle(),
665 CollectionUtil.listOrEmpty(add.getParams()),
666 CollectionUtil.listOrEmpty(add.getProps()),
667 CollectionUtil.listOrEmpty(add.getLinks()),
668 CollectionUtil.listOrEmpty(add.getParts()))) {
669
670 throw new ProfileResolutionEvaluationException(
671 String.format("The add did not match a valid target"));
672 }
673 } catch (ProfileResolutionEvaluationException ex) {
674 throw new ProfileResolutionEvaluationException(
675 String.format("Unable to apply the add targeting control '%s'%s at '%s'. %s",
676 control.getId(),
677 byId == null ? "" : String.format(" having by-id '%s'", byId),
678 addItem.toPath(IPathFormatter.METAPATH_PATH_FORMATER),
679 ex.getLocalizedMessage()),
680 ex);
681 }
682 });
683 }
684
685 private static void handleReferences(@NonNull Catalog resolvedCatalog, @NonNull IRootAssemblyNodeItem profileItem,
686 @NonNull IIndexer index) {
687
688 BasicIndexer profileIndex = new BasicIndexer();
689
690 new ControlIndexingVisitor(ObjectUtils.notNull(EnumSet.allOf(ItemType.class)))
691 .visitProfile(profileItem, profileIndex);
692
693
694 Metadata resolvedMetadata = resolvedCatalog.getMetadata();
695 resolvedMetadata.setRoles(
696 IIndexer.filterDistinct(
697 ObjectUtils.notNull(CollectionUtil.listOrEmpty(resolvedMetadata.getRoles()).stream()),
698 profileIndex.getEntitiesByItemType(IEntityItem.ItemType.ROLE),
699 Role::getId)
700 .collect(Collectors.toCollection(LinkedList::new)));
701 resolvedMetadata.setParties(
702 IIndexer.filterDistinct(
703 ObjectUtils.notNull(CollectionUtil.listOrEmpty(resolvedMetadata.getParties()).stream()),
704 profileIndex.getEntitiesByItemType(IEntityItem.ItemType.PARTY),
705 Party::getUuid)
706 .collect(Collectors.toCollection(LinkedList::new)));
707 resolvedMetadata.setLocations(
708 IIndexer.filterDistinct(
709 ObjectUtils.notNull(CollectionUtil.listOrEmpty(resolvedMetadata.getLocations()).stream()),
710 profileIndex.getEntitiesByItemType(IEntityItem.ItemType.LOCATION),
711 Location::getUuid)
712 .collect(Collectors.toCollection(LinkedList::new)));
713
714
715 BackMatter resolvedBackMatter = resolvedCatalog.getBackMatter();
716 List<Resource> resolvedResources = resolvedBackMatter == null ? CollectionUtil.emptyList()
717 : CollectionUtil.listOrEmpty(resolvedBackMatter.getResources());
718
719 List<Resource> resources = IIndexer.filterDistinct(
720 ObjectUtils.notNull(resolvedResources.stream()),
721 profileIndex.getEntitiesByItemType(IEntityItem.ItemType.RESOURCE),
722 Resource::getUuid)
723 .collect(Collectors.toCollection(LinkedList::new));
724
725 if (!resources.isEmpty()) {
726 if (resolvedBackMatter == null) {
727 resolvedBackMatter = new BackMatter();
728 resolvedCatalog.setBackMatter(resolvedBackMatter);
729 }
730
731 resolvedBackMatter.setResources(resources);
732 }
733
734 index.append(profileIndex);
735 }
736
737 }