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