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 java.io.File;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.io.Writer;
26  import java.lang.reflect.UndeclaredThrowableException;
27  import java.util.ArrayList;
28  import java.util.Arrays;
29  import java.util.Collections;
30  import java.util.List;
31  
32  import javax.xml.transform.TransformerConfigurationException;
33  
34  import org.apache.maven.plugin.AbstractMojo;
35  import org.apache.maven.plugin.MojoExecutionException;
36  import org.apache.maven.plugin.MojoFailureException;
37  import org.apache.maven.plugins.annotations.Parameter;
38  import org.apache.maven.project.MavenProject;
39  import org.apache.rat.Defaults;
40  import org.apache.rat.Report;
41  import org.apache.rat.ReportConfiguration;
42  import org.apache.rat.analysis.IHeaderMatcher;
43  import org.apache.rat.analysis.util.HeaderMatcherMultiplexer;
44  import org.apache.rat.api.RatException;
45  import org.apache.rat.license.ILicenseFamily;
46  import org.apache.rat.report.IReportable;
47  import org.apache.rat.report.claim.ClaimStatistic;
48  import org.codehaus.plexus.util.DirectoryScanner;
49  
50  /**
51   * Abstract base class for Mojos, which are running Rat.
52   */
53  public abstract class AbstractRatMojo extends AbstractMojo {
54      /**
55       * The Maven specific default excludes.
56       */
57      static final List<String> MAVEN_DEFAULT_EXCLUDES = Collections
58              .unmodifiableList(Arrays.asList("target/**/*", //
59                      "cobertura.ser", //
60                      "release.properties", //
61                      "pom.xml.releaseBackup"));
62  
63      /**
64       * The Eclipse specific default excludes.
65       */
66      static final List<String> ECLIPSE_DEFAULT_EXCLUDES = Collections
67              .unmodifiableList(Arrays.asList(".classpath",//
68                      ".project", //
69                      ".settings/**/*"));
70  
71      /**
72       * The IDEA specific default excludes.
73       */
74      static final List<String> IDEA_DEFAULT_EXCLUDES = Collections
75              .unmodifiableList(Arrays.asList(//
76                      "*.iml", //
77                      "*.ipr", //
78                      "*.iws", //
79                      ".idea/**/*"));
80  
81      /**
82       * The base directory, in which to search for files.
83       *
84       */
85      @Parameter(property = "rat.basedir", defaultValue = "${basedir}", required = true)
86      private File basedir;
87  
88      /**
89       * Specifies the licenses to accept. Deprecated, use {@link #licenses}
90       * instead.
91       *
92       * @deprecated Use {@link #licenses} instead.
93       */
94      @Deprecated
95      @Parameter
96      private HeaderMatcherSpecification[] licenseMatchers;
97  
98      /**
99       * Specifies the licenses to accept. By default, these are added to the
100      * default licenses, unless you set {@link #addDefaultLicenseMatchers} to
101      * true.
102      *
103      * @since 0.8
104      */
105     @Parameter
106     private IHeaderMatcher[] licenses;
107 
108     /**
109      * The set of approved license family names.
110      *
111      * @deprecated Use {@link #licenseFamilies} instead.
112      */
113     @Deprecated
114     private LicenseFamilySpecification[] licenseFamilyNames;
115 
116     /**
117      * Specifies the license families to accept.
118      *
119      * @since 0.8
120      */
121     @Parameter
122     private ILicenseFamily[] licenseFamilies;
123 
124     /**
125      * Whether to add the default list of license matchers.
126      *
127      */
128     @Parameter(property = "rat.addDefaultLicenseMatchers", defaultValue = "true")
129     private boolean addDefaultLicenseMatchers;
130 
131     /**
132      * Specifies files, which are included in the report. By default, all files
133      * are included.
134      *
135      */
136     @Parameter
137     private String[] includes;
138 
139     /**
140      * Specifies files, which are excluded in the report. By default, no files
141      * are excluded.
142      *
143      */
144     @Parameter
145     private String[] excludes;
146 
147     /**
148      * Whether to use the default excludes when scanning for files. The default
149      * excludes are:
150      * <ul>
151      * <li>meta data files for version control systems</li>
152      * <li>temporary files used by Maven, see <a
153      * href="#useMavenDefaultExcludes">useMavenDefaultExcludes</a></li>
154      * <li>configuration files for Eclipse, see <a
155      * href="#useEclipseDefaultExcludes">useEclipseDefaultExcludes</a></li>
156      * <li>configuration files for IDEA, see <a
157      * href="#useIdeaDefaultExcludes">useIdeaDefaultExcludes</a></li>
158      * </ul>
159      */
160     @Parameter(property = "rat.useDefaultExcludes", defaultValue = "true")
161     private boolean useDefaultExcludes;
162 
163     /**
164      * Whether to use the Maven specific default excludes when scanning for
165      * files. Maven specific default excludes are given by the constant
166      * MAVEN_DEFAULT_EXCLUDES: The <code>target</code> directory, the
167      * <code>cobertura.ser</code> file, and so on.
168      */
169     @Parameter(property = "rat.useMavenDefaultExcludes", defaultValue = "true")
170     private boolean useMavenDefaultExcludes;
171 
172     /**
173      * Whether to use the Eclipse specific default excludes when scanning for
174      * files. Eclipse specific default excludes are given by the constant
175      * ECLIPSE_DEFAULT_EXCLUDES: The <code>.classpath</code> and
176      * <code>.project</code> files, the <code>.settings</code> directory, and so
177      * on.
178      */
179     @Parameter(property = "rat.useEclipseDefaultExcludes", defaultValue = "true")
180     private boolean useEclipseDefaultExcludes;
181 
182     /**
183      * Whether to use the IDEA specific default excludes when scanning for
184      * files. IDEA specific default excludes are given by the constant
185      * IDEA_DEFAULT_EXCLUDES: The <code>*.iml</code>, <code>*.ipr</code> and
186      * <code>*.iws</code> files and the <code>.idea</code> directory.
187      */
188     @Parameter(property = "rat.useIdeaDefaultExcludes", defaultValue = "true")
189     private boolean useIdeaDefaultExcludes;
190 
191     /**
192      * Whether to exclude subprojects. This is recommended, if you want a
193      * separate apache-rat-plugin report for each subproject.
194      *
195      */
196     @Parameter(property = "rat.excludeSubprojects", defaultValue = "true")
197     private boolean excludeSubProjects;
198 
199     /**
200     * Holds the maven-internal project to allow resolution of artifact properties during mojo runs.
201     */
202     @Parameter(defaultValue = "${project}", required = true, readonly = true)
203     private MavenProject project;
204 
205     /**
206      * @return Returns the Maven project.
207      */
208     protected MavenProject getProject() {
209         return project;
210     }
211 
212     /**
213      * Returns the set of {@link IHeaderMatcher header matchers} to use.
214      *
215      * @throws MojoFailureException
216      *             An error in the plugin configuration was detected.
217      * @throws MojoExecutionException
218      *             An error occurred while calculating the result.
219      * @return Array of license matchers to use
220      */
221     protected IHeaderMatcher[] getLicenseMatchers()
222             throws MojoFailureException, MojoExecutionException {
223         final List<IHeaderMatcher> list = new ArrayList<IHeaderMatcher>();
224         if (licenses != null) {
225             list.addAll(Arrays.asList(licenses));
226         }
227 
228         if (licenseMatchers != null) {
229             for (final HeaderMatcherSpecification spec : licenseMatchers) {
230                 final String className = spec.getClassName();
231                 final IHeaderMatcher headerMatcher = newInstance(
232                         IHeaderMatcher.class, className);
233                 list.add(headerMatcher);
234             }
235         }
236 
237         if (addDefaultLicenseMatchers) {
238             list.addAll(Arrays.asList(Defaults.DEFAULT_MATCHERS));
239         }
240         return list.toArray(new IHeaderMatcher[list.size()]);
241     }
242 
243     private <T> T newInstance(final Class<T> clazz, final String className)
244             throws MojoExecutionException, MojoFailureException {
245         try {
246             final ClassLoader cl = Thread.currentThread()
247                     .getContextClassLoader();
248             @SuppressWarnings("unchecked") // incorrect cast will be caught below
249             final T o = (T) cl.loadClass(className).newInstance();
250 
251             if (!clazz.isAssignableFrom(o.getClass())) {
252                 throw new MojoFailureException("The class "
253                         + o.getClass().getName() + " does not implement "
254                         + clazz.getName());
255             }
256             return o;
257         } catch (InstantiationException e) {
258             throw new MojoExecutionException("Failed to instantiate class "
259                     + className + ": " + e.getMessage(), e);
260         } catch (ClassCastException e) {
261             throw new MojoExecutionException("The class " + className
262                     + " is not implementing " + clazz.getName() + ": "
263                     + e.getMessage(), e);
264         } catch (IllegalAccessException e) {
265             throw new MojoExecutionException("Illegal access to class "
266                     + className + ": " + e.getMessage(), e);
267         } catch (ClassNotFoundException e) {
268             throw new MojoExecutionException("Class " + className
269                     + " not found: " + e.getMessage(), e);
270         }
271     }
272 
273     /**
274      * Adds the given string array to the list.
275      *
276      * @param pList
277      *            The list to which the array elements are being added.
278      * @param pArray
279      *            The strings to add to the list.
280      */
281     private static void add(List<String> pList, String[] pArray) {
282         if (pArray != null) {
283             Collections.addAll(pList, pArray);
284         }
285     }
286 
287     /**
288      * Creates an iterator over the files to check.
289      *
290      * @return A container of files, which are being checked.
291      */
292     protected IReportable getResources() {
293         final DirectoryScanner ds = new DirectoryScanner();
294         ds.setBasedir(basedir);
295         setExcludes(ds);
296         setIncludes(ds);
297         ds.scan();
298         whenDebuggingLogExcludedFiles(ds);
299         final String[] files = ds.getIncludedFiles();
300         logAboutIncludedFiles(files);
301         try {
302             return new FilesReportable(basedir, files);
303         } catch (IOException e) {
304             throw new UndeclaredThrowableException(e);
305         }
306     }
307 
308     private void logAboutIncludedFiles(final String[] files) {
309         if (files.length == 0) {
310             getLog().warn("No resources included.");
311         } else {
312             getLog().info(
313                     files.length
314                             + " resources included (use -debug for more details)");
315             if (getLog().isDebugEnabled()) {
316                 for (String resource : files) {
317                     getLog().debug(" - included " + resource);
318                 }
319             }
320         }
321     }
322 
323     private void whenDebuggingLogExcludedFiles(final DirectoryScanner ds) {
324         if (getLog().isDebugEnabled()) {
325             final String[] excludedFiles = ds.getExcludedFiles();
326             if (excludedFiles.length == 0) {
327                 getLog().debug("No excluded resources.");
328             } else {
329                 getLog().debug(
330                         "Excluded " + excludedFiles.length + " resources:");
331                 for (final String resource : excludedFiles) {
332                     getLog().debug(" - excluded " + resource);
333                 }
334             }
335         }
336     }
337 
338     private void setIncludes(DirectoryScanner ds) {
339         if (includes != null) {
340             ds.setIncludes(includes);
341         }
342     }
343 
344     private void setExcludes(DirectoryScanner ds) {
345         final List<String> excludeList = buildDefaultExclusions();
346         if (excludes == null || excludes.length == 0) {
347             getLog().info("No excludes explicitly specified.");
348         } else {
349             for (String exclude : excludes) {
350                 getLog().info("Exclude: " + exclude);
351             }
352         }
353         add(excludeList, excludes);
354         if (!excludeList.isEmpty()) {
355             String[] allExcludes = excludeList.toArray(new String[excludeList
356                     .size()]);
357             ds.setExcludes(allExcludes);
358         }
359     }
360 
361     private List<String> buildDefaultExclusions() {
362         final List<String> results = new ArrayList<String>();
363 
364         addPlexusDefaults(results);
365 
366         addMavenDefaults(results);
367 
368         addEclipseDefaults(results);
369 
370         addIdeaDefaults(results);
371 
372         if (excludeSubProjects && project != null
373                 && project.getModules() != null) {
374             for (Object o : project.getModules()) {
375                 String moduleSubPath = (String) o;
376                 results.add(moduleSubPath + "/**/*");
377             }
378         }
379 
380         getLog().debug("Finished creating list of implicit excludes.");
381         if (results.isEmpty()) {
382             getLog().info("No excludes implicitly specified.");
383         } else {
384             getLog().info(
385                     results.size()
386                             + " implicit excludes (use -debug for more details).");
387             for (final String exclude : results) {
388                 getLog().debug("Implicit exclude: " + exclude);
389             }
390         }
391 
392         return results;
393     }
394 
395     private void addPlexusDefaults(final List<String> excludeList1) {
396         if (useDefaultExcludes) {
397             getLog().debug("Adding plexus default exclusions...");
398             Collections.addAll(excludeList1, DirectoryScanner.DEFAULTEXCLUDES);
399         } else {
400             getLog().debug(
401                     "rat.useDefaultExcludes set to false. "
402                             + "Plexus default exclusions will not be added");
403         }
404     }
405 
406     private void addMavenDefaults(final List<String> excludeList1) {
407         if (useMavenDefaultExcludes) {
408             getLog().debug(
409                     "Adding exclusions often needed by Maven projects...");
410             excludeList1.addAll(MAVEN_DEFAULT_EXCLUDES);
411         } else {
412             getLog().debug(
413                     "rat.useMavenDefaultExcludes set to false. "
414                             + "Exclusions often needed by Maven projects will not be added.");
415         }
416     }
417 
418     private void addEclipseDefaults(final List<String> excludeList1) {
419         if (useEclipseDefaultExcludes) {
420             getLog().debug(
421                     "Adding exclusions often needed by projects "
422                             + "developed in Eclipse...");
423             excludeList1.addAll(ECLIPSE_DEFAULT_EXCLUDES);
424         } else {
425             getLog().debug(
426                     "rat.useEclipseDefaultExcludes set to false. "
427                             + "Exclusions often needed by projects developed in "
428                             + "Eclipse will not be added.");
429         }
430     }
431 
432     private void addIdeaDefaults(final List<String> excludeList1) {
433         if (useIdeaDefaultExcludes) {
434             getLog().debug(
435                     "Adding exclusions often needed by projects "
436                             + "developed in IDEA...");
437             excludeList1.addAll(IDEA_DEFAULT_EXCLUDES);
438         } else {
439             getLog().debug(
440                     "rat.useIdeaDefaultExcludes set to false. "
441                             + "Exclusions often needed by projects developed in "
442                             + "IDEA will not be added.");
443         }
444     }
445 
446     /**
447      * Writes the report to the given stream.
448      *
449      * @param out
450      *            The target writer, to which the report is being written.
451      * @param style
452      *            The stylesheet to use, or <code>null</code> for raw XML
453      *
454      * @return the current statistic.
455      *
456      * @throws MojoFailureException
457      *             An error in the plugin configuration was detected.
458      * @throws MojoExecutionException
459      *             Another error occurred while creating the report.
460      */
461     protected ClaimStatistic createReport(Writer out, InputStream style)
462             throws MojoExecutionException, MojoFailureException {
463         final ReportConfiguration configuration = getConfiguration();
464         try {
465             if (style != null) {
466                 return Report.report(out, getResources(), style, configuration);
467             } else {
468                 return Report.report(getResources(), out, configuration);
469             }
470         } catch (TransformerConfigurationException e) {
471             throw new MojoExecutionException(e.getMessage(), e);
472         } catch (IOException e) {
473             throw new MojoExecutionException(e.getMessage(), e);
474         } catch (InterruptedException e) {
475             throw new MojoExecutionException(e.getMessage(), e);
476         } catch (RatException e) {
477             throw new MojoExecutionException(e.getMessage(), e);
478         }
479     }
480 
481     protected ReportConfiguration getConfiguration()
482             throws MojoFailureException, MojoExecutionException {
483         final ReportConfiguration configuration = new ReportConfiguration();
484         configuration.setHeaderMatcher(new HeaderMatcherMultiplexer(
485                 getLicenseMatchers()));
486         configuration.setApprovedLicenseNames(getApprovedLicenseNames());
487         return configuration;
488     }
489 
490     private ILicenseFamily[] getApprovedLicenseNames()
491             throws MojoExecutionException, MojoFailureException {
492         final List<ILicenseFamily> list = new ArrayList<ILicenseFamily>();
493         if (licenseFamilies != null) {
494             list.addAll(Arrays.asList(licenseFamilies));
495         }
496         if (licenseFamilyNames != null) {
497             for (LicenseFamilySpecification spec : licenseFamilyNames) {
498                 list.add(newInstance(ILicenseFamily.class, spec.getClassName()));
499             }
500         }
501 
502         if (list.isEmpty()) {
503             return null;
504         }
505         return list.toArray(new ILicenseFamily[list.size()]);
506     }
507 }