001/* 002 * Portions of this software was developed by employees of the National Institute 003 * of Standards and Technology (NIST), an agency of the Federal Government and is 004 * being made available as a public service. Pursuant to title 17 United States 005 * Code Section 105, works of NIST employees are not subject to copyright 006 * protection in the United States. This software may be subject to foreign 007 * copyright. Permission in the United States and in foreign countries, to the 008 * extent that NIST may hold copyright, to use, copy, modify, create derivative 009 * works, and distribute this software and its documentation without fee is hereby 010 * granted on a non-exclusive basis, provided that this notice and disclaimer 011 * of warranty appears in all copies. 012 * 013 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER 014 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY 015 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF 016 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM 017 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE 018 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE. IN NO EVENT 019 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT, 020 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM, 021 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY, 022 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR 023 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT 024 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER. 025 */ 026 027package gov.nist.secauto.metaschema.maven.plugin; 028 029import gov.nist.secauto.metaschema.core.configuration.DefaultConfiguration; 030import gov.nist.secauto.metaschema.core.configuration.IConfiguration; 031import gov.nist.secauto.metaschema.core.configuration.IMutableConfiguration; 032import gov.nist.secauto.metaschema.core.model.IModule; 033import gov.nist.secauto.metaschema.core.model.MetaschemaException; 034import gov.nist.secauto.metaschema.core.model.xml.ModuleLoader; 035import gov.nist.secauto.metaschema.core.util.ObjectUtils; 036import gov.nist.secauto.metaschema.schemagen.ISchemaGenerator; 037import gov.nist.secauto.metaschema.schemagen.SchemaGenerationFeature; 038import gov.nist.secauto.metaschema.schemagen.json.JsonSchemaGenerator; 039import gov.nist.secauto.metaschema.schemagen.xml.XmlSchemaGenerator; 040 041import org.apache.maven.plugin.MojoExecutionException; 042import org.apache.maven.plugins.annotations.LifecyclePhase; 043import org.apache.maven.plugins.annotations.Mojo; 044import org.apache.maven.plugins.annotations.Parameter; 045 046import java.io.File; 047import java.io.IOException; 048import java.io.OutputStream; 049import java.io.Writer; 050import java.nio.charset.StandardCharsets; 051import java.nio.file.Files; 052import java.nio.file.Path; 053import java.nio.file.StandardOpenOption; 054import java.util.EnumSet; 055import java.util.HashSet; 056import java.util.List; 057import java.util.Locale; 058import java.util.Set; 059import java.util.stream.Collectors; 060 061import edu.umd.cs.findbugs.annotations.NonNull; 062 063/** 064 * Goal which generates Java source files for a given set of Module definitions. 065 */ 066@Mojo(name = "generate-schemas", defaultPhase = LifecyclePhase.GENERATE_RESOURCES) 067public class GenerateSchemaMojo 068 extends AbstractMetaschemaMojo { 069 public enum SchemaFormat { 070 XSD, 071 JSON_SCHEMA; 072 } 073 074 @NonNull 075 private static final String STALE_FILE_NAME = "generateSschemaStaleFile"; 076 077 @NonNull 078 private static final XmlSchemaGenerator XML_SCHEMA_GENERATOR = new XmlSchemaGenerator(); 079 @NonNull 080 private static final JsonSchemaGenerator JSON_SCHEMA_GENERATOR = new JsonSchemaGenerator(); 081 082 /** 083 * Specifies the formats of the schemas to generate. Multiple formats can be 084 * supplied and this plugin will generate a schema for each of the desired 085 * formats. 086 * <p> 087 * A format is specified by supplying one of the following values in a 088 * <format> subelement: 089 * <ul> 090 * <li><em>json</em> - Creates a JSON Schema</li> 091 * <li><em>xsd</em> - Creates an XML Schema Definition</li> 092 * </ul> 093 */ 094 @Parameter 095 private List<String> formats; 096 097 /** 098 * If enabled, definitions that are defined inline will be generated as inline 099 * types. If disabled, definitions will always be generated as global types. 100 */ 101 @Parameter(defaultValue = "true") 102 private boolean inlineDefinitions = true; 103 104 /** 105 * If enabled, child definitions of a choice that are defined inline will be 106 * generated as inline types. If disabled, child definitions of a choice will 107 * always be generated as global types. This option will only be used if 108 * <code>inlineDefinitions</code> is also enabled. 109 */ 110 @Parameter(defaultValue = "false") 111 private boolean inlineChoiceDefinitions = false; 112 113 /** 114 * Determine if inlining definitions is required. 115 * 116 * @return {@code true} if inlining definitions is required, or {@code false} 117 * otherwise 118 */ 119 protected boolean isInlineDefinitions() { 120 return inlineDefinitions; 121 } 122 123 /** 124 * Determine if inlining choice definitions is required. 125 * 126 * @return {@code true} if inlining choice definitions is required, or 127 * {@code false} otherwise 128 */ 129 protected boolean isInlineChoiceDefinitions() { 130 return inlineChoiceDefinitions; 131 } 132 133 /** 134 * <p> 135 * Gets the last part of the stale filename. 136 * </p> 137 * <p> 138 * The full stale filename will be generated by pre-pending 139 * {@code "." + getExecution().getExecutionId()} to this staleFileName. 140 * 141 * @return the stale filename postfix 142 */ 143 @Override 144 protected String getStaleFileName() { 145 return STALE_FILE_NAME; 146 } 147 148 /** 149 * Performs schema generation using the provided Metaschema modules. 150 * 151 * @param modules 152 * the Metaschema modules to generate the schema for 153 * @throws MojoExecutionException 154 * if an error occurred during generation 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 { // XML Schema 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 { // JSON Schema 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 // generate Java sources based on provided Module sources 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 // create the stale file 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 // for m2e 309 getBuildContext().refresh(getOutputDirectory()); 310 } 311 312 // // add generated sources to Maven 313 // try { 314 // getMavenProject()..addCompileSourceRoot(getOutputDirectory().getCanonicalFile().getPath()); 315 // } catch (IOException ex) { 316 // throw new MojoExecutionException("Unable to add output directory to maven 317 // sources.", ex); 318 // } 319 } 320}