1 /*
2 * Portions of this software was developed by employees of the National Institute
3 * of Standards and Technology (NIST), an agency of the Federal Government and is
4 * being made available as a public service. Pursuant to title 17 United States
5 * Code Section 105, works of NIST employees are not subject to copyright
6 * protection in the United States. This software may be subject to foreign
7 * copyright. Permission in the United States and in foreign countries, to the
8 * extent that NIST may hold copyright, to use, copy, modify, create derivative
9 * works, and distribute this software and its documentation without fee is hereby
10 * granted on a non-exclusive basis, provided that this notice and disclaimer
11 * of warranty appears in all copies.
12 *
13 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
14 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
15 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
16 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
17 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
18 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE. IN NO EVENT
19 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
20 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
21 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
22 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
23 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
24 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
25 */
26
27 package gov.nist.secauto.metaschema.databind;
28
29 import gov.nist.secauto.metaschema.core.datatype.IDataTypeAdapter;
30 import gov.nist.secauto.metaschema.core.metapath.DynamicContext;
31 import gov.nist.secauto.metaschema.core.metapath.StaticContext;
32 import gov.nist.secauto.metaschema.core.metapath.item.node.IDocumentNodeItem;
33 import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem;
34 import gov.nist.secauto.metaschema.core.model.IAssemblyDefinition;
35 import gov.nist.secauto.metaschema.core.model.IFlagContainer;
36 import gov.nist.secauto.metaschema.core.model.IModule;
37 import gov.nist.secauto.metaschema.core.model.constraint.DefaultConstraintValidator;
38 import gov.nist.secauto.metaschema.core.model.constraint.FindingCollectingConstraintValidationHandler;
39 import gov.nist.secauto.metaschema.core.model.constraint.IConstraintValidationHandler;
40 import gov.nist.secauto.metaschema.core.model.constraint.IConstraintValidator;
41 import gov.nist.secauto.metaschema.core.model.validation.AggregateValidationResult;
42 import gov.nist.secauto.metaschema.core.model.validation.IValidationResult;
43 import gov.nist.secauto.metaschema.core.model.validation.JsonSchemaContentValidator;
44 import gov.nist.secauto.metaschema.core.model.validation.XmlSchemaContentValidator;
45 import gov.nist.secauto.metaschema.core.util.ObjectUtils;
46 import gov.nist.secauto.metaschema.databind.codegen.ModuleCompilerHelper;
47 import gov.nist.secauto.metaschema.databind.io.BindingException;
48 import gov.nist.secauto.metaschema.databind.io.DeserializationFeature;
49 import gov.nist.secauto.metaschema.databind.io.Format;
50 import gov.nist.secauto.metaschema.databind.io.IBoundLoader;
51 import gov.nist.secauto.metaschema.databind.io.IDeserializer;
52 import gov.nist.secauto.metaschema.databind.io.ISerializer;
53 import gov.nist.secauto.metaschema.databind.io.yaml.YamlOperations;
54 import gov.nist.secauto.metaschema.databind.model.IAssemblyClassBinding;
55 import gov.nist.secauto.metaschema.databind.model.IClassBinding;
56 import gov.nist.secauto.metaschema.databind.model.annotations.MetaschemaAssembly;
57 import gov.nist.secauto.metaschema.databind.model.annotations.MetaschemaField;
58
59 import org.json.JSONObject;
60 import org.xml.sax.SAXException;
61
62 import java.io.IOException;
63 import java.math.BigInteger;
64 import java.nio.file.Files;
65 import java.nio.file.Path;
66 import java.time.ZonedDateTime;
67 import java.util.List;
68
69 import javax.xml.namespace.QName;
70 import javax.xml.transform.Source;
71
72 import edu.umd.cs.findbugs.annotations.NonNull;
73 import edu.umd.cs.findbugs.annotations.Nullable;
74
75 /**
76 * Provides information supporting a binding between a set of Module models and
77 * corresponding Java classes.
78 */
79 public interface IBindingContext extends IModuleLoaderStrategy {
80
81 /**
82 * Get the singleton {@link IBindingContext} instance, which can be used to load
83 * information that binds a model to a set of Java classes.
84 *
85 * @return a new binding context
86 */
87 @NonNull
88 static IBindingContext instance() {
89 return DefaultBindingContext.instance();
90 }
91
92 /**
93 * Register a matcher used to identify a bound class by the content's root name.
94 *
95 * @param matcher
96 * the matcher implementation
97 * @return this instance
98 */
99 @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 }