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.util.CollectionUtil; 023import gov.nist.secauto.metaschema.core.util.ObjectUtils; 024import gov.nist.secauto.metaschema.databind.io.BindingException; 025import gov.nist.secauto.metaschema.databind.io.DeserializationFeature; 026import gov.nist.secauto.metaschema.databind.io.IBoundLoader; 027import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelAssembly; 028import gov.nist.secauto.oscal.lib.OscalBindingContext; 029import gov.nist.secauto.oscal.lib.OscalModelConstants; 030import gov.nist.secauto.oscal.lib.OscalUtils; 031import gov.nist.secauto.oscal.lib.model.BackMatter; 032import gov.nist.secauto.oscal.lib.model.BackMatter.Resource; 033import gov.nist.secauto.oscal.lib.model.BackMatter.Resource.Base64; 034import gov.nist.secauto.oscal.lib.model.BackMatter.Resource.Rlink; 035import gov.nist.secauto.oscal.lib.model.Catalog; 036import gov.nist.secauto.oscal.lib.model.Control; 037import gov.nist.secauto.oscal.lib.model.Merge; 038import gov.nist.secauto.oscal.lib.model.Metadata; 039import gov.nist.secauto.oscal.lib.model.Metadata.Location; 040import gov.nist.secauto.oscal.lib.model.Metadata.Party; 041import gov.nist.secauto.oscal.lib.model.Metadata.Role; 042import gov.nist.secauto.oscal.lib.model.Modify; 043import gov.nist.secauto.oscal.lib.model.Modify.ProfileSetParameter; 044import gov.nist.secauto.oscal.lib.model.Parameter; 045import gov.nist.secauto.oscal.lib.model.Profile; 046import gov.nist.secauto.oscal.lib.model.ProfileImport; 047import gov.nist.secauto.oscal.lib.model.Property; 048import gov.nist.secauto.oscal.lib.model.metadata.AbstractLink; 049import gov.nist.secauto.oscal.lib.model.metadata.AbstractProperty; 050import gov.nist.secauto.oscal.lib.profile.resolver.alter.AddVisitor; 051import gov.nist.secauto.oscal.lib.profile.resolver.alter.RemoveVisitor; 052import gov.nist.secauto.oscal.lib.profile.resolver.merge.FlatteningStructuringVisitor; 053import gov.nist.secauto.oscal.lib.profile.resolver.selection.Import; 054import gov.nist.secauto.oscal.lib.profile.resolver.selection.ImportCycleException; 055import gov.nist.secauto.oscal.lib.profile.resolver.support.BasicIndexer; 056import gov.nist.secauto.oscal.lib.profile.resolver.support.ControlIndexingVisitor; 057import gov.nist.secauto.oscal.lib.profile.resolver.support.IEntityItem; 058import gov.nist.secauto.oscal.lib.profile.resolver.support.IEntityItem.ItemType; 059import gov.nist.secauto.oscal.lib.profile.resolver.support.IIndexer; 060 061import org.apache.logging.log4j.LogManager; 062import org.apache.logging.log4j.Logger; 063 064import java.io.File; 065import java.io.IOException; 066import java.net.URI; 067import java.net.URISyntaxException; 068import java.net.URL; 069import java.nio.ByteBuffer; 070import java.nio.file.Path; 071import java.time.ZoneOffset; 072import java.time.ZonedDateTime; 073import java.util.EnumSet; 074import java.util.LinkedList; 075import java.util.List; 076import java.util.Stack; 077import java.util.UUID; 078import java.util.stream.Collectors; 079 080import javax.xml.namespace.QName; 081 082import edu.umd.cs.findbugs.annotations.NonNull; 083import edu.umd.cs.findbugs.annotations.Nullable; 084import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 085 086public class ProfileResolver { 087 private static final Logger LOGGER = LogManager.getLogger(ProfileResolver.class); 088 @NonNull 089 private static final QName IMPORT_QNAME = new QName(OscalModelConstants.NS_OSCAL, "import"); 090 091 @NonNull 092 private static final MetapathExpression METAPATH_SET_PARAMETER 093 = MetapathExpression.compile("modify/set-parameter", 094 OscalBindingContext.OSCAL_STATIC_METAPATH_CONTEXT); 095 @NonNull 096 private static final MetapathExpression METAPATH_ALTER 097 = MetapathExpression.compile("modify/alter", 098 OscalBindingContext.OSCAL_STATIC_METAPATH_CONTEXT); 099 @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 * Gets the configured loader or creates a new default loader if no loader was 127 * configured. 128 * 129 * @return the bound loader 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 // already a catalog 234 retval = profileOrCatalog.getParentNodeItem(); 235 } else { 236 // must be a profile 237 retval = resolveProfile(profileOrCatalog, importHistory); 238 } 239 return retval; 240 } 241 242 /** 243 * Resolve the profile to a catalog. 244 * 245 * @param profileItem 246 * a {@link IDocumentNodeItem} containing the profile to resolve 247 * @param importHistory 248 * the import stack for cycle detection 249 * @return the resolved profile 250 * @throws IOException 251 * if an error occurred while loading the profile or an import 252 * @throws ProfileResolutionException 253 * if an error occurred while resolving the profile 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 // metadata.setOscalVersion(OscalUtils.OSCAL_VERSION); 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 // first verify there is at least one import 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 // now process each import 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; // always non-null 358 359 // Import import = Import. 360 // InputSource source = newImportSource(importUri, profileItem); 361 // URI sourceUri = ObjectUtils.notNull(URI.create(source.getSystemId())); 362 363 // check for import cycle 364 try { 365 requireNonCycle( 366 importedUri, 367 importHistory); 368 } catch (ImportCycleException ex) { 369 throw new IOException(ex); 370 } 371 372 // track the import in the import history 373 importHistory.push(importedUri); 374 try { 375 IDocumentNodeItem importedCatalog = resolve(importedDocument, importHistory); 376 377 // Create a defensive deep copy of the document and associated values, since we 378 // will be making 379 // changes to the data. 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 // 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(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 // TODO: move this to an abstract method on profile 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 // handle combine 506 507 // handle structuring 508 switch (getStructuringDirective(toProfile(profileItem))) { 509 case AS_IS: 510 // do nothing 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 // // rebuild an index 530 // IDocumentNodeItem resolvedCatalogItem = 531 // DefaultNodeItemFactory.instance().newDocumentNodeItem( 532 // new RootAssemblyDefinition( 533 // ObjectUtils.notNull( 534 // (IAssemblyClassBinding) 535 // OscalBindingContext.instance().getClassBinding(Catalog.class))), 536 // resolvedCatalog, 537 // profileDocument.getBaseUri()); 538 // 539 // // FIXME: need to find a better way to create an index that doesn't auto 540 // select groups 541 // IIndexer indexer = new BasicIndexer(); 542 // ControlSelectionVisitor selectionVisitor 543 // = new ControlSelectionVisitor(IControlFilter.ALWAYS_MATCH, indexer); 544 // selectionVisitor.visitCatalog(resolvedCatalogItem); 545 // } 546 547 // rebuild the document, since the paths have changed 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") // ok 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 // apply the set parameter values 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 // copy roles, parties, and locations with prop name:keep and any referenced 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 // copy resources 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}