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