View Javadoc
1   package org.apache.rat.mp;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import static org.apache.rat.mp.util.ConfigurationHelper.newInstance;
23  import static org.apache.rat.mp.util.ExclusionHelper.addEclipseDefaults;
24  import static org.apache.rat.mp.util.ExclusionHelper.addIdeaDefaults;
25  import static org.apache.rat.mp.util.ExclusionHelper.addMavenDefaults;
26  import static org.apache.rat.mp.util.ExclusionHelper.addPlexusAndScmDefaults;
27  
28  import java.io.BufferedInputStream;
29  import java.io.BufferedReader;
30  import java.io.File;
31  import java.io.FileInputStream;
32  import java.io.IOException;
33  import java.io.InputStream;
34  import java.io.InputStreamReader;
35  import java.io.PrintWriter;
36  import java.io.Reader;
37  import java.io.StringWriter;
38  import java.io.Writer;
39  import java.lang.reflect.UndeclaredThrowableException;
40  import java.util.ArrayList;
41  import java.util.Arrays;
42  import java.util.Collections;
43  import java.util.HashSet;
44  import java.util.List;
45  import java.util.Set;
46  
47  import javax.xml.transform.TransformerConfigurationException;
48  
49  
50  import org.apache.commons.io.IOUtils;
51  import org.apache.maven.plugin.AbstractMojo;
52  import org.apache.maven.plugin.MojoExecutionException;
53  import org.apache.maven.plugin.MojoFailureException;
54  import org.apache.maven.plugins.annotations.Parameter;
55  import org.apache.maven.project.MavenProject;
56  import org.apache.rat.Defaults;
57  import org.apache.rat.Report;
58  import org.apache.rat.ReportConfiguration;
59  import org.apache.rat.analysis.IHeaderMatcher;
60  import org.apache.rat.analysis.util.HeaderMatcherMultiplexer;
61  import org.apache.rat.api.RatException;
62  import org.apache.rat.config.SourceCodeManagementSystems;
63  import org.apache.rat.license.ILicenseFamily;
64  import org.apache.rat.mp.util.ScmIgnoreParser;
65  import org.apache.rat.report.IReportable;
66  import org.apache.rat.report.claim.ClaimStatistic;
67  import org.codehaus.plexus.util.DirectoryScanner;
68  
69  /**
70   * Abstract base class for Mojos, which are running Rat.
71   */
72  public abstract class AbstractRatMojo extends AbstractMojo {
73  
74      /**
75       * The base directory, in which to search for files.
76       */
77      @Parameter(property = "rat.basedir", defaultValue = "${basedir}", required = true)
78      private File basedir;
79  
80      /**
81       * Specifies the licenses to accept. Deprecated, use {@link #licenses}
82       * instead.
83       *
84       * @deprecated Use {@link #licenses} instead.
85       */
86      @Deprecated
87      @Parameter
88      private HeaderMatcherSpecification[] licenseMatchers;
89  
90      /**
91       * Specifies the licenses to accept. By default, these are added to the
92       * default licenses, unless you set {@link #addDefaultLicenseMatchers} to
93       * false.
94       *
95       * @since 0.8
96       */
97      @Parameter
98      private IHeaderMatcher[] licenses;
99  
100     /**
101      * The set of approved license family names.
102      *
103      * @deprecated Use {@link #licenseFamilies} instead.
104      */
105     @Deprecated
106     private LicenseFamilySpecification[] licenseFamilyNames;
107 
108     /**
109      * Specifies the license families to accept.
110      *
111      * @since 0.8
112      */
113     @Parameter
114     private ILicenseFamily[] licenseFamilies;
115 
116     /**
117      * Whether to add the default list of license matchers.
118      */
119     @Parameter(property = "rat.addDefaultLicenseMatchers", defaultValue = "true")
120     private boolean addDefaultLicenseMatchers;
121 
122     /**
123      * Specifies files, which are included in the report. By default, all files
124      * are included.
125      */
126     @Parameter
127     private String[] includes;
128 
129     /**
130      * Specifies a file, from which to read includes. Basically, an alternative to
131      * specifying the includes as a list.
132      */
133     @Parameter(property="rat.includesFile")
134     private String includesFile;
135 
136     /**
137      * Specifies the include files character set. Defaults to @code{${project.build.sourceEncoding}),
138      * or @code{UTF8}.
139      */
140     @Parameter(property="rat.includesFileCharset", defaultValue="${project.build.sourceEncoding}")
141     private String includesFileCharset;
142     
143     /**
144      * Specifies files, which are excluded in the report. By default, no files
145      * are excluded.
146      */
147     @Parameter
148     private String[] excludes;
149 
150     /**
151      * Specifies a file, from which to read excludes. Basically, an alternative to
152      * specifying the excludes as a list.  The excludesFile is assumed to be using the
153      * UFT8 character set.
154      */
155     @Parameter(property="rat.excludesFile")
156     private String excludesFile;
157 
158     /**
159      * Specifies the include files character set. Defaults to @code{${project.build.sourceEncoding}),
160      * or @code{UTF8}.
161      */
162     @Parameter(property="rat.excludesFileCharset", defaultValue="${project.build.sourceEncoding}")
163     private String excludesFileCharset;
164 
165     /**
166      * Whether to use the default excludes when scanning for files. The default
167      * excludes are:
168      * <ul>
169      * <li>meta data files for source code management / revision control systems,
170      * see {@link SourceCodeManagementSystems}</li>
171      * <li>temporary files used by Maven, see <a
172      * href="#useMavenDefaultExcludes">useMavenDefaultExcludes</a></li>
173      * <li>configuration files for Eclipse, see <a
174      * href="#useEclipseDefaultExcludes">useEclipseDefaultExcludes</a></li>
175      * <li>configuration files for IDEA, see <a
176      * href="#useIdeaDefaultExcludes">useIdeaDefaultExcludes</a></li>
177      * </ul>
178      */
179     @Parameter(property = "rat.useDefaultExcludes", defaultValue = "true")
180     private boolean useDefaultExcludes;
181 
182     /**
183      * Whether to use the Maven specific default excludes when scanning for
184      * files. Maven specific default excludes are given by the constant
185      * MAVEN_DEFAULT_EXCLUDES: The <code>target</code> directory, the
186      * <code>cobertura.ser</code> file, and so on.
187      */
188     @Parameter(property = "rat.useMavenDefaultExcludes", defaultValue = "true")
189     private boolean useMavenDefaultExcludes;
190 
191     /**
192      * Whether to parse source code management system (SCM) ignore files and use their contents as excludes.
193      * At the moment this works for the following SCMs:
194      *
195      * @see org.apache.rat.config.SourceCodeManagementSystems
196      */
197     @Parameter(property = "rat.parseSCMIgnoresAsExcludes", defaultValue = "true")
198     private boolean parseSCMIgnoresAsExcludes;
199 
200     /**
201      * Whether to use the Eclipse specific default excludes when scanning for
202      * files. Eclipse specific default excludes are given by the constant
203      * ECLIPSE_DEFAULT_EXCLUDES: The <code>.classpath</code> and
204      * <code>.project</code> files, the <code>.settings</code> directory, and so
205      * on.
206      */
207     @Parameter(property = "rat.useEclipseDefaultExcludes", defaultValue = "true")
208     private boolean useEclipseDefaultExcludes;
209 
210     /**
211      * Whether to use the IDEA specific default excludes when scanning for
212      * files. IDEA specific default excludes are given by the constant
213      * IDEA_DEFAULT_EXCLUDES: The <code>*.iml</code>, <code>*.ipr</code> and
214      * <code>*.iws</code> files and the <code>.idea</code> directory.
215      */
216     @Parameter(property = "rat.useIdeaDefaultExcludes", defaultValue = "true")
217     private boolean useIdeaDefaultExcludes;
218 
219     /**
220      * Whether to exclude subprojects. This is recommended, if you want a
221      * separate apache-rat-plugin report for each subproject.
222      */
223     @Parameter(property = "rat.excludeSubprojects", defaultValue = "true")
224     private boolean excludeSubProjects;
225 
226     /**
227      * Will skip the plugin execution, e.g. for technical builds that do not take license compliance into account.
228      *
229      * @since 0.11
230      */
231     @Parameter(property = "rat.skip", defaultValue = "false")
232     protected boolean skip;
233 
234     /**
235      * Holds the maven-internal project to allow resolution of artifact properties during mojo runs.
236      */
237     @Parameter(defaultValue = "${project}", required = true, readonly = true)
238     private MavenProject project;
239 
240     /**
241      * @return Returns the Maven project.
242      */
243     protected MavenProject getProject() {
244         return project;
245     }
246 
247     /**
248      * Returns the set of {@link IHeaderMatcher header matchers} to use.
249      *
250      * @return list of license matchers to use
251      * @throws MojoFailureException   An error in the plugin configuration was detected.
252      * @throws MojoExecutionException An error occurred while calculating the result.
253      */
254     private List<IHeaderMatcher> mergeLicenseMatchers()
255             throws MojoFailureException, MojoExecutionException {
256         List<IHeaderMatcher> matchers = new ArrayList<IHeaderMatcher>();
257 
258         if (licenses != null) {
259             matchers.addAll(Arrays.asList(licenses));
260             getLog().info("Added " + licenses.length + " additional default licenses.");
261         }
262 
263         if (licenseMatchers != null) {
264             for (final HeaderMatcherSpecification spec : licenseMatchers) {
265                 matchers.add(newInstance(IHeaderMatcher.class, spec.getClassName()));
266             }
267         }
268 
269         if (addDefaultLicenseMatchers) {
270             getLog().info("Enabled default license matchers.");
271             matchers.addAll(Defaults.DEFAULT_MATCHERS);
272         }
273         logLicenseMatchers(matchers);
274 
275         return matchers;
276     }
277 
278     private void logLicenseMatchers(List<IHeaderMatcher> matchers) {
279         if (getLog().isDebugEnabled()) {
280             getLog().debug("The following license matchers are activated:");
281             for (IHeaderMatcher matcher : matchers) {
282                 getLog().debug("* " + matcher.toString());
283             }
284         }
285 
286     }
287 
288     /**
289      * Creates an iterator over the files to check.
290      *
291      * @return A container of files, which are being checked.
292      * @throws MojoExecutionException in case of errors. I/O errors result in UndeclaredThrowableExceptions.
293      */
294     protected IReportable getResources() throws MojoExecutionException {
295         final DirectoryScanner ds = new DirectoryScanner();
296         ds.setBasedir(basedir);
297         setExcludes(ds);
298         setIncludes(ds);
299         ds.scan();
300         whenDebuggingLogExcludedFiles(ds);
301         final String[] files = ds.getIncludedFiles();
302         logAboutIncludedFiles(files);
303         try {
304             return new FilesReportable(basedir, files);
305         } catch (final IOException e) {
306             throw new UndeclaredThrowableException(e);
307         }
308     }
309 
310     private void logAboutIncludedFiles(final String[] files) {
311         if (files.length == 0) {
312             getLog().warn("No resources included.");
313         } else {
314             getLog().info(
315                     files.length
316                             + " resources included (use -debug for more details)");
317             if (getLog().isDebugEnabled()) {
318                 for (final String resource : files) {
319                     getLog().debug(" - included " + resource);
320                 }
321             }
322         }
323     }
324 
325     private void whenDebuggingLogExcludedFiles(final DirectoryScanner ds) {
326         if (getLog().isDebugEnabled()) {
327             final String[] excludedFiles = ds.getExcludedFiles();
328             if (excludedFiles.length == 0) {
329                 getLog().debug("No excluded resources.");
330             } else {
331                 getLog().debug(
332                         "Excluded " + excludedFiles.length + " resources:");
333                 for (final String resource : excludedFiles) {
334                     getLog().debug(" - excluded " + resource);
335                 }
336             }
337         }
338     }
339 
340     private void setIncludes(DirectoryScanner ds) throws MojoExecutionException {
341         if ((includes != null  &&  includes.length > 0)  ||  includesFile != null) {
342         	final List<String> includeList = new ArrayList<String>();
343         	if (includes != null) {
344         		includeList.addAll(Arrays.asList(includes));
345         	}
346         	if (includesFile != null) {
347         		final String charset = includesFileCharset == null ? "UTF8" : includesFileCharset;
348         		final File f = new File(includesFile);
349         		if (!f.isFile()) {
350         			getLog().error("IncludesFile not found: " + f.getAbsolutePath());
351         		} else {
352         			getLog().info("Includes loaded from file " + includesFile + ", using character set " + charset);
353         		}
354         		includeList.addAll(getPatternsFromFile(f, charset));
355         	}
356             ds.setIncludes(includeList.toArray(new String[includeList.size()]));
357         }
358     }
359 
360     private List<String> getPatternsFromFile(File pFile, String pCharset) throws MojoExecutionException {
361     	InputStream is = null;
362     	BufferedInputStream bis = null;
363     	Reader r = null;
364     	BufferedReader br = null;
365     	Throwable th = null;
366     	final List<String> patterns = new ArrayList<String>();
367     	try {
368     		is = new FileInputStream(pFile);
369     		bis = new BufferedInputStream(is);
370     		r = new InputStreamReader(bis, pCharset);
371     		br = new BufferedReader(r);
372     		for (;;) {
373     			final String s = br.readLine();
374     			if (s == null) {
375     				break;
376     			}
377     			patterns.add(s);
378     		}
379     		br.close();
380     		br = null;
381     		r.close();
382     		r = null;
383     		bis.close();
384     		bis = null;
385     		is.close();
386     		is = null;
387     	} catch (Throwable t) {
388     		th = t;
389     	} finally {
390     		if (br != null) { try { br.close(); } catch (Throwable t) { if (th == null) { th = t; } } }
391     		if (r != null) { try { r.close(); } catch (Throwable t) { if (th == null) { th = t; } } }
392     		if (bis != null) { try { bis.close(); } catch (Throwable t) { if (th == null) { th = t; } } }
393     		if (is != null) { try { is.close(); } catch (Throwable t) { if (th == null) { th = t; } } }
394     	}
395     	if (th != null) {
396     		if (th instanceof RuntimeException) { throw (RuntimeException) th; }
397     		if (th instanceof Error) { throw (Error) th; }
398     		throw new MojoExecutionException(th.getMessage(), th);
399     	}
400     	return patterns;
401     }
402     
403     private void setExcludes(DirectoryScanner ds) throws MojoExecutionException {
404         final List<String> excludeList = mergeDefaultExclusions();
405         if (excludes == null || excludes.length == 0) {
406             getLog().info("No excludes explicitly specified.");
407         } else {
408         	for (final String exclude : excludes) {
409                 getLog().info("Exclude: " + exclude);
410             }
411         }
412         if (excludes != null) {
413             Collections.addAll(excludeList, excludes);
414         }
415         if (!excludeList.isEmpty()) {
416             final String[] allExcludes = excludeList.toArray(new String[excludeList
417                     .size()]);
418             ds.setExcludes(allExcludes);
419         }
420     }
421 
422     private List<String> mergeDefaultExclusions() throws MojoExecutionException {
423         final Set<String> results = new HashSet<String>();
424 
425         addPlexusAndScmDefaults(getLog(), useDefaultExcludes, results);
426         addMavenDefaults(getLog(), useMavenDefaultExcludes, results);
427         addEclipseDefaults(getLog(), useEclipseDefaultExcludes, results);
428         addIdeaDefaults(getLog(), useIdeaDefaultExcludes, results);
429 
430         if (parseSCMIgnoresAsExcludes) {
431             getLog().info("Will parse SCM ignores for exclusions...");
432             results.addAll(ScmIgnoreParser.getExclusionsFromSCM(getLog(), project.getBasedir()));
433             getLog().info("Finished adding exclusions from SCM ignore files.");
434         }
435 
436         if (excludeSubProjects && project != null
437                 && project.getModules() != null) {
438             for (final Object o : project.getModules()) {
439                 final String moduleSubPath = (String) o;
440                 results.add(moduleSubPath + "/**/*");
441             }
442         }
443 
444         getLog().debug("Finished creating list of implicit excludes.");
445         if (results.isEmpty()) {
446             getLog().info("No excludes implicitly specified.");
447         } else {
448             getLog().info(
449                     results.size()
450                             + " implicit excludes (use -debug for more details).");
451             for (final String exclude : results) {
452                 getLog().debug("Implicit exclude: " + exclude);
453             }
454         }
455         if (excludesFile != null) {
456         	final File f = new File(excludesFile);
457         	if (!f.isFile()) {
458         		getLog().error("Excludes file not found: " + f.getAbsolutePath());
459         	}
460         	if (!f.canRead()) {
461         		getLog().error("Excludes file not readable: " + f.getAbsolutePath());
462         	}
463         	final String charset = excludesFileCharset == null ? "UTF8" : excludesFileCharset;
464         	getLog().info("Loading excludes from file " + f + ", using character set " + charset);
465         	results.addAll(getPatternsFromFile(f, charset));
466         }
467 
468         return new ArrayList<String>(results);
469     }
470 
471     /**
472      * Creates the report as a string.
473      *
474      * @param styleSheet The style sheet to use when formatting the report
475      * @return Report contents
476      * @throws MojoFailureException   An error in the plugin configuration was detected.
477      * @throws MojoExecutionException An error occurred while creating the report.
478      */
479     protected String createReport(InputStream styleSheet)
480             throws MojoExecutionException, MojoFailureException {
481         StringWriter sw = new StringWriter();
482         PrintWriter pw = null;
483         try {
484             pw = new PrintWriter(sw);
485             createReport(new PrintWriter(sw), styleSheet);
486             final String result = sw.toString();
487             pw.close();
488             pw = null;
489             sw.close();
490             sw = null;
491             return result;
492         } catch (IOException e) {
493             throw new MojoExecutionException(e.getMessage(), e);
494         } finally {
495             IOUtils.closeQuietly(pw);
496             IOUtils.closeQuietly(sw);
497         }
498     }
499 
500     /**
501      * Writes the report to the given stream.
502      *
503      * @param out   The target writer, to which the report is being written.
504      * @param style The stylesheet to use, or <code>null</code> for raw XML
505      * @return the current statistic.
506      * @throws MojoFailureException   An error in the plugin configuration was detected.
507      * @throws MojoExecutionException Another error occurred while creating the report.
508      */
509     protected ClaimStatistic createReport(Writer out, InputStream style)
510             throws MojoExecutionException, MojoFailureException {
511         final ReportConfiguration configuration = getConfiguration();
512         try {
513             if (style != null) {
514                 return Report.report(out, getResources(), style, configuration);
515             } else {
516                 return Report.report(getResources(), out, configuration);
517             }
518         } catch (final TransformerConfigurationException e) {
519             throw new MojoExecutionException(e.getMessage(), e);
520         } catch (final IOException e) {
521             throw new MojoExecutionException(e.getMessage(), e);
522         } catch (final InterruptedException e) {
523             throw new MojoExecutionException(e.getMessage(), e);
524         } catch (final RatException e) {
525             throw new MojoExecutionException(e.getMessage(), e);
526         }
527     }
528 
529     protected ReportConfiguration getConfiguration()
530             throws MojoFailureException, MojoExecutionException {
531         final ReportConfiguration configuration = new ReportConfiguration();
532         configuration.setHeaderMatcher(new HeaderMatcherMultiplexer(
533                 mergeLicenseMatchers()));
534         configuration.setApprovedLicenseNames(mergeApprovedLicenseNames());
535         configuration.setApproveDefaultLicenses(addDefaultLicenseMatchers);
536         return configuration;
537     }
538 
539     private List<ILicenseFamily> mergeApprovedLicenseNames()
540             throws MojoExecutionException, MojoFailureException {
541         final List<ILicenseFamily> list = new ArrayList<ILicenseFamily>();
542         if (licenseFamilies != null) {
543             getLog().info("Added " + licenseFamilies.length + " custom approved licenses.");
544             list.addAll(Arrays.asList(licenseFamilies));
545         }
546         if (licenseFamilyNames != null) {
547             for (final LicenseFamilySpecification spec : licenseFamilyNames) {
548                 list.add(newInstance(ILicenseFamily.class, spec.getClassName()));
549             }
550         }
551         return list;
552     }
553 }