View Javadoc
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.schemagen;
28  
29  import static org.junit.jupiter.api.Assertions.assertEquals;
30  
31  import gov.nist.secauto.metaschema.core.configuration.DefaultConfiguration;
32  import gov.nist.secauto.metaschema.core.configuration.IConfiguration;
33  import gov.nist.secauto.metaschema.core.configuration.IMutableConfiguration;
34  import gov.nist.secauto.metaschema.core.model.IModule;
35  import gov.nist.secauto.metaschema.core.model.MetaschemaException;
36  import gov.nist.secauto.metaschema.core.model.validation.JsonSchemaContentValidator;
37  import gov.nist.secauto.metaschema.core.model.validation.XmlSchemaContentValidator;
38  import gov.nist.secauto.metaschema.core.model.xml.ModuleLoader;
39  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
40  import gov.nist.secauto.metaschema.databind.IBindingContext;
41  import gov.nist.secauto.metaschema.databind.io.Format;
42  import gov.nist.secauto.metaschema.model.testing.AbstractTestSuite;
43  import gov.nist.secauto.metaschema.schemagen.json.JsonSchemaGenerator;
44  import gov.nist.secauto.metaschema.schemagen.xml.XmlSchemaGenerator;
45  
46  import org.apache.logging.log4j.LogManager;
47  import org.apache.logging.log4j.Logger;
48  import org.junit.platform.commons.JUnitException;
49  import org.xml.sax.SAXException;
50  
51  import java.io.IOException;
52  import java.io.InputStream;
53  import java.io.Writer;
54  import java.net.URI;
55  import java.net.URL;
56  import java.nio.file.Files;
57  import java.nio.file.Path;
58  import java.nio.file.Paths;
59  import java.nio.file.StandardOpenOption;
60  import java.util.Collections;
61  import java.util.List;
62  import java.util.function.BiFunction;
63  import java.util.function.Function;
64  
65  import javax.xml.transform.Source;
66  import javax.xml.transform.stream.StreamSource;
67  
68  import edu.umd.cs.findbugs.annotations.NonNull;
69  
70  public abstract class AbstractSchemaGeneratorTestSuite
71      extends AbstractTestSuite {
72    private static final Logger LOGGER = LogManager.getLogger(AbstractTestSuite.class);
73    @NonNull
74    protected static final ISchemaGenerator XML_SCHEMA_GENERATOR = new XmlSchemaGenerator();
75    @NonNull
76    protected static final ISchemaGenerator JSON_SCHEMA_GENERATOR = new JsonSchemaGenerator();
77    @NonNull
78    protected static final IConfiguration<SchemaGenerationFeature<?>> SCHEMA_GENERATION_CONFIG;
79    @NonNull
80    protected static final BiFunction<IModule, Writer, Void> XML_SCHEMA_PROVIDER;
81    @NonNull
82    protected static final BiFunction<IModule, Writer, Void> JSON_SCHEMA_PROVIDER;
83    @NonNull
84    protected static final JsonSchemaContentValidator JSON_SCHEMA_VALIDATOR;
85    @NonNull
86    protected static final Function<Path, JsonSchemaContentValidator> JSON_CONTENT_VALIDATOR_PROVIDER;
87    @NonNull
88    protected static final Function<Path, XmlSchemaContentValidator> XML_CONTENT_VALIDATOR_PROVIDER;
89  
90    private static final String UNIT_TEST_CONFIG
91        = "../core/metaschema/test-suite/schema-generation/unit-tests.xml";
92  
93    static {
94      IMutableConfiguration<SchemaGenerationFeature<?>> features = new DefaultConfiguration<>();
95      features.enableFeature(SchemaGenerationFeature.INLINE_DEFINITIONS);
96      SCHEMA_GENERATION_CONFIG = features;
97  
98      BiFunction<IModule, Writer, Void> xmlProvider = (module, writer) -> {
99        assert module != null;
100       assert writer != null;
101       try {
102         XML_SCHEMA_GENERATOR.generateFromModule(module, writer, SCHEMA_GENERATION_CONFIG);
103       } catch (SchemaGenerationException ex) {
104         throw new JUnitException("IO error", ex);
105       }
106       return null;
107     };
108     XML_SCHEMA_PROVIDER = xmlProvider;
109 
110     BiFunction<IModule, Writer, Void> jsonProvider = (module, writer) -> {
111       assert module != null;
112       assert writer != null;
113       try {
114         JSON_SCHEMA_GENERATOR.generateFromModule(module, writer, SCHEMA_GENERATION_CONFIG);
115       } catch (SchemaGenerationException ex) {
116         throw new JUnitException("IO error", ex);
117       }
118       return null;
119     };
120     JSON_SCHEMA_PROVIDER = jsonProvider;
121 
122     try (InputStream is = ModuleLoader.class.getResourceAsStream("/schema/json/json-schema.json")) {
123       assert is != null : "unable to get JSON schema resource";
124       JsonSchemaContentValidator schemaValidator = new JsonSchemaContentValidator(is);
125       JSON_SCHEMA_VALIDATOR = schemaValidator;
126     } catch (IOException ex) {
127       throw new IllegalStateException(ex);
128     }
129 
130     @SuppressWarnings("null")
131     @NonNull Function<Path, XmlSchemaContentValidator> xmlContentValidatorProvider = (path) -> {
132       try {
133         URL schemaResource = path.toUri().toURL();
134         @SuppressWarnings("resource") StreamSource source
135             = new StreamSource(schemaResource.openStream(), schemaResource.toString());
136         List<? extends Source> schemaSources = Collections.singletonList(source);
137         return new XmlSchemaContentValidator(schemaSources);
138       } catch (IOException | SAXException ex) {
139         throw new IllegalStateException(ex);
140       }
141     };
142     XML_CONTENT_VALIDATOR_PROVIDER = xmlContentValidatorProvider;
143 
144     @NonNull Function<Path, JsonSchemaContentValidator> jsonContentValidatorProvider = (path) -> {
145       try (InputStream is = Files.newInputStream(path, StandardOpenOption.READ)) {
146         assert is != null;
147         return new JsonSchemaContentValidator(is);
148       } catch (IOException ex) {
149         throw new JUnitException("Failed to create content validator for schema: " + path.toString(), ex);
150       }
151     };
152     JSON_CONTENT_VALIDATOR_PROVIDER = jsonContentValidatorProvider;
153   }
154 
155   @Override
156   protected URI getTestSuiteURI() {
157     return ObjectUtils
158         .notNull(Paths.get(UNIT_TEST_CONFIG).toUri());
159   }
160 
161   @Override
162   protected Path getGenerationPath() {
163     return ObjectUtils.notNull(Paths.get("target/test-schemagen"));
164   }
165 
166   protected Path produceXmlSchema(@NonNull IModule module, @NonNull Path schemaPath) throws IOException {
167     produceSchema(module, schemaPath, XML_SCHEMA_PROVIDER);
168     return schemaPath;
169   }
170 
171   protected Path produceJsonSchema(@NonNull IModule module, @NonNull Path schemaPath) throws IOException {
172     produceSchema(module, schemaPath, JSON_SCHEMA_PROVIDER);
173     return schemaPath;
174   }
175 
176   @SuppressWarnings("null")
177   protected void doTest(
178       @NonNull String collectionName,
179       @NonNull String metaschemaName,
180       @NonNull String generatedSchemaName,
181       @NonNull ContentCase... contentCases) throws IOException, MetaschemaException {
182     Path generationDir = getGenerationPath();
183 
184     Path testSuite = Paths.get("../core/metaschema/test-suite/schema-generation/");
185     Path collectionPath = testSuite.resolve(collectionName);
186 
187     ModuleLoader loader = new ModuleLoader();
188     loader.allowEntityResolution();
189     Path modulePath = collectionPath.resolve(metaschemaName);
190     IModule module = loader.load(modulePath);
191 
192     Path jsonSchema = produceJsonSchema(module, generationDir.resolve(generatedSchemaName + ".json"));
193     assertEquals(true, validate(JSON_SCHEMA_VALIDATOR, jsonSchema),
194         String.format("JSON schema '%s' was invalid", jsonSchema.toString()));
195     Path xmlSchema = produceXmlSchema(module, generationDir.resolve(generatedSchemaName + ".xsd"));
196 
197     Path schemaPath;
198     switch (getRequiredContentFormat()) {
199     case JSON:
200     case YAML:
201       schemaPath = jsonSchema;
202       break;
203     case XML:
204       schemaPath = xmlSchema;
205       break;
206     default:
207       throw new IllegalStateException();
208     }
209 
210     IBindingContext context = IBindingContext.instance();
211     context.registerModule(module, generationDir);
212     for (ContentCase contentCase : contentCases) {
213       Path contentPath = collectionPath.resolve(contentCase.getName());
214 
215       if (!getRequiredContentFormat().equals(contentCase.getActualFormat())) {
216         contentPath = convertContent(contentPath.toUri(), generationDir, context);
217       }
218 
219       assertEquals(contentCase.isValid(),
220           validate(getContentValidatorSupplier().apply(schemaPath), contentPath),
221           String.format("validation of '%s' did not match expectation", contentPath));
222     }
223   }
224 
225   @NonNull
226   protected ContentCase contentCase(
227       @NonNull Format actualFormat,
228       @NonNull String contentName,
229       boolean valid) {
230     return new ContentCase(contentName, actualFormat, valid);
231   }
232 
233   protected static class ContentCase {
234     @NonNull
235     private final String name;
236     @NonNull
237     private final Format actualFormat;
238     private final boolean valid;
239 
240     public ContentCase(@NonNull String name, @NonNull Format actualFormat, boolean valid) {
241       this.name = name;
242       this.actualFormat = actualFormat;
243       this.valid = valid;
244     }
245 
246     @NonNull
247     public String getName() {
248       return name;
249     }
250 
251     @NonNull
252     public Format getActualFormat() {
253       return actualFormat;
254     }
255 
256     public boolean isValid() {
257       return valid;
258     }
259   }
260 }