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
30 import java.io.IOException;
31 import java.io.InputStream;
32 import java.io.Writer;
33 import java.net.URI;
34 import java.net.URL;
35 import java.nio.file.Files;
36 import java.nio.file.Path;
37 import java.nio.file.Paths;
38 import java.nio.file.StandardOpenOption;
39 import java.util.Collection;
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
117 Function<Path, XmlSchemaContentValidator> xmlContentValidatorProvider = path -> {
118 try {
119 URL schemaResource = path.toUri().toURL();
120 @SuppressWarnings("resource")
121 StreamSource source
122 = new StreamSource(schemaResource.openStream(), schemaResource.toString());
123 List<? extends Source> schemaSources = Collections.singletonList(source);
124 return new XmlSchemaContentValidator(schemaSources);
125 } catch (IOException ex) {
126 throw new IllegalStateException(ex);
127 }
128 };
129 XML_CONTENT_VALIDATOR_PROVIDER = xmlContentValidatorProvider;
130
131 @NonNull
132 Function<Path, JsonSchemaContentValidator> jsonContentValidatorProvider = path -> {
133 try (InputStream is = Files.newInputStream(path, StandardOpenOption.READ)) {
134 assert is != null;
135 return new JsonSchemaContentValidator(is);
136 } catch (IOException ex) {
137 throw new JUnitException("Failed to create content validator for schema: " + path.toString(), ex);
138 }
139 };
140 JSON_CONTENT_VALIDATOR_PROVIDER = jsonContentValidatorProvider;
141 }
142
143 @NonNull
144 protected static IBindingContext newBindingContext() throws IOException {
145 return newBindingContext(CollectionUtil.emptyList());
146 }
147
148 @NonNull
149 protected static IBindingContext newBindingContext(@NonNull Collection<IConstraintSet> constraints)
150 throws IOException {
151 Path generationDir = Paths.get("target/generated-modules");
152 Files.createDirectories(generationDir);
153
154 return IBindingContext.builder()
155 .compilePath(ObjectUtils.notNull(Files.createTempDirectory(generationDir, "modules-")))
156 .constraintSet(constraints)
157 .build();
158 }
159
160 @Override
161 protected URI getTestSuiteURI() {
162 return ObjectUtils
163 .notNull(Paths.get(UNIT_TEST_CONFIG).toUri());
164 }
165
166 @Override
167 protected Path getGenerationPath() {
168 return ObjectUtils.notNull(Paths.get("target/test-schemagen"));
169 }
170
171 protected Path produceXmlSchema(@NonNull IModule module, @NonNull Path schemaPath) throws IOException {
172 generateSchema(module, schemaPath, XML_SCHEMA_PROVIDER);
173 return schemaPath;
174 }
175
176 protected Path produceJsonSchema(@NonNull IModule module, @NonNull Path schemaPath)
177 throws IOException {
178 generateSchema(module, schemaPath, JSON_SCHEMA_PROVIDER);
179 return schemaPath;
180 }
181
182 @SuppressWarnings("null")
183 protected void doTest(
184 @NonNull String collectionName,
185 @NonNull String metaschemaName,
186 @NonNull String generatedSchemaName,
187 @NonNull ContentCase... contentCases) throws IOException, MetaschemaException {
188 Path generationDir = getGenerationPath();
189
190 Path testSuite = Paths.get("../core/metaschema/test-suite/schema-generation/");
191 Path collectionPath = testSuite.resolve(collectionName);
192
193 IBindingContext bindingContext = newBindingContext();
194
195
196 IBindingModuleLoader loader = bindingContext.newModuleLoader();
197 loader.allowEntityResolution();
198 Path modulePath = collectionPath.resolve(metaschemaName);
199 IModule module = loader.load(modulePath);
200
201
202 Path schemaPath;
203 Format requiredContentFormat = getRequiredContentFormat();
204 switch (requiredContentFormat) {
205 case JSON:
206 case YAML:
207 Path jsonSchema = produceJsonSchema(module, generationDir.resolve(generatedSchemaName + ".json"));
208 assertEquals(true, validateWithSchema(JSON_SCHEMA_VALIDATOR, jsonSchema),
209 String.format("JSON schema '%s' was invalid", jsonSchema.toString()));
210 schemaPath = jsonSchema;
211 break;
212 case XML:
213 schemaPath = produceXmlSchema(module, generationDir.resolve(generatedSchemaName + ".xsd"));
214 break;
215 default:
216 throw new IllegalStateException();
217 }
218
219
220 for (ContentCase contentCase : contentCases) {
221 Path contentPath = collectionPath.resolve(contentCase.getName());
222
223 if (!requiredContentFormat.equals(contentCase.getActualFormat())) {
224 contentPath = convertContent(contentPath.toUri(), generationDir, bindingContext);
225 }
226
227 assertEquals(contentCase.isValid(),
228 validateWithSchema(getContentValidatorSupplier().apply(schemaPath), contentPath),
229 String.format("validation of '%s' did not match expectation", contentPath));
230 }
231 }
232
233 @NonNull
234 protected ContentCase contentCase(
235 @NonNull Format actualFormat,
236 @NonNull String contentName,
237 boolean valid) {
238 return new ContentCase(contentName, actualFormat, valid);
239 }
240
241 protected static class ContentCase {
242 @NonNull
243 private final String name;
244 @NonNull
245 private final Format actualFormat;
246 private final boolean valid;
247
248 public ContentCase(@NonNull String name, @NonNull Format actualFormat, boolean valid) {
249 this.name = name;
250 this.actualFormat = actualFormat;
251 this.valid = valid;
252 }
253
254 @NonNull
255 public String getName() {
256 return name;
257 }
258
259 @NonNull
260 public Format getActualFormat() {
261 return actualFormat;
262 }
263
264 public boolean isValid() {
265 return valid;
266 }
267 }
268 }