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