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