1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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 }