001/* 002 * SPDX-FileCopyrightText: none 003 * SPDX-License-Identifier: CC0-1.0 004 */ 005 006package dev.metaschema.maven.plugin; 007 008import org.apache.maven.plugin.MojoExecutionException; 009import org.apache.maven.plugins.annotations.LifecyclePhase; 010import org.apache.maven.plugins.annotations.Mojo; 011import org.apache.maven.plugins.annotations.Parameter; 012 013import java.io.File; 014import java.io.IOException; 015import java.io.Writer; 016import java.nio.charset.StandardCharsets; 017import java.nio.file.Files; 018import java.nio.file.Path; 019import java.nio.file.StandardOpenOption; 020import java.util.EnumSet; 021import java.util.LinkedList; 022import java.util.List; 023import java.util.Locale; 024import java.util.Set; 025 026import dev.metaschema.core.configuration.DefaultConfiguration; 027import dev.metaschema.core.configuration.IConfiguration; 028import dev.metaschema.core.configuration.IMutableConfiguration; 029import dev.metaschema.core.model.IModule; 030import dev.metaschema.core.util.CollectionUtil; 031import dev.metaschema.core.util.ObjectUtils; 032import dev.metaschema.schemagen.ISchemaGenerator; 033import dev.metaschema.schemagen.SchemaGenerationFeature; 034import dev.metaschema.schemagen.json.JsonSchemaGenerator; 035import dev.metaschema.schemagen.xml.XmlSchemaGenerator; 036import edu.umd.cs.findbugs.annotations.NonNull; 037 038/** 039 * Goal which generates XML and JSON schemas for a given set of Metaschema 040 * modules. 041 */ 042@Mojo(name = "generate-schemas", defaultPhase = LifecyclePhase.GENERATE_RESOURCES) 043public class GenerateSchemaMojo 044 extends AbstractMetaschemaMojo { 045 /** 046 * Supported schema output formats for generation. 047 */ 048 public enum SchemaFormat { 049 /** 050 * XML Schema Definition (XSD) format. 051 */ 052 XSD, 053 /** 054 * JSON Schema format. 055 */ 056 JSON_SCHEMA; 057 } 058 059 @NonNull 060 private static final String STALE_FILE_NAME = "generateSschemaStaleFile"; 061 062 @NonNull 063 private static final XmlSchemaGenerator XML_SCHEMA_GENERATOR = new XmlSchemaGenerator(); 064 @NonNull 065 private static final JsonSchemaGenerator JSON_SCHEMA_GENERATOR = new JsonSchemaGenerator(); 066 067 /** 068 * Specifies the formats of the schemas to generate. Multiple formats can be 069 * supplied and this plugin will generate a schema for each of the desired 070 * formats. 071 * <p> 072 * A format is specified by supplying one of the following values in a 073 * <format> subelement: 074 * <ul> 075 * <li><em>json</em> - Creates a JSON Schema 076 * <li><em>xsd</em> - Creates an XML Schema Definition 077 * </ul> 078 */ 079 @Parameter 080 private List<String> formats; 081 082 /** 083 * If enabled, definitions that are defined inline will be generated as inline 084 * types. If disabled, definitions will always be generated as global types. 085 */ 086 @Parameter(defaultValue = "true") 087 @SuppressWarnings("PMD.ImmutableField") 088 private boolean inlineDefinitions = true; 089 090 /** 091 * If enabled, child definitions of a choice that are defined inline will be 092 * generated as inline types. If disabled, child definitions of a choice will 093 * always be generated as global types. This option will only be used if 094 * <code>inlineDefinitions</code> is also enabled. 095 */ 096 @Parameter(defaultValue = "false") 097 private boolean inlineChoiceDefinitions; // false; 098 099 /** 100 * Determine if inlining definitions is required. 101 * 102 * @return {@code true} if inlining definitions is required, or {@code false} 103 * otherwise 104 */ 105 protected boolean isInlineDefinitions() { 106 return inlineDefinitions; 107 } 108 109 /** 110 * Determine if inlining choice definitions is required. 111 * 112 * @return {@code true} if inlining choice definitions is required, or 113 * {@code false} otherwise 114 */ 115 protected boolean isInlineChoiceDefinitions() { 116 return inlineChoiceDefinitions; 117 } 118 119 /** 120 * <p> 121 * Gets the last part of the stale filename. 122 * </p> 123 * <p> 124 * The full stale filename will be generated by pre-pending 125 * {@code "." + getExecution().getExecutionId()} to this staleFileName. 126 * 127 * @return the stale filename postfix 128 */ 129 @Override 130 protected String getStaleFileName() { 131 return STALE_FILE_NAME; 132 } 133 134 /** 135 * Performs schema generation using the provided Metaschema modules. 136 * 137 * @param modules 138 * the Metaschema modules to generate the schema for 139 * @return the list of generated schema files 140 * @throws MojoExecutionException 141 * if an error occurred during generation 142 */ 143 @Override 144 @NonNull 145 protected List<File> generate(@NonNull Set<IModule> modules) throws MojoExecutionException { 146 IMutableConfiguration<SchemaGenerationFeature<?>> schemaGenerationConfig 147 = new DefaultConfiguration<>(); 148 149 if (isInlineDefinitions()) { 150 schemaGenerationConfig.enableFeature(SchemaGenerationFeature.INLINE_DEFINITIONS); 151 } else { 152 schemaGenerationConfig.disableFeature(SchemaGenerationFeature.INLINE_DEFINITIONS); 153 } 154 155 if (isInlineChoiceDefinitions()) { 156 schemaGenerationConfig.enableFeature(SchemaGenerationFeature.INLINE_CHOICE_DEFINITIONS); 157 } else { 158 schemaGenerationConfig.disableFeature(SchemaGenerationFeature.INLINE_CHOICE_DEFINITIONS); 159 } 160 161 Set<SchemaFormat> schemaFormats; 162 if (formats != null) { 163 schemaFormats = ObjectUtils.notNull(EnumSet.noneOf(SchemaFormat.class)); 164 for (String format : formats) { 165 switch (format.toLowerCase(Locale.ROOT)) { 166 case "xsd": 167 schemaFormats.add(SchemaFormat.XSD); 168 break; 169 case "json": 170 schemaFormats.add(SchemaFormat.JSON_SCHEMA); 171 break; 172 default: 173 throw new IllegalStateException("Unsupported schema format: " + format); 174 } 175 } 176 } else { 177 schemaFormats = ObjectUtils.notNull(EnumSet.allOf(SchemaFormat.class)); 178 } 179 180 Path outputDirectory = ObjectUtils.notNull(getOutputDirectory().toPath()); 181 List<File> generatedSchemas = new LinkedList<>(); 182 for (IModule module : modules) { 183 if (getLog().isInfoEnabled()) { 184 getLog().info(String.format("Processing metaschema: %s", module.getLocation())); 185 } 186 if (module.getExportedRootAssemblyDefinitions().isEmpty()) { 187 continue; 188 } 189 generatedSchemas.addAll(generateSchemas(module, schemaGenerationConfig, outputDirectory, schemaFormats)); 190 } 191 return CollectionUtil.unmodifiableList(generatedSchemas); 192 } 193 194 @SuppressWarnings("PMD.AvoidCatchingGenericException") 195 @NonNull 196 private static List<File> generateSchemas( 197 @NonNull IModule module, 198 @NonNull IConfiguration<SchemaGenerationFeature<?>> schemaGenerationConfig, 199 @NonNull Path outputDirectory, 200 @NonNull Set<SchemaFormat> schemaFormats) throws MojoExecutionException { 201 202 String shortName = module.getShortName(); 203 204 List<File> generatedSchemas = new LinkedList<>(); 205 if (schemaFormats.contains(SchemaFormat.XSD)) { 206 try { // XML Schema 207 String filename = String.format("%s_schema.xsd", shortName); 208 Path xmlSchema = ObjectUtils.notNull(outputDirectory.resolve(filename)); 209 generateSchema(module, schemaGenerationConfig, xmlSchema, XML_SCHEMA_GENERATOR); 210 generatedSchemas.add(xmlSchema.toFile()); 211 } catch (Exception ex) { 212 throw new MojoExecutionException("Unable to generate XML schema.", ex); 213 } 214 } 215 216 if (schemaFormats.contains(SchemaFormat.JSON_SCHEMA)) { 217 try { // JSON Schema 218 String filename = String.format("%s_schema.json", shortName); 219 Path jsonSchema = ObjectUtils.notNull(outputDirectory.resolve(filename)); 220 generateSchema(module, schemaGenerationConfig, jsonSchema, JSON_SCHEMA_GENERATOR); 221 generatedSchemas.add(jsonSchema.toFile()); 222 } catch (Exception ex) { 223 throw new MojoExecutionException("Unable to generate JSON schema.", ex); 224 } 225 } 226 return CollectionUtil.unmodifiableList(generatedSchemas); 227 } 228 229 private static void generateSchema( 230 @NonNull IModule module, 231 @NonNull IConfiguration<SchemaGenerationFeature<?>> schemaGenerationConfig, 232 @NonNull Path schemaPath, 233 @NonNull ISchemaGenerator generator) throws IOException { 234 try (@SuppressWarnings("resource") 235 Writer writer = ObjectUtils.notNull(Files.newBufferedWriter( 236 schemaPath, 237 StandardCharsets.UTF_8, 238 StandardOpenOption.CREATE, 239 StandardOpenOption.WRITE, 240 StandardOpenOption.TRUNCATE_EXISTING))) { 241 generator.generateFromModule(module, writer, schemaGenerationConfig); 242 } 243 } 244}