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.IBindingContext;
15 import gov.nist.secauto.metaschema.databind.model.metaschema.IBindingModuleLoader;
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.Writer;
29 import java.nio.charset.StandardCharsets;
30 import java.nio.file.Files;
31 import java.nio.file.Path;
32 import java.nio.file.StandardOpenOption;
33 import java.util.EnumSet;
34 import java.util.List;
35 import java.util.Locale;
36 import java.util.Set;
37
38 import edu.umd.cs.findbugs.annotations.NonNull;
39
40
41
42
43
44 @Mojo(name = "generate-schemas", defaultPhase = LifecyclePhase.GENERATE_RESOURCES)
45 public class GenerateSchemaMojo
46 extends AbstractMetaschemaMojo {
47 public enum SchemaFormat {
48 XSD,
49 JSON_SCHEMA;
50 }
51
52 @NonNull
53 private static final String STALE_FILE_NAME = "generateSschemaStaleFile";
54
55 @NonNull
56 private static final XmlSchemaGenerator XML_SCHEMA_GENERATOR = new XmlSchemaGenerator();
57 @NonNull
58 private static final JsonSchemaGenerator JSON_SCHEMA_GENERATOR = new JsonSchemaGenerator();
59
60
61
62
63
64
65
66
67
68
69
70
71
72 @Parameter
73 private List<String> formats;
74
75
76
77
78
79 @Parameter(defaultValue = "true")
80 @SuppressWarnings("PMD.ImmutableField")
81 private boolean inlineDefinitions = true;
82
83
84
85
86
87
88
89 @Parameter(defaultValue = "false")
90 private boolean inlineChoiceDefinitions;
91
92
93
94
95
96
97
98 protected boolean isInlineDefinitions() {
99 return inlineDefinitions;
100 }
101
102
103
104
105
106
107
108 protected boolean isInlineChoiceDefinitions() {
109 return inlineChoiceDefinitions;
110 }
111
112
113
114
115
116
117
118
119
120
121
122 @Override
123 protected String getStaleFileName() {
124 return STALE_FILE_NAME;
125 }
126
127
128
129
130
131
132
133
134
135 protected void generate(@NonNull Set<IModule> modules) throws MojoExecutionException {
136 IMutableConfiguration<SchemaGenerationFeature<?>> schemaGenerationConfig
137 = new DefaultConfiguration<>();
138
139 if (isInlineDefinitions()) {
140 schemaGenerationConfig.enableFeature(SchemaGenerationFeature.INLINE_DEFINITIONS);
141 } else {
142 schemaGenerationConfig.disableFeature(SchemaGenerationFeature.INLINE_DEFINITIONS);
143 }
144
145 if (isInlineChoiceDefinitions()) {
146 schemaGenerationConfig.enableFeature(SchemaGenerationFeature.INLINE_CHOICE_DEFINITIONS);
147 } else {
148 schemaGenerationConfig.disableFeature(SchemaGenerationFeature.INLINE_CHOICE_DEFINITIONS);
149 }
150
151 Set<SchemaFormat> schemaFormats;
152 if (formats != null) {
153 schemaFormats = ObjectUtils.notNull(EnumSet.noneOf(SchemaFormat.class));
154 for (String format : formats) {
155 switch (format.toLowerCase(Locale.ROOT)) {
156 case "xsd":
157 schemaFormats.add(SchemaFormat.XSD);
158 break;
159 case "json":
160 schemaFormats.add(SchemaFormat.JSON_SCHEMA);
161 break;
162 default:
163 throw new IllegalStateException("Unsupported schema format: " + format);
164 }
165 }
166 } else {
167 schemaFormats = ObjectUtils.notNull(EnumSet.allOf(SchemaFormat.class));
168 }
169
170 Path outputDirectory = ObjectUtils.notNull(getOutputDirectory().toPath());
171 for (IModule module : modules) {
172 if (getLog().isInfoEnabled()) {
173 getLog().info(String.format("Processing metaschema: %s", module.getLocation()));
174 }
175 if (module.getExportedRootAssemblyDefinitions().isEmpty()) {
176 continue;
177 }
178 generateSchemas(module, schemaGenerationConfig, outputDirectory, schemaFormats);
179 }
180 }
181
182 @SuppressWarnings("PMD.AvoidCatchingGenericException")
183 private static void generateSchemas(
184 @NonNull IModule module,
185 @NonNull IConfiguration<SchemaGenerationFeature<?>> schemaGenerationConfig,
186 @NonNull Path outputDirectory,
187 @NonNull Set<SchemaFormat> schemaFormats) throws MojoExecutionException {
188
189 String shortName = module.getShortName();
190
191 if (schemaFormats.contains(SchemaFormat.XSD)) {
192 try {
193 String filename = String.format("%s_schema.xsd", shortName);
194 Path xmlSchema = ObjectUtils.notNull(outputDirectory.resolve(filename));
195 generateSchema(module, schemaGenerationConfig, xmlSchema, XML_SCHEMA_GENERATOR);
196 } catch (Exception ex) {
197 throw new MojoExecutionException("Unable to generate XML schema.", ex);
198 }
199 }
200
201 if (schemaFormats.contains(SchemaFormat.JSON_SCHEMA)) {
202 try {
203 String filename = String.format("%s_schema.json", shortName);
204 Path xmlSchema = ObjectUtils.notNull(outputDirectory.resolve(filename));
205 generateSchema(module, schemaGenerationConfig, xmlSchema, JSON_SCHEMA_GENERATOR);
206 } catch (Exception ex) {
207 throw new MojoExecutionException("Unable to generate JSON schema.", ex);
208 }
209 }
210 }
211
212 private static void generateSchema(
213 @NonNull IModule module,
214 @NonNull IConfiguration<SchemaGenerationFeature<?>> schemaGenerationConfig,
215 @NonNull Path schemaPath,
216 @NonNull ISchemaGenerator generator) throws IOException {
217 try (@SuppressWarnings("resource")
218 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 = ObjectUtils.notNull(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 performGeneration();
256 createStaleFile(staleFile);
257
258
259 getBuildContext().refresh(getOutputDirectory());
260 }
261
262
263
264
265
266
267
268 }
269
270 @SuppressWarnings("PMD.AvoidCatchingGenericException")
271 private void performGeneration() throws MojoExecutionException {
272 File outputDir = getOutputDirectory();
273 if (getLog().isDebugEnabled()) {
274 getLog().debug(String.format("Using outputDirectory: %s", outputDir.getPath()));
275 }
276
277 if (!outputDir.exists() && !outputDir.mkdirs()) {
278 throw new MojoExecutionException("Unable to create output directory: " + outputDir);
279 }
280
281 IBindingContext bindingContext;
282 try {
283 bindingContext = newBindingContext();
284 } catch (MetaschemaException | IOException ex) {
285 throw new MojoExecutionException("Failed to create the binding context", ex);
286 }
287
288 IBindingModuleLoader loader = bindingContext.newModuleLoader();
289 loader.allowEntityResolution();
290
291
292 Set<IModule> modules;
293 try {
294 modules = getModulesToGenerateFor(bindingContext);
295 } catch (Exception ex) {
296 throw new MojoExecutionException("Loading of metaschema modules failed", ex);
297 }
298
299 generate(modules);
300
301 }
302 }