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}