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