1 /* 2 * SPDX-FileCopyrightText: none 3 * SPDX-License-Identifier: CC0-1.0 4 */ 5 6 package gov.nist.secauto.metaschema.databind; 7 8 import gov.nist.secauto.metaschema.core.configuration.IConfiguration; 9 import gov.nist.secauto.metaschema.core.datatype.DataTypeService; 10 import gov.nist.secauto.metaschema.core.datatype.IDataTypeAdapter; 11 import gov.nist.secauto.metaschema.core.metapath.DynamicContext; 12 import gov.nist.secauto.metaschema.core.metapath.item.node.IDefinitionNodeItem; 13 import gov.nist.secauto.metaschema.core.metapath.item.node.IDocumentNodeItem; 14 import gov.nist.secauto.metaschema.core.metapath.item.node.IRootAssemblyNodeItem; 15 import gov.nist.secauto.metaschema.core.model.IBoundObject; 16 import gov.nist.secauto.metaschema.core.model.IConstraintLoader; 17 import gov.nist.secauto.metaschema.core.model.IModule; 18 import gov.nist.secauto.metaschema.core.model.IModuleLoader; 19 import gov.nist.secauto.metaschema.core.model.MetaschemaException; 20 import gov.nist.secauto.metaschema.core.model.constraint.DefaultConstraintValidator; 21 import gov.nist.secauto.metaschema.core.model.constraint.ExternalConstraintsModulePostProcessor; 22 import gov.nist.secauto.metaschema.core.model.constraint.FindingCollectingConstraintValidationHandler; 23 import gov.nist.secauto.metaschema.core.model.constraint.IConstraintSet; 24 import gov.nist.secauto.metaschema.core.model.constraint.IConstraintValidationHandler; 25 import gov.nist.secauto.metaschema.core.model.constraint.IConstraintValidator; 26 import gov.nist.secauto.metaschema.core.model.constraint.ValidationFeature; 27 import gov.nist.secauto.metaschema.core.model.validation.AggregateValidationResult; 28 import gov.nist.secauto.metaschema.core.model.validation.IValidationResult; 29 import gov.nist.secauto.metaschema.core.model.validation.JsonSchemaContentValidator; 30 import gov.nist.secauto.metaschema.core.model.validation.XmlSchemaContentValidator; 31 import gov.nist.secauto.metaschema.core.util.CollectionUtil; 32 import gov.nist.secauto.metaschema.core.util.ObjectUtils; 33 import gov.nist.secauto.metaschema.databind.codegen.DefaultModuleBindingGenerator; 34 import gov.nist.secauto.metaschema.databind.io.BindingException; 35 import gov.nist.secauto.metaschema.databind.io.DefaultBoundLoader; 36 import gov.nist.secauto.metaschema.databind.io.DeserializationFeature; 37 import gov.nist.secauto.metaschema.databind.io.Format; 38 import gov.nist.secauto.metaschema.databind.io.IBoundLoader; 39 import gov.nist.secauto.metaschema.databind.io.IDeserializer; 40 import gov.nist.secauto.metaschema.databind.io.ISerializer; 41 import gov.nist.secauto.metaschema.databind.io.yaml.YamlOperations; 42 import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModel; 43 import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelAssembly; 44 import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelComplex; 45 import gov.nist.secauto.metaschema.databind.model.IBoundModule; 46 import gov.nist.secauto.metaschema.databind.model.annotations.MetaschemaAssembly; 47 import gov.nist.secauto.metaschema.databind.model.annotations.MetaschemaField; 48 import gov.nist.secauto.metaschema.databind.model.metaschema.BindingConstraintLoader; 49 import gov.nist.secauto.metaschema.databind.model.metaschema.IBindingMetaschemaModule; 50 import gov.nist.secauto.metaschema.databind.model.metaschema.IBindingModuleLoader; 51 import gov.nist.secauto.metaschema.databind.model.metaschema.ModuleLoadingPostProcessor; 52 53 import org.json.JSONObject; 54 import org.json.JSONTokener; 55 import org.xml.sax.SAXException; 56 57 import java.io.BufferedInputStream; 58 import java.io.FileNotFoundException; 59 import java.io.IOException; 60 import java.io.InputStream; 61 import java.math.BigInteger; 62 import java.net.URI; 63 import java.net.URL; 64 import java.nio.file.Path; 65 import java.time.ZonedDateTime; 66 import java.util.Collection; 67 import java.util.LinkedList; 68 import java.util.List; 69 import java.util.function.Function; 70 71 import javax.xml.namespace.QName; 72 73 import edu.umd.cs.findbugs.annotations.NonNull; 74 import edu.umd.cs.findbugs.annotations.Nullable; 75 76 /** 77 * Provides information supporting a binding between a set of Module models and 78 * corresponding Java classes. 79 */ 80 public interface IBindingContext { 81 /** 82 * Get a new builder that can produce a new, configured binding context. 83 * 84 * @return the builder 85 * @since 2.0.0 86 */ 87 static BindingContextBuilder builder() { 88 return new BindingContextBuilder(); 89 } 90 91 /** 92 * Get a new {@link IBindingContext} instance, which can be used to load 93 * information that binds a model to a set of Java classes. 94 * 95 * @return a new binding context 96 * @since 2.0.0 97 */ 98 @NonNull 99 static IBindingContext newInstance() { 100 return new DefaultBindingContext(); 101 } 102 103 /** 104 * Get a new {@link IBindingContext} instance, which can be used to load 105 * information that binds a model to a set of Java classes. 106 * 107 * @param strategy 108 * the loader strategy to use when loading Metaschema modules 109 * @return a new binding context 110 * @since 2.0.0 111 */ 112 @NonNull 113 static IBindingContext newInstance(@NonNull IBindingContext.IModuleLoaderStrategy strategy) { 114 return new DefaultBindingContext(strategy); 115 } 116 117 /** 118 * Get the Metaschema module loader strategy used by this binding context to 119 * load modules. 120 * 121 * @return the strategy instance 122 * @since 2.0.0 123 */ 124 @NonNull 125 IModuleLoaderStrategy getModuleLoaderStrategy(); 126 127 /** 128 * Get a loader that supports loading a Metaschema module from a specified 129 * resource. 130 * <p> 131 * Modules loaded with this loader are automatically registered with this 132 * binding context. 133 * <p> 134 * Use of this method requires that the binding context is initialized using a 135 * {@link IModuleLoaderStrategy} that supports dynamic bound module loading. 136 * This can be accomplished using the {@link SimpleModuleLoaderStrategy} 137 * initialized using the {@link DefaultModuleBindingGenerator}. * @return the 138 * loader 139 * 140 * @return the loader 141 * @since 2.0.0 142 */ 143 @NonNull 144 IBindingModuleLoader newModuleLoader(); 145 146 /** 147 * Loads a Metaschema module from the specified path. 148 * <p> 149 * This method automatically registers the module with this binding context. 150 * <p> 151 * Use of this method requires that the binding context is initialized using a 152 * {@link IModuleLoaderStrategy} that supports dynamic bound module loading. 153 * This can be accomplished using the {@link SimpleModuleLoaderStrategy} 154 * initialized using the {@link DefaultModuleBindingGenerator}. 155 * 156 * @param path 157 * the path to load the module from 158 * @return the loaded Metaschema module 159 * @throws MetaschemaException 160 * if an error occurred while processing the resource 161 * @throws IOException 162 * if an error occurred parsing the resource 163 * @throws UnsupportedOperationException 164 * if this binding context is not configured to support dynamic bound 165 * module loading 166 * @since 2.0.0 167 */ 168 @NonNull 169 default IBindingMetaschemaModule loadMetaschema(@NonNull Path path) throws MetaschemaException, IOException { 170 return newModuleLoader().load(path); 171 } 172 173 /** 174 * Loads a Metaschema module from the specified URL. 175 * <p> 176 * This method automatically registers the module with this binding context. 177 * <p> 178 * Use of this method requires that the binding context is initialized using a 179 * {@link IModuleLoaderStrategy} that supports dynamic bound module loading. 180 * This can be accomplished using the {@link SimpleModuleLoaderStrategy} 181 * initialized using the {@link DefaultModuleBindingGenerator}. 182 * 183 * @param url 184 * the URL to load the module from 185 * @return the loaded Metaschema module 186 * @throws MetaschemaException 187 * if an error occurred while processing the resource 188 * @throws IOException 189 * if an error occurred parsing the resource 190 * @throws UnsupportedOperationException 191 * if this binding context is not configured to support dynamic bound 192 * module loading 193 * @since 2.0.0 194 */ 195 @NonNull 196 default IBindingMetaschemaModule loadMetaschema(@NonNull URL url) throws MetaschemaException, IOException { 197 return newModuleLoader().load(url); 198 } 199 200 /** 201 * Get a loader that supports loading Metaschema module constraints from a 202 * specified resource. 203 * <p> 204 * Metaschema module constraints loaded this need to be used with a new 205 * {@link IBindingContext} instance to be applied to loaded modules. The new 206 * binding context must initialized using the 207 * {@link PostProcessingModuleLoaderStrategy} that is initialized with a 208 * {@link ExternalConstraintsModulePostProcessor} instance. 209 * 210 * @return the loader 211 * @since 2.0.0 212 */ 213 @NonNull 214 static IConstraintLoader getConstraintLoader() { 215 return new BindingConstraintLoader(DefaultBindingContext.instance()); 216 } 217 218 /** 219 * Get a loader that supports loading Metaschema module constraints from a 220 * specified resource. 221 * <p> 222 * Metaschema module constraints loaded this need to be used with a new 223 * {@link IBindingContext} instance to be applied to loaded modules. The new 224 * binding context must initialized using the 225 * {@link PostProcessingModuleLoaderStrategy} that is initialized with a 226 * {@link ExternalConstraintsModulePostProcessor} instance. 227 * 228 * @return the loader 229 * @since 2.0.0 230 */ 231 @NonNull 232 default IConstraintLoader newConstraintLoader() { 233 return new BindingConstraintLoader(this); 234 } 235 236 /** 237 * Load a bound Metaschema module implemented by the provided class. 238 * <p> 239 * Also registers any associated bound classes. 240 * <p> 241 * Implementations are expected to return the same IModule instance for multiple 242 * calls to this method with the same class argument. 243 * 244 * @param clazz 245 * the class implementing a bound Metaschema module 246 * @return the loaded module 247 */ 248 @NonNull 249 IBoundModule registerModule(@NonNull Class<? extends IBoundModule> clazz); 250 251 /** 252 * Registers the provided Metaschema module with this binding context. 253 * <p> 254 * If the provided instance is not an instance of {@link IBoundModule}, then 255 * annotated Java classes for this module will be generated, compiled, and 256 * loaded based on the provided Module. 257 * 258 * @param module 259 * the Module module to generate classes for 260 * @return the registered module, which may be a different instance than what 261 * was provided if dynamic compilation was performed 262 * @throws UnsupportedOperationException 263 * if this binding context is not configured to support dynamic bound 264 * module loading and the module instance is not a subclass of 265 * {@link IBoundModule} 266 * @since 2.0.0 267 */ 268 @NonNull 269 default IBoundModule registerModule(@NonNull IModule module) { 270 return getModuleLoaderStrategy().registerModule(module, this); 271 } 272 273 /** 274 * Register a class binding for a given bound class. 275 * 276 * @param definition 277 * the bound class information to register 278 * @return the old bound class information or {@code null} if no binding existed 279 * for the associated class 280 */ 281 @Nullable 282 IBoundDefinitionModelComplex registerClassBinding(@NonNull IBoundDefinitionModelComplex definition); 283 284 /** 285 * Get the {@link IBoundDefinitionModel} instance associated with the provided 286 * Java class. 287 * <p> 288 * Typically the class will have a {@link MetaschemaAssembly} or 289 * {@link MetaschemaField} annotation. 290 * 291 * @param clazz 292 * the class binding to load 293 * @return the associated class binding instance or {@code null} if the class is 294 * not bound 295 */ 296 @Nullable 297 IBoundDefinitionModelComplex getBoundDefinitionForClass(@NonNull Class<? extends IBoundObject> clazz); 298 299 /** 300 * Determine the bound class for the provided XML {@link QName}. 301 * 302 * @param rootQName 303 * the root XML element's QName 304 * @return the bound class or {@code null} if not recognized 305 */ 306 @Nullable 307 Class<? extends IBoundObject> getBoundClassForRootXmlQName(@NonNull QName rootQName); 308 309 /** 310 * Determine the bound class for the provided JSON/YAML property/item name using 311 * any registered matchers. 312 * 313 * @param rootName 314 * the JSON/YAML property/item name 315 * @return the bound class or {@code null} if not recognized 316 */ 317 @Nullable 318 Class<? extends IBoundObject> getBoundClassForRootJsonName(@NonNull String rootName); 319 320 /** 321 * Get's the {@link IDataTypeAdapter} associated with the specified Java class, 322 * which is used to read and write XML, JSON, and YAML data to and from 323 * instances of that class. Thus, this adapter supports a direct binding between 324 * the Java class and structured data in one of the supported formats. Adapters 325 * are used to support bindings for simple data objects (e.g., {@link String}, 326 * {@link BigInteger}, {@link ZonedDateTime}, etc). 327 * 328 * @param <TYPE> 329 * the class type of the adapter 330 * @param clazz 331 * the Java {@link Class} for the bound type 332 * @return the adapter instance or {@code null} if the provided class is not 333 * bound 334 */ 335 @Nullable 336 default <TYPE extends IDataTypeAdapter<?>> TYPE getJavaTypeAdapterInstance(@NonNull Class<TYPE> clazz) { 337 return DataTypeService.getInstance().getJavaTypeAdapterByClass(clazz); 338 } 339 340 /** 341 * Gets a data {@link ISerializer} which can be used to write Java instance data 342 * for the provided class in the requested format. 343 * <p> 344 * The provided class must be a bound Java class with a 345 * {@link MetaschemaAssembly} or {@link MetaschemaField} annotation for which a 346 * {@link IBoundDefinitionModel} exists. 347 * 348 * @param <CLASS> 349 * the Java type this serializer can write data from 350 * @param format 351 * the format to serialize into 352 * @param clazz 353 * the Java data object to serialize 354 * @return the serializer instance 355 * @throws NullPointerException 356 * if any of the provided arguments, except the configuration, are 357 * {@code null} 358 * @throws IllegalArgumentException 359 * if the provided class is not bound to a Module assembly or field 360 * @throws UnsupportedOperationException 361 * if the requested format is not supported by the implementation 362 * @see #getBoundDefinitionForClass(Class) 363 */ 364 @NonNull 365 <CLASS extends IBoundObject> ISerializer<CLASS> newSerializer( 366 @NonNull Format format, 367 @NonNull Class<CLASS> clazz); 368 369 /** 370 * Gets a data {@link IDeserializer} which can be used to read Java instance 371 * data for the provided class from the requested format. 372 * <p> 373 * The provided class must be a bound Java class with a 374 * {@link MetaschemaAssembly} or {@link MetaschemaField} annotation for which a 375 * {@link IBoundDefinitionModel} exists. 376 * 377 * @param <CLASS> 378 * the Java type this deserializer can read data into 379 * @param format 380 * the format to serialize into 381 * @param clazz 382 * the Java data type to serialize 383 * @return the deserializer instance 384 * @throws NullPointerException 385 * if any of the provided arguments, except the configuration, are 386 * {@code null} 387 * @throws IllegalArgumentException 388 * if the provided class is not bound to a Module assembly or field 389 * @throws UnsupportedOperationException 390 * if the requested format is not supported by the implementation 391 * @see #getBoundDefinitionForClass(Class) 392 */ 393 @NonNull 394 <CLASS extends IBoundObject> IDeserializer<CLASS> newDeserializer( 395 @NonNull Format format, 396 @NonNull Class<CLASS> clazz); 397 398 /** 399 * Get a new {@link IBoundLoader} instance to load bound content instances. 400 * 401 * @return the instance 402 */ 403 @NonNull 404 default IBoundLoader newBoundLoader() { 405 return new DefaultBoundLoader(this); 406 } 407 408 /** 409 * Create a deep copy of the provided bound object. 410 * 411 * @param <CLASS> 412 * the bound object type 413 * @param other 414 * the object to copy 415 * @param parentInstance 416 * the object's parent or {@code null} 417 * @return a deep copy of the provided object 418 * @throws BindingException 419 * if an error occurred copying content between java instances 420 * @throws NullPointerException 421 * if the provided object is {@code null} 422 * @throws IllegalArgumentException 423 * if the provided class is not bound to a Module assembly or field 424 */ 425 @NonNull 426 <CLASS extends IBoundObject> CLASS deepCopy(@NonNull CLASS other, IBoundObject parentInstance) 427 throws BindingException; 428 429 /** 430 * Get a new single use constraint validator. 431 * 432 * @param handler 433 * the validation handler to use to process the validation results 434 * @param config 435 * the validation configuration 436 * 437 * @return the validator 438 */ 439 default IConstraintValidator newValidator( 440 @NonNull IConstraintValidationHandler handler, 441 @Nullable IConfiguration<ValidationFeature<?>> config) { 442 IBoundLoader loader = newBoundLoader(); 443 loader.disableFeature(DeserializationFeature.DESERIALIZE_VALIDATE_CONSTRAINTS); 444 445 DynamicContext context = new DynamicContext(); 446 context.setDocumentLoader(loader); 447 448 DefaultConstraintValidator retval = new DefaultConstraintValidator(handler); 449 if (config != null) { 450 retval.applyConfiguration(config); 451 } 452 return retval; 453 } 454 455 /** 456 * Perform constraint validation on the provided bound object represented as an 457 * {@link IDocumentNodeItem}. 458 * 459 * @param nodeItem 460 * the node item to validate 461 * @param loader 462 * a module loader used to load and resolve referenced resources 463 * @param config 464 * the validation configuration 465 * @return the validation result 466 * @throws IllegalArgumentException 467 * if the provided class is not bound to a Module assembly or field 468 */ 469 default IValidationResult validate( 470 @NonNull IDocumentNodeItem nodeItem, 471 @NonNull IBoundLoader loader, 472 @Nullable IConfiguration<ValidationFeature<?>> config) { 473 IRootAssemblyNodeItem root = nodeItem.getRootAssemblyNodeItem(); 474 return validate(root, loader, config); 475 } 476 477 /** 478 * Perform constraint validation on the provided bound object represented as an 479 * {@link IDefinitionNodeItem}. 480 * 481 * @param nodeItem 482 * the node item to validate 483 * @param loader 484 * a module loader used to load and resolve referenced resources 485 * @param config 486 * the validation configuration 487 * @return the validation result 488 * @throws IllegalArgumentException 489 * if the provided class is not bound to a Module assembly or field 490 */ 491 default IValidationResult validate( 492 @NonNull IDefinitionNodeItem<?, ?> nodeItem, 493 @NonNull IBoundLoader loader, 494 @Nullable IConfiguration<ValidationFeature<?>> config) { 495 496 FindingCollectingConstraintValidationHandler handler = new FindingCollectingConstraintValidationHandler(); 497 IConstraintValidator validator = newValidator(handler, config); 498 499 DynamicContext dynamicContext = new DynamicContext(nodeItem.getStaticContext()); 500 dynamicContext.setDocumentLoader(loader); 501 502 validator.validate(nodeItem, dynamicContext); 503 validator.finalizeValidation(dynamicContext); 504 return handler; 505 } 506 507 /** 508 * Load and perform schema and constraint validation on the target. The 509 * constraint validation will only be performed if the schema validation passes. 510 * 511 * @param target 512 * the target to validate 513 * @param asFormat 514 * the schema format to use to validate the target 515 * @param schemaProvider 516 * provides callbacks to get the appropriate schemas 517 * @param config 518 * the validation configuration 519 * @return the validation result 520 * @throws IOException 521 * if an error occurred while reading the target 522 */ 523 default IValidationResult validate( 524 @NonNull URI target, 525 @NonNull Format asFormat, 526 @NonNull ISchemaValidationProvider schemaProvider, 527 @Nullable IConfiguration<ValidationFeature<?>> config) throws IOException { 528 529 IValidationResult retval = schemaProvider.validateWithSchema(target, asFormat, this); 530 531 if (retval.isPassing()) { 532 IValidationResult constraintValidationResult = validateWithConstraints(target, config); 533 retval = AggregateValidationResult.aggregate(retval, constraintValidationResult); 534 } 535 return retval; 536 } 537 538 /** 539 * Load and validate the provided {@code target} using the associated Module 540 * module constraints. 541 * 542 * @param target 543 * the file to load and validate 544 * @param config 545 * the validation configuration 546 * @return the validation results 547 * @throws IOException 548 * if an error occurred while parsing the target 549 */ 550 default IValidationResult validateWithConstraints( 551 @NonNull URI target, 552 @Nullable IConfiguration<ValidationFeature<?>> config) 553 throws IOException { 554 IBoundLoader loader = newBoundLoader(); 555 loader.disableFeature(DeserializationFeature.DESERIALIZE_VALIDATE_CONSTRAINTS); 556 IDocumentNodeItem nodeItem = loader.loadAsNodeItem(target); 557 558 return validate(nodeItem, loader, config); 559 } 560 561 /** 562 * A behavioral class used by the binding context to load Metaschema modules. 563 * <p> 564 * A module will flow through the following process. 565 * <ol> 566 * <li><b>Loading:</b> The module is read from its source.</li> 567 * <li><b>Post Processing:</b> The module is prepared for use.</li> 568 * <li><b>Registration:</b> The module is registered for use.</li> 569 * </ol> 570 * <p> 571 * A module will be loaded when either the module or one of its global 572 * definitions is accessed the first time. 573 */ 574 interface IModuleLoaderStrategy extends ModuleLoadingPostProcessor { 575 /** 576 * Load the bound Metaschema module represented by the provided class. 577 * <p> 578 * This is the primary entry point for loading an already bound module. This 579 * method must ensure that the loaded module is post-processed and registered. 580 * <p> 581 * Implementations are allowed to return a cached instance if the module has 582 * already been loaded by this method. 583 * 584 * @param clazz 585 * the Module class 586 * @param bindingContext 587 * the Metaschema binding context used to load bound resources 588 * @return the module 589 * @throws IllegalStateException 590 * if an error occurred while processing the associated module 591 * information 592 * @since 2.0.0 593 */ 594 @NonNull 595 IBoundModule loadModule( 596 @NonNull Class<? extends IBoundModule> clazz, 597 @NonNull IBindingContext bindingContext); 598 599 /** 600 * Perform post-processing on the module. 601 * 602 * @param module 603 * the Metaschema module to post-process 604 * @param bindingContext 605 * the Metaschema binding context used to load bound resources 606 * @since 2.0.0 607 */ 608 @Override 609 default void postProcessModule( 610 @NonNull IModule module, 611 @NonNull IBindingContext bindingContext) { 612 // do nothing by default 613 } 614 615 /** 616 * Registers the provided Metaschema module. 617 * <p> 618 * If this module has not been post-processed, this method is expected to drive 619 * post-processing first. 620 * <p> 621 * If the provided instance is not an instance of {@link IBoundModule}, then 622 * annotated Java classes for this module will be generated, compiled, and 623 * loaded based on the provided Module. 624 * 625 * @param module 626 * the Module module to generate classes for 627 * @param bindingContext 628 * the Metaschema binding context used to load bound resources 629 * @return the registered module, which may be a different instance than what 630 * was provided if dynamic compilation was performed 631 * @throws UnsupportedOperationException 632 * if this binding context is not configured to support dynamic bound 633 * module loading and the module instance is not a subclass of 634 * {@link IBoundModule} 635 * @since 2.0.0 636 */ 637 @NonNull 638 IBoundModule registerModule( 639 @NonNull IModule module, 640 @NonNull IBindingContext bindingContext); 641 // 642 // /** 643 // * Register a matcher used to identify a bound class by the definition's root 644 // * name. 645 // * 646 // * @param definition 647 // * the definition to match for 648 // * @return the matcher 649 // */ 650 // @NonNull 651 // IBindingMatcher registerBindingMatcher(@NonNull IBoundDefinitionModelAssembly 652 // definition); 653 654 /** 655 * Get the matchers used to identify the bound class associated with the 656 * definition's root name. 657 * 658 * @return the matchers 659 */ 660 @NonNull 661 Collection<IBindingMatcher> getBindingMatchers(); 662 663 /** 664 * Get the {@link IBoundDefinitionModel} instance associated with the provided 665 * Java class. 666 * <p> 667 * Typically the class will have a {@link MetaschemaAssembly} or 668 * {@link MetaschemaField} annotation. 669 * 670 * @param clazz 671 * the class binding to load 672 * @param bindingContext 673 * the Metaschema binding context used to load bound resources 674 * @return the associated class binding instance 675 * @throws IllegalArgumentException 676 * if the class is not a bound definition with a 677 * {@link MetaschemaAssembly} or {@link MetaschemaField} annotation 678 */ 679 @NonNull 680 IBoundDefinitionModelComplex getBoundDefinitionForClass( 681 @NonNull Class<? extends IBoundObject> clazz, 682 @NonNull IBindingContext bindingContext); 683 } 684 685 /** 686 * Enables building a {@link IBindingContext} using common configuration options 687 * based on the builder pattern. 688 * 689 * @since 2.0.0 690 */ 691 final class BindingContextBuilder { 692 private Path compilePath; 693 private final List<IModuleLoader.IModulePostProcessor> postProcessors = new LinkedList<>(); 694 private final List<IConstraintSet> constraintSets = new LinkedList<>(); 695 @NonNull 696 private final Function<IBindingContext.IModuleLoaderStrategy, IBindingContext> initializer; 697 698 private BindingContextBuilder() { 699 this(DefaultBindingContext::new); 700 } 701 702 /** 703 * Construct a new builder. 704 * 705 * @param initializer 706 * the callback to use to get a new binding context instance 707 */ 708 public BindingContextBuilder( 709 @NonNull Function<IBindingContext.IModuleLoaderStrategy, IBindingContext> initializer) { 710 this.initializer = initializer; 711 } 712 713 /** 714 * Enable dynamic code generation and compilation for Metaschema module-based 715 * classes. 716 * 717 * @param path 718 * the path to use to generate and compile Metaschema module-based 719 * classes 720 * @return this builder 721 */ 722 @NonNull 723 public BindingContextBuilder compilePath(@NonNull Path path) { 724 compilePath = path; 725 return this; 726 } 727 728 /** 729 * Configure a Metaschema module post processor. 730 * 731 * @param processor 732 * the post processor to configure 733 * @return this builder 734 */ 735 @NonNull 736 public BindingContextBuilder postProcessor(@NonNull IModuleLoader.IModulePostProcessor processor) { 737 postProcessors.add(processor); 738 return this; 739 } 740 741 /** 742 * Configure a set of constraints targeting Metaschema modules. 743 * 744 * @param set 745 * the constraint set to configure 746 * @return this builder 747 */ 748 @NonNull 749 public BindingContextBuilder constraintSet(@NonNull IConstraintSet set) { 750 constraintSets.add(set); 751 return this; 752 } 753 754 /** 755 * Configure a collection of constraint sets targeting Metaschema modules. 756 * 757 * @param set 758 * the constraint sets to configure 759 * @return this builder 760 */ 761 @NonNull 762 public BindingContextBuilder constraintSet(@NonNull Collection<IConstraintSet> set) { 763 constraintSets.addAll(set); 764 return this; 765 } 766 767 /** 768 * Build a {@link IBindingContext} using the configuration options provided to 769 * the builder. 770 * 771 * @return a new, configured binding context 772 */ 773 @NonNull 774 public IBindingContext build() { 775 // get loader strategy based on if code generation is configured 776 IBindingContext.IModuleLoaderStrategy strategy = compilePath == null 777 ? new SimpleModuleLoaderStrategy() 778 : new SimpleModuleLoaderStrategy(new DefaultModuleBindingGenerator(compilePath)); 779 780 // determine if any post processors are configured or need to be 781 List<IModuleLoader.IModulePostProcessor> processors = new LinkedList<>(postProcessors); 782 if (!constraintSets.isEmpty()) { 783 processors.add(new ExternalConstraintsModulePostProcessor(constraintSets)); 784 } 785 786 if (!processors.isEmpty()) { 787 // post processors are configured, configure the loader strategy to handle them 788 strategy = new PostProcessingModuleLoaderStrategy( 789 CollectionUtil.unmodifiableList(processors), 790 strategy); 791 } 792 793 return ObjectUtils.notNull(initializer.apply(strategy)); 794 } 795 } 796 797 /** 798 * Provides schema validation capabilities. 799 */ 800 interface ISchemaValidationProvider { 801 802 /** 803 * Validate the target resource. 804 * 805 * @param target 806 * the resource to validate 807 * @param asFormat 808 * the format to validate the content as 809 * @param bindingContext 810 * the Metaschema binding context used to load bound resources 811 * @return the validation result 812 * @throws FileNotFoundException 813 * if the resource was not found 814 * @throws IOException 815 * if an error occurred while reading the resource 816 */ 817 @NonNull 818 default IValidationResult validateWithSchema( 819 @NonNull URI target, 820 @NonNull Format asFormat, 821 @NonNull IBindingContext bindingContext) 822 throws FileNotFoundException, IOException { 823 URL targetResource = ObjectUtils.notNull(target.toURL()); 824 825 IValidationResult retval; 826 switch (asFormat) { 827 case JSON: { 828 JSONObject json; 829 try (@SuppressWarnings("resource") 830 InputStream is 831 = new BufferedInputStream(ObjectUtils.notNull(targetResource.openStream()))) { 832 json = new JSONObject(new JSONTokener(is)); 833 } 834 retval = getJsonSchema(json, bindingContext).validate(json, target); 835 break; 836 } 837 case XML: 838 try { 839 retval = getXmlSchemas(targetResource, bindingContext).validate(target); 840 } catch (SAXException ex) { 841 throw new IOException(ex); 842 } 843 break; 844 case YAML: { 845 JSONObject json = YamlOperations.yamlToJson(YamlOperations.parseYaml(target)); 846 assert json != null; 847 retval = getJsonSchema(json, bindingContext).validate(json, ObjectUtils.notNull(target)); 848 break; 849 } 850 default: 851 throw new UnsupportedOperationException("Unsupported format: " + asFormat.name()); 852 } 853 return retval; 854 } 855 856 /** 857 * Get a JSON schema to use for content validation. 858 * 859 * @param json 860 * the JSON content to validate 861 * @param bindingContext 862 * the Metaschema binding context used to load bound resources 863 * @return the JSON schema validator 864 * @throws IOException 865 * if an error occurred while loading the schema 866 * @since 2.0.0 867 */ 868 @NonNull 869 JsonSchemaContentValidator getJsonSchema(@NonNull JSONObject json, @NonNull IBindingContext bindingContext) 870 throws IOException; 871 872 /** 873 * Get a XML schema to use for content validation. 874 * 875 * @param targetResource 876 * the URL for the XML content to validate 877 * @param bindingContext 878 * the Metaschema binding context used to load bound resources 879 * @return the XML schema validator 880 * @throws IOException 881 * if an error occurred while loading the schema 882 * @throws SAXException 883 * if an error occurred while parsing the schema 884 * @since 2.0.0 885 */ 886 @NonNull 887 XmlSchemaContentValidator getXmlSchemas(@NonNull URL targetResource, @NonNull IBindingContext bindingContext) 888 throws IOException, SAXException; 889 } 890 891 /** 892 * Implementations of this interface provide a means by which a bound class can 893 * be found that corresponds to an XML element, JSON property, or YAML item 894 * name. 895 */ 896 interface IBindingMatcher { 897 /** 898 * Construct a new binding matcher for the provided assembly definition. 899 * 900 * @param assembly 901 * the assembly definition that matcher is for 902 * @return the matcher 903 */ 904 @SuppressWarnings("PMD.ShortMethodName") 905 @NonNull 906 static IBindingMatcher of(IBoundDefinitionModelAssembly assembly) { 907 if (!assembly.isRoot()) { 908 throw new IllegalArgumentException( 909 String.format("The provided class '%s' is not a root assembly.", assembly.getBoundClass().getName())); 910 } 911 return new RootAssemblyBindingMatcher(assembly); 912 } 913 914 /** 915 * Determine the bound class for the provided XML {@link QName}. 916 * 917 * @param rootQName 918 * the root XML element's QName 919 * @return the bound class for the XML qualified name or {@code null} if not 920 * recognized 921 */ 922 Class<? extends IBoundObject> getBoundClassForXmlQName(QName rootQName); 923 924 /** 925 * Determine the bound class for the provided JSON/YAML property/item name. 926 * 927 * @param rootName 928 * the JSON/YAML property/item name 929 * @return the bound class for the JSON property name or {@code null} if not 930 * recognized 931 */ 932 Class<? extends IBoundObject> getBoundClassForJsonName(String rootName); 933 } 934 }