001/* 002 * Portions of this software was developed by employees of the National Institute 003 * of Standards and Technology (NIST), an agency of the Federal Government and is 004 * being made available as a public service. Pursuant to title 17 United States 005 * Code Section 105, works of NIST employees are not subject to copyright 006 * protection in the United States. This software may be subject to foreign 007 * copyright. Permission in the United States and in foreign countries, to the 008 * extent that NIST may hold copyright, to use, copy, modify, create derivative 009 * works, and distribute this software and its documentation without fee is hereby 010 * granted on a non-exclusive basis, provided that this notice and disclaimer 011 * of warranty appears in all copies. 012 * 013 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER 014 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY 015 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF 016 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM 017 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE 018 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE. IN NO EVENT 019 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT, 020 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM, 021 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY, 022 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR 023 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT 024 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER. 025 */ 026 027package gov.nist.secauto.metaschema.databind; 028 029import gov.nist.secauto.metaschema.core.datatype.IDataTypeAdapter; 030import gov.nist.secauto.metaschema.core.metapath.DynamicContext; 031import gov.nist.secauto.metaschema.core.metapath.StaticContext; 032import gov.nist.secauto.metaschema.core.metapath.item.node.IDocumentNodeItem; 033import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem; 034import gov.nist.secauto.metaschema.core.model.IAssemblyDefinition; 035import gov.nist.secauto.metaschema.core.model.IFlagContainer; 036import gov.nist.secauto.metaschema.core.model.IModule; 037import gov.nist.secauto.metaschema.core.model.constraint.DefaultConstraintValidator; 038import gov.nist.secauto.metaschema.core.model.constraint.FindingCollectingConstraintValidationHandler; 039import gov.nist.secauto.metaschema.core.model.constraint.IConstraintValidationHandler; 040import gov.nist.secauto.metaschema.core.model.constraint.IConstraintValidator; 041import gov.nist.secauto.metaschema.core.model.validation.AggregateValidationResult; 042import gov.nist.secauto.metaschema.core.model.validation.IValidationResult; 043import gov.nist.secauto.metaschema.core.model.validation.JsonSchemaContentValidator; 044import gov.nist.secauto.metaschema.core.model.validation.XmlSchemaContentValidator; 045import gov.nist.secauto.metaschema.core.util.ObjectUtils; 046import gov.nist.secauto.metaschema.databind.codegen.ModuleCompilerHelper; 047import gov.nist.secauto.metaschema.databind.io.BindingException; 048import gov.nist.secauto.metaschema.databind.io.DeserializationFeature; 049import gov.nist.secauto.metaschema.databind.io.Format; 050import gov.nist.secauto.metaschema.databind.io.IBoundLoader; 051import gov.nist.secauto.metaschema.databind.io.IDeserializer; 052import gov.nist.secauto.metaschema.databind.io.ISerializer; 053import gov.nist.secauto.metaschema.databind.io.yaml.YamlOperations; 054import gov.nist.secauto.metaschema.databind.model.IAssemblyClassBinding; 055import gov.nist.secauto.metaschema.databind.model.IClassBinding; 056import gov.nist.secauto.metaschema.databind.model.annotations.MetaschemaAssembly; 057import gov.nist.secauto.metaschema.databind.model.annotations.MetaschemaField; 058 059import org.json.JSONObject; 060import org.xml.sax.SAXException; 061 062import java.io.IOException; 063import java.math.BigInteger; 064import java.nio.file.Files; 065import java.nio.file.Path; 066import java.time.ZonedDateTime; 067import java.util.List; 068 069import javax.xml.namespace.QName; 070import javax.xml.transform.Source; 071 072import edu.umd.cs.findbugs.annotations.NonNull; 073import edu.umd.cs.findbugs.annotations.Nullable; 074 075/** 076 * Provides information supporting a binding between a set of Module models and 077 * corresponding Java classes. 078 */ 079public interface IBindingContext extends IModuleLoaderStrategy { 080 081 /** 082 * Get the singleton {@link IBindingContext} instance, which can be used to load 083 * information that binds a model to a set of Java classes. 084 * 085 * @return a new binding context 086 */ 087 @NonNull 088 static IBindingContext instance() { 089 return DefaultBindingContext.instance(); 090 } 091 092 /** 093 * Register a matcher used to identify a bound class by the content's root name. 094 * 095 * @param matcher 096 * the matcher implementation 097 * @return this instance 098 */ 099 @NonNull 100 IBindingContext registerBindingMatcher(@NonNull IBindingMatcher matcher); 101 102 /** 103 * Determine the bound class for the provided XML {@link QName}. 104 * 105 * @param rootQName 106 * the root XML element's QName 107 * @return the bound class or {@code null} if not recognized 108 * @see IBindingContext#registerBindingMatcher(IBindingMatcher) 109 */ 110 @Nullable 111 Class<?> getBoundClassForXmlQName(@NonNull QName rootQName); 112 113 /** 114 * Determine the bound class for the provided JSON/YAML property/item name using 115 * any registered matchers. 116 * 117 * @param rootName 118 * the JSON/YAML property/item name 119 * @return the bound class or {@code null} if not recognized 120 * @see IBindingContext#registerBindingMatcher(IBindingMatcher) 121 */ 122 @Nullable 123 Class<?> getBoundClassForJsonName(@NonNull String rootName); 124 125 /** 126 * Get's the {@link IDataTypeAdapter} associated with the specified Java class, 127 * which is used to read and write XML, JSON, and YAML data to and from 128 * instances of that class. Thus, this adapter supports a direct binding between 129 * the Java class and structured data in one of the supported formats. Adapters 130 * are used to support bindings for simple data objects (e.g., {@link String}, 131 * {@link BigInteger}, {@link ZonedDateTime}, etc). 132 * 133 * @param <TYPE> 134 * the class type of the adapter 135 * @param clazz 136 * the Java {@link Class} for the bound type 137 * @return the adapter instance or {@code null} if the provided class is not 138 * bound 139 */ 140 @Nullable 141 <TYPE extends IDataTypeAdapter<?>> TYPE getJavaTypeAdapterInstance(@NonNull Class<TYPE> clazz); 142 143 /** 144 * Generate, compile, and load a set of generated Module annotated Java classes 145 * based on the provided Module {@code module}. 146 * 147 * @param module 148 * the Module module to generate classes for 149 * @param compilePath 150 * the path to the directory to generate classes in 151 * @return this instance 152 * @throws IOException 153 * if an error occurred while generating or loading the classes 154 */ 155 @SuppressWarnings("PMD.UseProperClassLoader") // false positive 156 @NonNull 157 default IBindingContext registerModule( 158 @NonNull IModule module, 159 @NonNull Path compilePath) throws IOException { 160 Files.createDirectories(compilePath); 161 162 ClassLoader classLoader = ModuleCompilerHelper.newClassLoader( 163 compilePath, 164 ObjectUtils.notNull(Thread.currentThread().getContextClassLoader())); 165 166 ModuleCompilerHelper.compileMetaschema(module, compilePath).getGlobalDefinitionClassesAsStream() 167 .filter(definitionInfo -> { 168 boolean retval = false; 169 IFlagContainer definition = definitionInfo.getDefinition(); 170 if (definition instanceof IAssemblyDefinition) { 171 IAssemblyDefinition assembly = (IAssemblyDefinition) definition; 172 if (assembly.isRoot()) { 173 retval = true; 174 } 175 } 176 return retval; 177 }) 178 .map( 179 generatedClass -> { 180 try { 181 @SuppressWarnings("unchecked") Class<IAssemblyClassBinding> clazz 182 = ObjectUtils.notNull((Class<IAssemblyClassBinding>) classLoader 183 .loadClass(generatedClass.getClassName().reflectionName())); 184 185 IAssemblyDefinition definition = (IAssemblyDefinition) generatedClass.getDefinition(); 186 return new DynamicBindingMatcher( 187 definition, 188 clazz); 189 } catch (ClassNotFoundException ex) { 190 throw new IllegalStateException(ex); 191 } 192 }) 193 .forEachOrdered( 194 matcher -> registerBindingMatcher( 195 ObjectUtils.notNull( 196 matcher))); 197 return this; 198 } 199 200 /** 201 * Gets a data {@link ISerializer} which can be used to write Java instance data 202 * for the provided class to the requested format. The provided class must be a 203 * bound Java class with a {@link MetaschemaAssembly} or {@link MetaschemaField} 204 * annotation for which a {@link IClassBinding} exists. 205 * 206 * @param <CLASS> 207 * the Java type this deserializer can write data from 208 * @param format 209 * the format to serialize into 210 * @param clazz 211 * the Java data type to serialize 212 * @return the serializer instance 213 * @throws NullPointerException 214 * if any of the provided arguments, except the configuration, are 215 * {@code null} 216 * @throws IllegalArgumentException 217 * if the provided class is not bound to a Module assembly or field 218 * @throws UnsupportedOperationException 219 * if the requested format is not supported by the implementation 220 * @see #getClassBinding(Class) 221 */ 222 @NonNull 223 <CLASS> ISerializer<CLASS> newSerializer(@NonNull Format format, @NonNull Class<CLASS> clazz); 224 225 /** 226 * Gets a data {@link IDeserializer} which can be used to read Java instance 227 * data for the provided class from the requested format. The provided class 228 * must be a bound Java class with a {@link MetaschemaAssembly} or 229 * {@link MetaschemaField} annotation for which a {@link IClassBinding} exists. 230 * 231 * @param <CLASS> 232 * the Java type this deserializer can read data into 233 * @param format 234 * the format to serialize into 235 * @param clazz 236 * the Java data type to serialize 237 * @return the deserializer instance 238 * @throws NullPointerException 239 * if any of the provided arguments, except the configuration, are 240 * {@code null} 241 * @throws IllegalArgumentException 242 * if the provided class is not bound to a Module assembly or field 243 * @throws UnsupportedOperationException 244 * if the requested format is not supported by the implementation 245 * @see #getClassBinding(Class) 246 */ 247 @NonNull 248 <CLASS> IDeserializer<CLASS> newDeserializer(@NonNull Format format, @NonNull Class<CLASS> clazz); 249 250 /** 251 * Get a new {@link IBoundLoader} instance. 252 * 253 * @return the instance 254 */ 255 @NonNull 256 IBoundLoader newBoundLoader(); 257 258 /** 259 * Create a deep copy of the provided bound object. 260 * 261 * @param <CLASS> 262 * the bound object type 263 * @param other 264 * the object to copy 265 * @param parentInstance 266 * the object's parent or {@code null} 267 * @return a deep copy of the provided object 268 * @throws BindingException 269 * if an error occurred copying content between java instances 270 * @throws NullPointerException 271 * if the provided object is {@code null} 272 * @throws IllegalArgumentException 273 * if the provided class is not bound to a Module assembly or field 274 */ 275 @NonNull 276 <CLASS> CLASS copyBoundObject(@NonNull CLASS other, Object parentInstance) throws BindingException; 277 278 /** 279 * Get a new single use constraint validator. 280 * 281 * @param handler 282 * the validation handler to use to process the validation results 283 * 284 * @return the validator 285 */ 286 default IConstraintValidator newValidator(@NonNull IConstraintValidationHandler handler) { 287 IBoundLoader loader = newBoundLoader(); 288 loader.disableFeature(DeserializationFeature.DESERIALIZE_VALIDATE_CONSTRAINTS); 289 290 DynamicContext context = StaticContext.newInstance().newDynamicContext(); 291 context.setDocumentLoader(loader); 292 293 return new DefaultConstraintValidator(context, handler); 294 } 295 296 /** 297 * Perform constraint validation on the provided bound object represented as an 298 * {@link INodeItem}. 299 * 300 * @param nodeItem 301 * the node item to validate 302 * @return the validation result 303 * @throws IllegalArgumentException 304 * if the provided class is not bound to a Module assembly or field 305 */ 306 default IValidationResult validate(@NonNull INodeItem nodeItem) { 307 FindingCollectingConstraintValidationHandler handler = new FindingCollectingConstraintValidationHandler(); 308 IConstraintValidator validator = newValidator(handler); 309 validator.validate(nodeItem); 310 validator.finalizeValidation(); 311 return handler; 312 } 313 314 /** 315 * Load and perform schema and constraint validation on the target. The 316 * constraint validation will only be performed if the schema validation passes. 317 * 318 * @param target 319 * the target to validate 320 * @param asFormat 321 * the schema format to use to validate the target 322 * @param schemaProvider 323 * provides callbacks to get the appropriate schemas 324 * @return the validation result 325 * @throws IOException 326 * if an error occurred while reading the target 327 * @throws SAXException 328 * if an error occurred when parsing the target as XML 329 */ 330 default IValidationResult validate( 331 @NonNull Path target, 332 @NonNull Format asFormat, 333 @NonNull IValidationSchemaProvider schemaProvider) throws IOException, SAXException { 334 IValidationResult retval; 335 switch (asFormat) { 336 case JSON: 337 retval = new JsonSchemaContentValidator(schemaProvider.getJsonSchema()).validate(target); 338 break; 339 case XML: 340 List<Source> schemaSources = schemaProvider.getXmlSchemas(); 341 retval = new XmlSchemaContentValidator(schemaSources).validate(target); 342 break; 343 case YAML: 344 JSONObject json = YamlOperations.yamlToJson(YamlOperations.parseYaml(target)); 345 assert json != null; 346 retval = new JsonSchemaContentValidator(schemaProvider.getJsonSchema()) 347 .validate(json, ObjectUtils.notNull(target.toUri())); 348 break; 349 default: 350 throw new UnsupportedOperationException("Unsupported format: " + asFormat.name()); 351 } 352 353 if (retval.isPassing()) { 354 IValidationResult constraintValidationResult = validateWithConstraints(target); 355 retval = AggregateValidationResult.aggregate(retval, constraintValidationResult); 356 } 357 return retval; 358 } 359 360 /** 361 * Load and validate the provided {@code target} using the associated Module 362 * module constraints. 363 * 364 * @param target 365 * the file to load and validate 366 * @return the validation results 367 * @throws IOException 368 * if an error occurred while loading the document 369 */ 370 default IValidationResult validateWithConstraints(@NonNull Path target) throws IOException { 371 IBoundLoader loader = newBoundLoader(); 372 loader.disableFeature(DeserializationFeature.DESERIALIZE_VALIDATE_CONSTRAINTS); 373 374 DynamicContext dynamicContext = StaticContext.newInstance().newDynamicContext(); 375 dynamicContext.setDocumentLoader(loader); 376 IDocumentNodeItem nodeItem = loader.loadAsNodeItem(target); 377 378 return validate(nodeItem); 379 } 380 381 interface IValidationSchemaProvider { 382 /** 383 * Get a JSON schema to use for content validation. 384 * 385 * @return the JSON schema 386 * @throws IOException 387 * if an error occurred while loading the schema 388 */ 389 @NonNull 390 JSONObject getJsonSchema() throws IOException; 391 392 /** 393 * Get a XML schema to use for content validation. 394 * 395 * @return the XML schema sources 396 * @throws IOException 397 * if an error occurred while loading the schema 398 */ 399 @NonNull 400 List<Source> getXmlSchemas() throws IOException; 401 } 402 403}