1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.metaschema.maven.plugin;
7   
8   import gov.nist.secauto.metaschema.core.model.IModule;
9   import gov.nist.secauto.metaschema.core.model.MetaschemaException;
10  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
11  import gov.nist.secauto.metaschema.databind.codegen.JavaGenerator;
12  import gov.nist.secauto.metaschema.databind.codegen.config.DefaultBindingConfiguration;
13  import gov.nist.secauto.metaschema.databind.model.metaschema.BindingModuleLoader;
14  import gov.nist.secauto.metaschema.databind.model.metaschema.IBindingMetaschemaModule;
15  
16  import org.apache.maven.plugin.MojoExecutionException;
17  import org.apache.maven.plugins.annotations.LifecyclePhase;
18  import org.apache.maven.plugins.annotations.Mojo;
19  import org.apache.maven.plugins.annotations.Parameter;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.io.OutputStream;
24  import java.nio.file.Files;
25  import java.nio.file.StandardOpenOption;
26  import java.util.Arrays;
27  import java.util.Collections;
28  import java.util.HashSet;
29  import java.util.List;
30  import java.util.Set;
31  import java.util.stream.Collectors;
32  
33  import edu.umd.cs.findbugs.annotations.NonNull;
34  
35  /**
36   * Goal which generates Java source files for a given set of Metaschema modules.
37   */
38  @Mojo(name = "generate-sources", defaultPhase = LifecyclePhase.GENERATE_SOURCES)
39  public class GenerateSourcesMojo
40      extends AbstractMetaschemaMojo {
41    private static final String STALE_FILE_NAME = "generateSourcesStaleFile";
42  
43    /**
44     * A set of binding configurations.
45     */
46    @Parameter
47    protected File[] configs;
48  
49    /**
50     * <p>
51     * Gets the last part of the stale filename.
52     * </p>
53     * <p>
54     * The full stale filename will be generated by pre-pending
55     * {@code "." + getExecution().getExecutionId()} to this staleFileName.
56     *
57     * @return the stale filename postfix
58     */
59    @Override
60    protected String getStaleFileName() {
61      return STALE_FILE_NAME;
62    }
63  
64    /**
65     * Retrieve a list of binding configurations.
66     *
67     * @return the collection of binding configurations
68     */
69    protected List<File> getConfigs() {
70      List<File> retval;
71      if (configs == null) {
72        retval = Collections.emptyList();
73      } else {
74        retval = Arrays.asList(configs);
75      }
76      return retval;
77    }
78  
79    /**
80     * Generate the Java source files for the provided Metaschemas.
81     *
82     * @param modules
83     *          the collection of Metaschema modules to generate sources for
84     * @throws MojoExecutionException
85     *           if an error occurred while generating sources
86     */
87    protected void generate(@NonNull Set<IModule> modules) throws MojoExecutionException {
88      DefaultBindingConfiguration bindingConfiguration = new DefaultBindingConfiguration();
89      for (File config : getConfigs()) {
90        try {
91          if (getLog().isInfoEnabled()) {
92            getLog().info("Loading binding configuration: " + config.getPath());
93          }
94          bindingConfiguration.load(config);
95        } catch (IOException ex) {
96          throw new MojoExecutionException(
97              String.format("Unable to load binding configuration from '%s'.", config.getPath()), ex);
98        }
99      }
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 }