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