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