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.model.IModule;
009import gov.nist.secauto.metaschema.core.model.MetaschemaException;
010import gov.nist.secauto.metaschema.core.util.ObjectUtils;
011import gov.nist.secauto.metaschema.databind.codegen.JavaGenerator;
012import gov.nist.secauto.metaschema.databind.codegen.config.DefaultBindingConfiguration;
013import gov.nist.secauto.metaschema.databind.model.metaschema.BindingModuleLoader;
014import gov.nist.secauto.metaschema.databind.model.metaschema.IBindingMetaschemaModule;
015
016import org.apache.maven.plugin.MojoExecutionException;
017import org.apache.maven.plugins.annotations.LifecyclePhase;
018import org.apache.maven.plugins.annotations.Mojo;
019import org.apache.maven.plugins.annotations.Parameter;
020
021import java.io.File;
022import java.io.IOException;
023import java.io.OutputStream;
024import java.nio.file.Files;
025import java.nio.file.StandardOpenOption;
026import java.util.Arrays;
027import java.util.Collections;
028import java.util.HashSet;
029import java.util.List;
030import java.util.Set;
031import java.util.stream.Collectors;
032
033import edu.umd.cs.findbugs.annotations.NonNull;
034
035/**
036 * Goal which generates Java source files for a given set of Metaschema modules.
037 */
038@Mojo(name = "generate-sources", defaultPhase = LifecyclePhase.GENERATE_SOURCES)
039public class GenerateSourcesMojo
040    extends AbstractMetaschemaMojo {
041  private static final String STALE_FILE_NAME = "generateSourcesStaleFile";
042
043  /**
044   * A set of binding configurations.
045   */
046  @Parameter
047  protected File[] configs;
048
049  /**
050   * <p>
051   * Gets the last part of the stale filename.
052   * </p>
053   * <p>
054   * The full stale filename will be generated by pre-pending
055   * {@code "." + getExecution().getExecutionId()} to this staleFileName.
056   *
057   * @return the stale filename postfix
058   */
059  @Override
060  protected String getStaleFileName() {
061    return STALE_FILE_NAME;
062  }
063
064  /**
065   * Retrieve a list of binding configurations.
066   *
067   * @return the collection of binding configurations
068   */
069  protected List<File> getConfigs() {
070    List<File> retval;
071    if (configs == null) {
072      retval = Collections.emptyList();
073    } else {
074      retval = Arrays.asList(configs);
075    }
076    return retval;
077  }
078
079  /**
080   * Generate the Java source files for the provided Metaschemas.
081   *
082   * @param modules
083   *          the collection of Metaschema modules to generate sources for
084   * @throws MojoExecutionException
085   *           if an error occurred while generating sources
086   */
087  protected void generate(@NonNull Set<IModule> modules) throws MojoExecutionException {
088    DefaultBindingConfiguration bindingConfiguration = new DefaultBindingConfiguration();
089    for (File config : getConfigs()) {
090      try {
091        if (getLog().isInfoEnabled()) {
092          getLog().info("Loading binding configuration: " + config.getPath());
093        }
094        bindingConfiguration.load(config);
095      } catch (IOException ex) {
096        throw new MojoExecutionException(
097            String.format("Unable to load binding configuration from '%s'.", config.getPath()), ex);
098      }
099    }
100
101    try {
102      if (getLog().isInfoEnabled()) {
103        getLog().info("Generating Java classes in: " + getOutputDirectory().getPath());
104      }
105      JavaGenerator.generate(modules, ObjectUtils.notNull(getOutputDirectory().toPath()),
106          bindingConfiguration);
107    } catch (IOException ex) {
108      throw new MojoExecutionException("Creation of Java classes failed.", ex);
109    }
110  }
111
112  @Override
113  public void execute() throws MojoExecutionException {
114    File staleFile = getStaleFile();
115    try {
116      staleFile = staleFile.getCanonicalFile();
117    } catch (IOException ex) {
118      if (getLog().isWarnEnabled()) {
119        getLog().warn("Unable to resolve canonical path to stale file. Treating it as not existing.", ex);
120      }
121    }
122
123    boolean generate;
124    if (shouldExecutionBeSkipped()) {
125      if (getLog().isDebugEnabled()) {
126        getLog().debug(String.format("Source file generation is configured to be skipped. Skipping."));
127      }
128      generate = false;
129    } else if (!staleFile.exists()) {
130      if (getLog().isInfoEnabled()) {
131        getLog().info(String.format("Stale file '%s' doesn't exist! Generating source files.", staleFile.getPath()));
132      }
133      generate = true;
134    } else {
135      generate = isGenerationRequired();
136    }
137
138    if (generate) {
139
140      File outputDir = getOutputDirectory();
141      if (getLog().isDebugEnabled()) {
142        getLog().debug(String.format("Using outputDirectory: %s", outputDir.getPath()));
143      }
144
145      if (!outputDir.exists() && !outputDir.mkdirs()) {
146        throw new MojoExecutionException("Unable to create output directory: " + outputDir);
147      }
148
149      BindingModuleLoader loader = newModuleLoader();
150
151      // generate Java sources based on provided metaschema sources
152      final Set<IModule> modules = new HashSet<>();
153      for (File source : getModuleSources().collect(Collectors.toList())) {
154        assert source != null;
155        if (getLog().isInfoEnabled()) {
156          getLog().info("Using metaschema source: " + source.getPath());
157        }
158        IBindingMetaschemaModule module;
159        try {
160          module = loader.load(source);
161        } catch (MetaschemaException | IOException ex) {
162          throw new MojoExecutionException("Loading of metaschema failed", ex);
163        }
164        modules.add(module);
165      }
166
167      generate(modules);
168
169      // create the stale file
170      if (!staleFileDirectory.exists() && !staleFileDirectory.mkdirs()) {
171        throw new MojoExecutionException("Unable to create output directory: " + staleFileDirectory);
172      }
173      try (OutputStream os
174          = Files.newOutputStream(staleFile.toPath(), StandardOpenOption.CREATE, StandardOpenOption.WRITE,
175              StandardOpenOption.TRUNCATE_EXISTING)) {
176        os.close();
177        if (getLog().isInfoEnabled()) {
178          getLog().info("Created stale file: " + staleFile);
179        }
180      } catch (IOException ex) {
181        throw new MojoExecutionException("Failed to write stale file: " + staleFile.getPath(), ex);
182      }
183
184      // for m2e
185      getBuildContext().refresh(getOutputDirectory());
186    }
187
188    // add generated sources to Maven
189    try {
190      getMavenProject().addCompileSourceRoot(getOutputDirectory().getCanonicalFile().getPath());
191    } catch (IOException ex) {
192      throw new MojoExecutionException("Unable to add output directory to maven sources.", ex);
193    }
194  }
195}