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