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   * &lt;format&gt; 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}