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