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