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 }