001/* 002 * Portions of this software was developed by employees of the National Institute 003 * of Standards and Technology (NIST), an agency of the Federal Government and is 004 * being made available as a public service. Pursuant to title 17 United States 005 * Code Section 105, works of NIST employees are not subject to copyright 006 * protection in the United States. This software may be subject to foreign 007 * copyright. Permission in the United States and in foreign countries, to the 008 * extent that NIST may hold copyright, to use, copy, modify, create derivative 009 * works, and distribute this software and its documentation without fee is hereby 010 * granted on a non-exclusive basis, provided that this notice and disclaimer 011 * of warranty appears in all copies. 012 * 013 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER 014 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY 015 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF 016 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM 017 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE 018 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE. IN NO EVENT 019 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT, 020 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM, 021 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY, 022 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR 023 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT 024 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER. 025 */ 026 027package gov.nist.secauto.metaschema.maven.plugin; 028 029import org.apache.maven.plugin.AbstractMojo; 030import org.apache.maven.plugin.MojoExecution; 031import org.apache.maven.plugins.annotations.Component; 032import org.apache.maven.plugins.annotations.Parameter; 033import org.apache.maven.project.MavenProject; 034import org.codehaus.plexus.util.DirectoryScanner; 035import org.sonatype.plexus.build.incremental.BuildContext; 036 037import java.io.File; 038import java.net.URI; 039import java.util.Objects; 040import java.util.stream.Collectors; 041import java.util.stream.Stream; 042 043import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 044 045public abstract class AbstractMetaschemaMojo 046 extends AbstractMojo { 047 private static final String SYSTEM_FILE_ENCODING_PROPERTY = "file.encoding"; 048 private static final String[] DEFAULT_INCLUDES = { "**/*.xml" }; 049 050 /** 051 * The Maven project context. 052 * 053 * @parameter default-value="${project}" 054 * @required 055 * @readonly 056 */ 057 @Parameter(defaultValue = "${project}", required = true, readonly = true) 058 MavenProject mavenProject; 059 060 /** 061 * This will be injected if this plugin is executed as part of the standard 062 * Maven lifecycle. If the mojo is directly invoked, this parameter will not be 063 * injected. 064 */ 065 @Parameter(defaultValue = "${mojoExecution}", readonly = true) 066 private MojoExecution mojoExecution; 067 068 @Component 069 private BuildContext buildContext; 070 071 /** 072 * <p> 073 * The directory where the staleFile is found. The staleFile is used to 074 * determine if re-generation of generated Java classes is needed, by recording 075 * when the last build occurred. 076 * </p> 077 * <p> 078 * This directory is expected to be located within the 079 * <code>${project.build.directory}</code>, to ensure that code (re)generation 080 * occurs after cleaning the project. 081 * </p> 082 */ 083 @Parameter(defaultValue = "${project.build.directory}/metaschema", readonly = true, required = true) 084 protected File staleFileDirectory; 085 086 /** 087 * <p> 088 * Defines the encoding used for generating Java Source files. 089 * </p> 090 * <p> 091 * The algorithm for finding the encoding to use is as follows (where the first 092 * non-null value found is used for encoding): 093 * <ol> 094 * <li>If the configuration property is explicitly given within the plugin's 095 * configuration, use that value.</li> 096 * <li>If the Maven property <code>project.build.sourceEncoding</code> is 097 * defined, use its value.</li> 098 * <li>Otherwise use the value from the system property 099 * <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}