View Javadoc
1   /*
2    * Portions of this software was developed by employees of the National Institute
3    * of Standards and Technology (NIST), an agency of the Federal Government and is
4    * being made available as a public service. Pursuant to title 17 United States
5    * Code Section 105, works of NIST employees are not subject to copyright
6    * protection in the United States. This software may be subject to foreign
7    * copyright. Permission in the United States and in foreign countries, to the
8    * extent that NIST may hold copyright, to use, copy, modify, create derivative
9    * works, and distribute this software and its documentation without fee is hereby
10   * granted on a non-exclusive basis, provided that this notice and disclaimer
11   * of warranty appears in all copies.
12   *
13   * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
14   * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
15   * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
16   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
17   * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
18   * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
19   * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
20   * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
21   * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
22   * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
23   * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
24   * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
25   */
26  
27  package gov.nist.secauto.metaschema.maven.plugin;
28  
29  import org.apache.maven.plugin.AbstractMojo;
30  import org.apache.maven.plugin.MojoExecution;
31  import org.apache.maven.plugins.annotations.Component;
32  import org.apache.maven.plugins.annotations.Parameter;
33  import org.apache.maven.project.MavenProject;
34  import org.codehaus.plexus.util.DirectoryScanner;
35  import org.sonatype.plexus.build.incremental.BuildContext;
36  
37  import java.io.File;
38  import java.net.URI;
39  import java.util.Objects;
40  import java.util.stream.Collectors;
41  import java.util.stream.Stream;
42  
43  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
44  
45  public abstract class AbstractMetaschemaMojo
46      extends AbstractMojo {
47    private static final String SYSTEM_FILE_ENCODING_PROPERTY = "file.encoding";
48    private static final String[] DEFAULT_INCLUDES = { "**/*.xml" };
49  
50    /**
51     * The Maven project context.
52     *
53     * @parameter default-value="${project}"
54     * @required
55     * @readonly
56     */
57    @Parameter(defaultValue = "${project}", required = true, readonly = true)
58    MavenProject mavenProject;
59  
60    /**
61     * This will be injected if this plugin is executed as part of the standard
62     * Maven lifecycle. If the mojo is directly invoked, this parameter will not be
63     * injected.
64     */
65    @Parameter(defaultValue = "${mojoExecution}", readonly = true)
66    private MojoExecution mojoExecution;
67  
68    @Component
69    private BuildContext buildContext;
70  
71    /**
72     * <p>
73     * The directory where the staleFile is found. The staleFile is used to
74     * determine if re-generation of generated Java classes is needed, by recording
75     * when the last build occurred.
76     * </p>
77     * <p>
78     * This directory is expected to be located within the
79     * <code>${project.build.directory}</code>, to ensure that code (re)generation
80     * occurs after cleaning the project.
81     * </p>
82     */
83    @Parameter(defaultValue = "${project.build.directory}/metaschema", readonly = true, required = true)
84    protected File staleFileDirectory;
85  
86    /**
87     * <p>
88     * Defines the encoding used for generating Java Source files.
89     * </p>
90     * <p>
91     * The algorithm for finding the encoding to use is as follows (where the first
92     * non-null value found is used for encoding):
93     * <ol>
94     * <li>If the configuration property is explicitly given within the plugin's
95     * configuration, use that value.</li>
96     * <li>If the Maven property <code>project.build.sourceEncoding</code> is
97     * defined, use its value.</li>
98     * <li>Otherwise use the value from the system property
99     * <code>file.encoding</code>.</li>
100    * </ol>
101    * </p>
102    *
103    * @see #getEncoding()
104    * @since 2.0
105    */
106   @Parameter(defaultValue = "${project.build.sourceEncoding}")
107   private String encoding;
108 
109   /**
110    * Location to generate Java source files in.
111    */
112   @Parameter(defaultValue = "${project.build.directory}/generated-sources/metaschema", required = true)
113   private File outputDirectory;
114 
115   /**
116    * The directory to read source metaschema from.
117    */
118   @Parameter(defaultValue = "${basedir}/src/main/metaschema")
119   private File metaschemaDir;
120 
121   /**
122    * A set of inclusion patterns used to select which metaschema are to be
123    * processed. By default, all files are processed.
124    */
125   @Parameter
126   protected String[] includes;
127 
128   /**
129    * A set of exclusion patterns used to prevent certain files from being
130    * processed. By default, this set is empty such that no files are excluded.
131    */
132   @Parameter
133   protected String[] excludes;
134 
135   /**
136    * Indicate if the execution should be skipped.
137    */
138   @Parameter(property = "metaschema.skip", defaultValue = "false")
139   private boolean skip;
140 
141   /**
142    * The BuildContext is used to identify which files or directories were modified
143    * since last build. This is used to determine if Module-based generation must
144    * be performed again.
145    *
146    * @return the active Plexus BuildContext.
147    */
148   protected final BuildContext getBuildContext() {
149     return buildContext;
150   }
151 
152   /**
153    * Retrieve the Maven project context.
154    *
155    * @return The active MavenProject.
156    */
157   protected final MavenProject getMavenProject() {
158     return mavenProject;
159   }
160 
161   /**
162    * Retrieve the mojo execution context.
163    *
164    * @return The active MojoExecution.
165    */
166   @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "this is a data holder")
167   public MojoExecution getMojoExecution() {
168     return mojoExecution;
169   }
170 
171   /**
172    * Retrieve the directory where generated classes will be stored.
173    *
174    * @return the directory
175    */
176   protected File getOutputDirectory() {
177     return outputDirectory;
178   }
179 
180   /**
181    * Set the directory where generated classes will be stored.
182    *
183    * @param outputDirectory
184    *          the directory to use
185    */
186   protected void setOutputDirectory(File outputDirectory) {
187     Objects.requireNonNull(outputDirectory, "outputDirectory");
188     this.outputDirectory = outputDirectory;
189   }
190 
191   /**
192    * Gets the file encoding to use for generated classes.
193    * <p>
194    * The algorithm for finding the encoding to use is as follows (where the first
195    * non-null value found is used for encoding):
196    * </p>
197    * <ol>
198    * <li>If the configuration property is explicitly given within the plugin's
199    * configuration, use that value.</li>
200    * <li>If the Maven property <code>project.build.sourceEncoding</code> is
201    * defined, use its value.</li>
202    * <li>Otherwise use the value from the system property
203    * <code>file.encoding</code>.</li>
204    * </ol>
205    *
206    * @return The encoding to be used by this AbstractJaxbMojo and its tools.
207    */
208   protected final String getEncoding() {
209     String encoding;
210     if (this.encoding != null) {
211       // first try to use the provided encoding
212       encoding = this.encoding;
213       getLog().debug(String.format("Using configured encoding [%s].", encoding));
214     } else {
215       encoding = System.getProperty(SYSTEM_FILE_ENCODING_PROPERTY);
216       getLog().warn(String.format("Using system encoding [%s]. This build is platform dependent!", encoding));
217     }
218     return encoding;
219   }
220 
221   /**
222    * Retrieve a stream of Module file sources.
223    *
224    * @return the stream
225    */
226   protected Stream<File> getSources() {
227     DirectoryScanner ds = new DirectoryScanner();
228     ds.setBasedir(metaschemaDir);
229     ds.setIncludes(includes != null && includes.length > 0 ? includes : DEFAULT_INCLUDES);
230     ds.setExcludes(excludes != null && excludes.length > 0 ? excludes : null);
231     ds.addDefaultExcludes();
232     ds.setCaseSensitive(true);
233     ds.setFollowSymlinks(false);
234     ds.scan();
235     return Stream.of(ds.getIncludedFiles()).map(filename -> new File(metaschemaDir, filename)).distinct();
236   }
237 
238   /**
239    * Determine if the execution of this mojo should be skipped.
240    *
241    * @return {@code true} if the mojo execution should be skipped, or
242    *         {@code false} otherwise
243    */
244   protected boolean shouldExecutionBeSkipped() {
245     return skip;
246   }
247 
248   /**
249    * Get the name of the file that is used to detect staleness.
250    *
251    * @return the name
252    */
253   protected abstract String getStaleFileName();
254 
255   /**
256    * Gets the staleFile for this execution.
257    *
258    * @return the staleFile
259    */
260   protected final File getStaleFile() {
261     StringBuilder builder = new StringBuilder();
262     if (getMojoExecution() != null) {
263       builder.append(getMojoExecution().getExecutionId()).append('-');
264     }
265     builder.append(getStaleFileName());
266     return new File(staleFileDirectory, builder.toString());
267   }
268 
269   /**
270    * Determine if code generation is required. This is done by comparing the last
271    * modified time of each Module source file against the stale file managed by
272    * this plugin.
273    *
274    * @return {@code true} if the code generation is needed, or {@code false}
275    *         otherwise
276    */
277   protected boolean isGenerationRequired() {
278     final File staleFile = getStaleFile();
279     boolean generate = !staleFile.exists();
280     if (generate) {
281       getLog().info(String.format("Stale file '%s' doesn't exist! Generating source files.", staleFile.getPath()));
282       generate = true;
283     } else {
284       generate = false;
285       // check for staleness
286       long staleLastModified = staleFile.lastModified();
287 
288       BuildContext buildContext = getBuildContext();
289       URI metaschemaDirRelative = getMavenProject().getBasedir().toURI().relativize(metaschemaDir.toURI());
290 
291       if (buildContext.isIncremental() && buildContext.hasDelta(metaschemaDirRelative.toString())) {
292         getLog().info("metaschemaDirRelative: " + metaschemaDirRelative.toString());
293         generate = true;
294       }
295 
296       if (!generate) {
297         for (File sourceFile : getSources().collect(Collectors.toList())) {
298           getLog().info("Source file: " + sourceFile.getPath());
299           if (sourceFile.lastModified() > staleLastModified) {
300             generate = true;
301           }
302         }
303       }
304     }
305     return generate;
306   }
307 }