001/* 002 * SPDX-FileCopyrightText: none 003 * SPDX-License-Identifier: CC0-1.0 004 */ 005 006package gov.nist.secauto.oscal.lib.profile.resolver; 007 008import gov.nist.secauto.metaschema.core.metapath.DynamicContext; 009import gov.nist.secauto.metaschema.core.metapath.IDocumentLoader; 010import gov.nist.secauto.metaschema.core.metapath.IMetapathExpression; 011import gov.nist.secauto.metaschema.core.metapath.StaticContext; 012import gov.nist.secauto.metaschema.core.metapath.format.IPathFormatter; 013import gov.nist.secauto.metaschema.core.metapath.function.FunctionUtils; 014import gov.nist.secauto.metaschema.core.metapath.item.IItem; 015import gov.nist.secauto.metaschema.core.metapath.item.ISequence; 016import gov.nist.secauto.metaschema.core.metapath.item.node.IAssemblyNodeItem; 017import gov.nist.secauto.metaschema.core.metapath.item.node.IDocumentNodeItem; 018import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem; 019import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItemFactory; 020import gov.nist.secauto.metaschema.core.metapath.item.node.IRootAssemblyNodeItem; 021import gov.nist.secauto.metaschema.core.model.IBoundObject; 022import gov.nist.secauto.metaschema.core.qname.IEnhancedQName; 023import gov.nist.secauto.metaschema.core.util.CollectionUtil; 024import gov.nist.secauto.metaschema.core.util.ObjectUtils; 025import gov.nist.secauto.metaschema.databind.io.BindingException; 026import gov.nist.secauto.metaschema.databind.io.DeserializationFeature; 027import gov.nist.secauto.metaschema.databind.io.IBoundLoader; 028import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelAssembly; 029import gov.nist.secauto.oscal.lib.OscalBindingContext; 030import gov.nist.secauto.oscal.lib.OscalModelConstants; 031import gov.nist.secauto.oscal.lib.OscalUtils; 032import gov.nist.secauto.oscal.lib.model.BackMatter; 033import gov.nist.secauto.oscal.lib.model.BackMatter.Resource; 034import gov.nist.secauto.oscal.lib.model.BackMatter.Resource.Base64; 035import gov.nist.secauto.oscal.lib.model.BackMatter.Resource.Rlink; 036import gov.nist.secauto.oscal.lib.model.Catalog; 037import gov.nist.secauto.oscal.lib.model.Control; 038import gov.nist.secauto.oscal.lib.model.Merge; 039import gov.nist.secauto.oscal.lib.model.Metadata; 040import gov.nist.secauto.oscal.lib.model.Metadata.Location; 041import gov.nist.secauto.oscal.lib.model.Metadata.Party; 042import gov.nist.secauto.oscal.lib.model.Metadata.Role; 043import gov.nist.secauto.oscal.lib.model.Modify; 044import gov.nist.secauto.oscal.lib.model.Modify.ProfileSetParameter; 045import gov.nist.secauto.oscal.lib.model.Parameter; 046import gov.nist.secauto.oscal.lib.model.Profile; 047import gov.nist.secauto.oscal.lib.model.ProfileImport; 048import gov.nist.secauto.oscal.lib.model.Property; 049import gov.nist.secauto.oscal.lib.model.metadata.AbstractLink; 050import gov.nist.secauto.oscal.lib.model.metadata.AbstractProperty; 051import gov.nist.secauto.oscal.lib.profile.resolver.alter.AddVisitor; 052import gov.nist.secauto.oscal.lib.profile.resolver.alter.RemoveVisitor; 053import gov.nist.secauto.oscal.lib.profile.resolver.merge.FlatteningStructuringVisitor; 054import gov.nist.secauto.oscal.lib.profile.resolver.selection.Import; 055import gov.nist.secauto.oscal.lib.profile.resolver.selection.ImportCycleException; 056import gov.nist.secauto.oscal.lib.profile.resolver.support.BasicIndexer; 057import gov.nist.secauto.oscal.lib.profile.resolver.support.ControlIndexingVisitor; 058import gov.nist.secauto.oscal.lib.profile.resolver.support.IEntityItem; 059import gov.nist.secauto.oscal.lib.profile.resolver.support.IEntityItem.ItemType; 060import gov.nist.secauto.oscal.lib.profile.resolver.support.IIndexer; 061 062import org.apache.logging.log4j.LogManager; 063import org.apache.logging.log4j.Logger; 064 065import java.io.File; 066import java.io.IOException; 067import java.net.URI; 068import java.net.URISyntaxException; 069import java.net.URL; 070import java.nio.ByteBuffer; 071import java.nio.file.Path; 072import java.time.ZoneOffset; 073import java.time.ZonedDateTime; 074import java.util.EnumSet; 075import java.util.LinkedList; 076import java.util.List; 077import java.util.Stack; 078import java.util.UUID; 079import java.util.stream.Collectors; 080 081import edu.umd.cs.findbugs.annotations.NonNull; 082import edu.umd.cs.findbugs.annotations.Nullable; 083 084public class ProfileResolver { 085 086 public enum StructuringDirective { 087 FLAT, 088 AS_IS, 089 CUSTOM; 090 } 091 092 private static final Logger LOGGER = LogManager.getLogger(ProfileResolver.class); 093 @NonNull 094 private static final IEnhancedQName IMPORT_QNAME = IEnhancedQName.of(OscalModelConstants.NS_OSCAL, "import"); 095 096 @NonNull 097 private static final IMetapathExpression METAPATH_SET_PARAMETER 098 = IMetapathExpression.compile("modify/set-parameter", 099 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 * Gets the configured loader or creates a new default loader if no loader was 151 * configured. 152 * 153 * @return the bound loader 154 * @since 5.0.0 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 // already a catalog 229 retval = profileOrCatalog.getParentNodeItem(); 230 } else { 231 // must be a profile 232 retval = resolveProfile(profileOrCatalog, importHistory); 233 } 234 return retval; 235 } 236 237 /** 238 * Resolve the profile to a catalog. 239 * 240 * @param profileItem 241 * a {@link IDocumentNodeItem} containing the profile to resolve 242 * @param importHistory 243 * the import stack for cycle detection 244 * @return the resolved profile 245 * @throws IOException 246 * if an error occurred while loading the profile or an import 247 * @throws ProfileResolutionException 248 * if an error occurred while resolving the profile 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 // metadata.setOscalVersion(OscalUtils.OSCAL_VERSION); 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 // first verify there is at least one import 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 // now process each import 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; // always non-null 357 358 // Import import = Import. 359 // InputSource source = newImportSource(importUri, profileItem); 360 // URI sourceUri = ObjectUtils.notNull(URI.create(source.getSystemId())); 361 362 // check for import cycle 363 try { 364 requireNonCycle( 365 importedUri, 366 importHistory); 367 } catch (ImportCycleException ex) { 368 throw new IOException(ex); 369 } 370 371 // track the import in the import history 372 importHistory.push(importedUri); 373 try { 374 IDocumentNodeItem importedCatalog = resolve(importedDocument, importHistory); 375 376 // Create a defensive deep copy of the document and associated values, since we 377 // will be making 378 // changes to the data. 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 // pop the resolved catalog from the import history 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 // handle internal reference 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 // first try base64 data 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 // TODO: move this to an abstract method on profile 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 // handle combine 511 512 // handle structuring 513 switch (getStructuringDirective(toProfile(profileItem))) { 514 case AS_IS: 515 // do nothing 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 // // rebuild an index 535 // IDocumentNodeItem resolvedCatalogItem = 536 // DefaultNodeItemFactory.instance().newDocumentNodeItem( 537 // new RootAssemblyDefinition( 538 // ObjectUtils.notNull( 539 // (IAssemblyClassBinding) 540 // OscalBindingContext.instance().getClassBinding(Catalog.class))), 541 // resolvedCatalog, 542 // profileDocument.getBaseUri()); 543 // 544 // // FIXME: need to find a better way to create an index that doesn't auto 545 // select groups 546 // IIndexer indexer = new BasicIndexer(); 547 // ControlSelectionVisitor selectionVisitor 548 // = new ControlSelectionVisitor(IControlFilter.ALWAYS_MATCH, indexer); 549 // selectionVisitor.visitCatalog(resolvedCatalogItem); 550 // } 551 552 // rebuild the document, since the paths have changed 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") // ok 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 // apply the set parameter values 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 // copy roles, parties, and locations with prop name:keep and any referenced 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 // copy resources 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}