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.maven.plugin;
28
29 import gov.nist.secauto.metaschema.core.configuration.DefaultConfiguration;
30 import gov.nist.secauto.metaschema.core.configuration.IConfiguration;
31 import gov.nist.secauto.metaschema.core.configuration.IMutableConfiguration;
32 import gov.nist.secauto.metaschema.core.model.IModule;
33 import gov.nist.secauto.metaschema.core.model.MetaschemaException;
34 import gov.nist.secauto.metaschema.core.model.xml.ModuleLoader;
35 import gov.nist.secauto.metaschema.core.util.ObjectUtils;
36 import gov.nist.secauto.metaschema.schemagen.ISchemaGenerator;
37 import gov.nist.secauto.metaschema.schemagen.SchemaGenerationFeature;
38 import gov.nist.secauto.metaschema.schemagen.json.JsonSchemaGenerator;
39 import gov.nist.secauto.metaschema.schemagen.xml.XmlSchemaGenerator;
40
41 import org.apache.maven.plugin.MojoExecutionException;
42 import org.apache.maven.plugins.annotations.LifecyclePhase;
43 import org.apache.maven.plugins.annotations.Mojo;
44 import org.apache.maven.plugins.annotations.Parameter;
45
46 import java.io.File;
47 import java.io.IOException;
48 import java.io.OutputStream;
49 import java.io.Writer;
50 import java.nio.charset.StandardCharsets;
51 import java.nio.file.Files;
52 import java.nio.file.Path;
53 import java.nio.file.StandardOpenOption;
54 import java.util.EnumSet;
55 import java.util.HashSet;
56 import java.util.List;
57 import java.util.Locale;
58 import java.util.Set;
59 import java.util.stream.Collectors;
60
61 import edu.umd.cs.findbugs.annotations.NonNull;
62
63
64
65
66 @Mojo(name = "generate-schemas", defaultPhase = LifecyclePhase.GENERATE_RESOURCES)
67 public class GenerateSchemaMojo
68 extends AbstractMetaschemaMojo {
69 public enum SchemaFormat {
70 XSD,
71 JSON_SCHEMA;
72 }
73
74 @NonNull
75 private static final String STALE_FILE_NAME = "generateSschemaStaleFile";
76
77 @NonNull
78 private static final XmlSchemaGenerator XML_SCHEMA_GENERATOR = new XmlSchemaGenerator();
79 @NonNull
80 private static final JsonSchemaGenerator JSON_SCHEMA_GENERATOR = new JsonSchemaGenerator();
81
82
83
84
85
86
87
88
89
90
91
92
93
94 @Parameter
95 private List<String> formats;
96
97
98
99
100
101 @Parameter(defaultValue = "true")
102 private boolean inlineDefinitions = true;
103
104
105
106
107
108
109
110 @Parameter(defaultValue = "false")
111 private boolean inlineChoiceDefinitions = false;
112
113
114
115
116
117
118
119 protected boolean isInlineDefinitions() {
120 return inlineDefinitions;
121 }
122
123
124
125
126
127
128
129 protected boolean isInlineChoiceDefinitions() {
130 return inlineChoiceDefinitions;
131 }
132
133
134
135
136
137
138
139
140
141
142
143 @Override
144 protected String getStaleFileName() {
145 return STALE_FILE_NAME;
146 }
147
148
149
150
151
152
153
154
155
156 protected void generate(@NonNull Set<IModule> modules) throws MojoExecutionException {
157 IMutableConfiguration<SchemaGenerationFeature<?>> schemaGenerationConfig
158 = new DefaultConfiguration<>();
159
160 if (isInlineDefinitions()) {
161 schemaGenerationConfig.enableFeature(SchemaGenerationFeature.INLINE_DEFINITIONS);
162 } else {
163 schemaGenerationConfig.disableFeature(SchemaGenerationFeature.INLINE_DEFINITIONS);
164 }
165
166 if (isInlineChoiceDefinitions()) {
167 schemaGenerationConfig.enableFeature(SchemaGenerationFeature.INLINE_CHOICE_DEFINITIONS);
168 } else {
169 schemaGenerationConfig.disableFeature(SchemaGenerationFeature.INLINE_CHOICE_DEFINITIONS);
170 }
171
172 Set<SchemaFormat> schemaFormats;
173 if (formats != null) {
174 schemaFormats = ObjectUtils.notNull(EnumSet.noneOf(SchemaFormat.class));
175 for (String format : formats) {
176 switch (format.toLowerCase(Locale.ROOT)) {
177 case "xsd":
178 schemaFormats.add(SchemaFormat.XSD);
179 break;
180 case "json":
181 schemaFormats.add(SchemaFormat.JSON_SCHEMA);
182 break;
183 default:
184 throw new IllegalStateException("Unsupported schema format: " + format);
185 }
186 }
187 } else {
188 schemaFormats = ObjectUtils.notNull(EnumSet.allOf(SchemaFormat.class));
189 }
190
191 Path outputDirectory = ObjectUtils.notNull(getOutputDirectory().toPath());
192 for (IModule module : modules) {
193 getLog().info(String.format("Processing metaschema: %s", module.getLocation()));
194 if (module.getExportedRootAssemblyDefinitions().isEmpty()) {
195 continue;
196 }
197
198 generateSchemas(module, schemaGenerationConfig, outputDirectory, schemaFormats);
199 }
200 }
201
202 private static void generateSchemas(
203 @NonNull IModule module,
204 @NonNull IConfiguration<SchemaGenerationFeature<?>> schemaGenerationConfig,
205 @NonNull Path outputDirectory,
206 @NonNull Set<SchemaFormat> schemaFormats) throws MojoExecutionException {
207
208 String shortName = module.getShortName();
209
210 if (schemaFormats.contains(SchemaFormat.XSD)) {
211 try {
212 String filename = String.format("%s_schema.xsd", shortName);
213 Path xmlSchema = ObjectUtils.notNull(outputDirectory.resolve(filename));
214 generateSchema(module, schemaGenerationConfig, xmlSchema, XML_SCHEMA_GENERATOR);
215 } catch (Exception ex) {
216 throw new MojoExecutionException("Unable to generate XML schema.", ex);
217 }
218 }
219
220 if (schemaFormats.contains(SchemaFormat.JSON_SCHEMA)) {
221 try {
222 String filename = String.format("%s_schema.json", shortName);
223 Path xmlSchema = ObjectUtils.notNull(outputDirectory.resolve(filename));
224 generateSchema(module, schemaGenerationConfig, xmlSchema, JSON_SCHEMA_GENERATOR);
225 } catch (Exception ex) {
226 throw new MojoExecutionException("Unable to generate JSON schema.", ex);
227 }
228 }
229 }
230
231 private static void generateSchema(
232 @NonNull IModule module,
233 @NonNull IConfiguration<SchemaGenerationFeature<?>> schemaGenerationConfig,
234 @NonNull Path schemaPath,
235 @NonNull ISchemaGenerator generator) throws IOException {
236 try (@SuppressWarnings("resource") Writer writer = ObjectUtils.notNull(Files.newBufferedWriter(
237 schemaPath,
238 StandardCharsets.UTF_8,
239 StandardOpenOption.CREATE,
240 StandardOpenOption.WRITE,
241 StandardOpenOption.TRUNCATE_EXISTING))) {
242 generator.generateFromModule(module, writer, schemaGenerationConfig);
243 }
244 }
245
246 @Override
247 public void execute() throws MojoExecutionException {
248 File staleFile = getStaleFile();
249 try {
250 staleFile = staleFile.getCanonicalFile();
251 } catch (IOException ex) {
252 getLog().warn("Unable to resolve canonical path to stale file. Treating it as not existing.", ex);
253 }
254
255 boolean generate;
256 if (shouldExecutionBeSkipped()) {
257 getLog().debug(String.format("Schema generation is configured to be skipped. Skipping."));
258 generate = false;
259 } else if (!staleFile.exists()) {
260 getLog().info(String.format("Stale file '%s' doesn't exist! Generating source files.", staleFile.getPath()));
261 generate = true;
262 } else {
263 generate = isGenerationRequired();
264 }
265
266 if (generate) {
267 File outputDir = getOutputDirectory();
268 getLog().debug(String.format("Using outputDirectory: %s", outputDir.getPath()));
269
270 if (!outputDir.exists()) {
271 if (!outputDir.mkdirs()) {
272 throw new MojoExecutionException("Unable to create output directory: " + outputDir);
273 }
274 }
275
276
277 final ModuleLoader loader = new ModuleLoader();
278 loader.allowEntityResolution();
279 final Set<IModule> modules = new HashSet<>();
280 for (File source : getSources().collect(Collectors.toList())) {
281 getLog().info("Using metaschema source: " + source.getPath());
282 IModule module;
283 try {
284 module = loader.load(source);
285 } catch (MetaschemaException | IOException ex) {
286 throw new MojoExecutionException("Loading of metaschema failed", ex);
287 }
288 modules.add(module);
289 }
290
291 generate(modules);
292
293
294 if (!staleFileDirectory.exists()) {
295 if (!staleFileDirectory.mkdirs()) {
296 throw new MojoExecutionException("Unable to create output directory: " + staleFileDirectory);
297 }
298 }
299 try (OutputStream os
300 = Files.newOutputStream(staleFile.toPath(), StandardOpenOption.CREATE, StandardOpenOption.WRITE,
301 StandardOpenOption.TRUNCATE_EXISTING)) {
302 os.close();
303 getLog().info("Created stale file: " + staleFile);
304 } catch (IOException ex) {
305 throw new MojoExecutionException("Failed to write stale file: " + staleFile.getPath(), ex);
306 }
307
308
309 getBuildContext().refresh(getOutputDirectory());
310 }
311
312
313
314
315
316
317
318
319 }
320 }