001package com.randomnoun.maven.plugin.yamlCombine; 002 003import java.io.File; 004import java.io.FileOutputStream; 005import java.io.IOException; 006import java.io.OutputStreamWriter; 007import java.io.PrintWriter; 008import java.nio.charset.Charset; 009import java.util.List; 010 011import org.apache.maven.execution.MavenSession; 012import org.apache.maven.plugin.AbstractMojo; 013import org.apache.maven.plugin.MojoExecutionException; 014import org.apache.maven.plugins.annotations.LifecyclePhase; 015import org.apache.maven.plugins.annotations.Mojo; 016import org.apache.maven.plugins.annotations.Parameter; 017import org.apache.maven.project.MavenProject; 018import org.apache.maven.project.MavenProjectHelper; 019import org.apache.maven.settings.Settings; 020import org.apache.maven.shared.filtering.DefaultMavenFileFilter; 021import org.apache.maven.shared.filtering.FilterWrapper; 022import org.apache.maven.shared.filtering.MavenFilteringException; 023import org.apache.maven.shared.filtering.MavenResourcesExecution; 024import org.apache.maven.shared.model.fileset.FileSet; 025import org.apache.maven.shared.utils.io.DirectoryScanner; 026import org.codehaus.plexus.logging.Logger; 027import org.codehaus.plexus.logging.console.ConsoleLoggerManager; 028import org.sonatype.plexus.build.incremental.BuildContext; 029 030/** 031 * Maven goal which combines a bunch of yaml files into a big yaml file. 032 * 033 * @blog http://www.randomnoun.com/wp/2021/06/29/swagger-combine/ 034 */ 035@Mojo (name = "yaml-combine", defaultPhase = LifecyclePhase.GENERATE_SOURCES) 036 037public class YamlCombineMojo 038 extends AbstractMojo 039{ 040 // FileSet handling from 041 // https://github.com/apache/maven-jar-plugin/blob/maven-jar-plugin-3.2.0/src/main/java/org/apache/maven/plugins/jar/AbstractJarMojo.java 042 043 /** 044 * The input files to combine. Files that are included via <code>$xref</code> references do not need to be included in this fileset. 045 */ 046 @Parameter( property="fileset") 047 private FileSet fileset; 048 049 @Parameter( defaultValue = "UTF-8", required = false ) 050 private String fileSetEncoding; 051 052 /** 053 * Directory containing the generated JAR. 054 */ 055 @Parameter( defaultValue = "${project.build.directory}", required = true ) 056 private File outputDirectory; 057 058 059 060 @Parameter( defaultValue = "UTF-8", required = false ) 061 private String outputFileEncoding; 062 063 /** 064 * Name of the generated JAR. 065 */ 066 @Parameter( defaultValue = "${project.build.finalName}", readonly = true ) 067 private String finalName; 068 069 /** 070 * True to filter files before processing, in the maven copy-resources sense ( i.e. perform variable substitution ). 071 */ 072 // Because that needed a new verb obviously. 073 @Parameter( property="filtering", defaultValue = "false", required = true ) 074 private boolean filtering; 075 076 /** 077 * True to increase logging 078 */ 079 @Parameter( property="verbose", defaultValue = "false", required = true ) 080 private boolean verbose; 081 082 083 084 /** 085 * The {@link {MavenProject}. 086 */ 087 @Parameter( defaultValue = "${project}", readonly = true, required = true ) 088 private MavenProject project; 089 090 /** 091 * The {@link MavenSession}. 092 */ 093 @Parameter( defaultValue = "${session}", readonly = true, required = true ) 094 private MavenSession session; 095 096 097 /** @component */ 098 private BuildContext buildContext; 099 100 101 /** 102 * @parameter property="settings" 103 * @required 104 * @since 1.0 105 * @readonly 106 */ 107 private Settings settings; 108 109 110 /** 111 * 112 */ 113 @org.apache.maven.plugins.annotations.Component 114 private MavenProjectHelper projectHelper; 115 116 117 // private method in FileSetManager 118 119 private DirectoryScanner scan(FileSet fileSet) { 120 File basedir = new File(fileSet.getDirectory()); 121 if (!basedir.exists() || !basedir.isDirectory()) { 122 return null; 123 } 124 125 DirectoryScanner scanner = new DirectoryScanner(); 126 127 String[] includesArray = fileSet.getIncludesArray(); 128 String[] excludesArray = fileSet.getExcludesArray(); 129 130 if (includesArray.length > 0) { 131 scanner.setIncludes(includesArray); 132 } 133 134 if (excludesArray.length > 0) { 135 scanner.setExcludes(excludesArray); 136 } 137 138 if (fileSet.isUseDefaultExcludes()) { 139 scanner.addDefaultExcludes(); 140 } 141 142 scanner.setBasedir(basedir); 143 scanner.setFollowSymlinks(fileSet.isFollowSymlinks()); 144 145 scanner.scan(); 146 147 return scanner; 148 } 149 150 151 152 153 /** 154 * Generates the JAR. 155 * 156 * @return The instance of File for the created archive file. 157 * @throws MojoExecutionException in case of an error. 158 */ 159 public File createCombinedYaml() throws MojoExecutionException { 160 if (outputDirectory == null) { 161 throw new IllegalArgumentException("Missing outputDirectory"); 162 } 163 if (finalName == null) { 164 throw new IllegalArgumentException("Missing finalName"); 165 } 166 File destFile = new File(outputDirectory, finalName); 167 Charset outputFileCharset = Charset.forName(outputFileEncoding); 168 Charset inputFileSetCharset = Charset.forName(fileSetEncoding); 169 170 // filesets aren't order-preserving. 171 // which is kind of annoying. could sort the Files just to get some 172 // deterministic order I suppose 173 174 if (destFile.exists() && destFile.isDirectory()) { 175 throw new MojoExecutionException(destFile + " is a directory."); 176 } 177 if (destFile.exists() && !destFile.canWrite()) { 178 throw new MojoExecutionException(destFile + " is not writable."); 179 } 180 181 getLog().info("Creating combined yaml file " + destFile.getAbsolutePath()); 182 183 if (!destFile.getParentFile().exists()) { 184 // create the parent directory... 185 if (!destFile.getParentFile().mkdirs()) { 186 // Failure, unable to create specified directory for some unknown reason. 187 throw new MojoExecutionException("Unable to create directory or parent directory of " + destFile); 188 } 189 } 190 if (destFile.exists()) { 191 // delete existing 192 if (!destFile.delete()) { 193 throw new MojoExecutionException("Unable to delete existing file " + destFile); 194 } 195 } 196 197 198 DirectoryScanner scanner = scan(fileset); 199 String[] files = scanner.getIncludedFiles(); // also performs exclusion. So way to go, maven. 200 201 // let's just have a single output file then. 202 FileOutputStream fos = null; 203 try { 204 YamlCombiner sc = new YamlCombiner(); 205 sc.setRelativeDir(new File(fileset.getDirectory())); 206 sc.setFiles(files); 207 sc.setLog(getLog()); 208 sc.setVerbose(verbose); 209 210 fos = new FileOutputStream(destFile); 211 if (filtering) { 212 MavenResourcesExecution mre = new MavenResourcesExecution(); 213 mre.setMavenProject( project ); 214 mre.setFileFilters( null ); 215 mre.setEscapeWindowsPaths( true ); 216 mre.setMavenSession( session ); 217 mre.setInjectProjectBuildFilters( true ); 218 219 // there's probably a better way of getting a plexus Logger instance 220 // but for the life of me I can't work it out 221 // after about 15 levels of indirection they end up not-properly wrapping slf4j anyway. 222 // ( see https://maven.apache.org/ref/3.6.0/maven-embedder/apidocs/org/apache/maven/cli/logging/Slf4jLoggerManager.html ) 223 ConsoleLoggerManager clm = new ConsoleLoggerManager(); 224 Logger logger = clm.getLoggerForComponent("YamlCombineMojo"); 225 226 DefaultMavenFileFilter dmff = new DefaultMavenFileFilter(buildContext); 227 // dmff.enableLogging(logger); // filter will NPE if this isn't set 228 getLog().info("logger is " + logger); 229 try { 230 List<FilterWrapper> filterWrappers = dmff.getDefaultFilterWrappers( mre ); 231 sc.setFilterWrappers(filterWrappers); 232 } catch (MavenFilteringException e) { 233 throw new IllegalStateException("Coult not get default filter wrappers", e); 234 } 235 } 236 PrintWriter w = new PrintWriter(new OutputStreamWriter(fos, outputFileCharset)); 237 sc.combine(w,inputFileSetCharset); 238 } catch (IOException ioe) { 239 // trouble at the mill 240 throw new MojoExecutionException("Could not create combined yaml file", ioe); 241 } finally { 242 if (fos != null) { 243 try { 244 fos.close(); 245 } catch(IOException ioe) { 246 throw new IllegalStateException("IOException closing file", ioe); 247 } 248 } 249 } 250 return destFile; 251 252 } 253 254 255 public void execute() 256 throws MojoExecutionException 257 { 258 createCombinedYaml(); 259 } 260 261 262 263 // getters / setters 264 265 /** 266 * {@inheritDoc} 267 */ 268 protected Boolean getFiltering() { 269 return filtering; 270 } 271 272 273 /** 274 * {@inheritDoc} 275 */ 276 protected Boolean getVerbose() { 277 return verbose; 278 } 279 280 /** 281 * @return the project 282 */ 283 public MavenProject getProject() { 284 return project; 285 } 286 287 /** 288 * @param project 289 * the project to set 290 */ 291 public void setProject(final MavenProject project) { 292 this.project = project; 293 } 294 295 /** 296 * @return the settings 297 */ 298 public Settings getSettings() { 299 return settings; 300 } 301 302 /** 303 * @param settings 304 * the settings to set 305 */ 306 public void setSettings(final Settings settings) { 307 this.settings = settings; 308 } 309 310}