View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.rat.mp;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.io.PrintWriter;
24  import java.net.URI;
25  import java.util.ArrayList;
26  import java.util.Arrays;
27  import java.util.Collection;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.Objects;
31  import java.util.SortedSet;
32  import java.util.function.Consumer;
33  import java.util.stream.Collectors;
34  import java.util.stream.Stream;
35  
36  import org.apache.commons.cli.Option;
37  import org.apache.commons.lang3.StringUtils;
38  import org.apache.maven.plugin.MojoExecutionException;
39  import org.apache.maven.plugins.annotations.Parameter;
40  import org.apache.maven.project.MavenProject;
41  import org.apache.rat.Defaults;
42  import org.apache.rat.OptionCollection;
43  import org.apache.rat.ReportConfiguration;
44  import org.apache.rat.analysis.license.DeprecatedConfig;
45  import org.apache.rat.commandline.Arg;
46  import org.apache.rat.config.exclusion.StandardCollection;
47  import org.apache.rat.configuration.Format;
48  import org.apache.rat.configuration.LicenseReader;
49  import org.apache.rat.configuration.MatcherReader;
50  import org.apache.rat.document.DocumentName;
51  import org.apache.rat.document.FileDocument;
52  import org.apache.rat.license.ILicense;
53  import org.apache.rat.license.ILicenseFamily;
54  import org.apache.rat.license.LicenseSetFactory.LicenseFilter;
55  import org.apache.rat.license.SimpleLicenseFamily;
56  import org.apache.rat.plugin.BaseRatMojo;
57  import org.apache.rat.utils.DefaultLog;
58  import org.apache.rat.utils.Log;
59  import org.apache.rat.walker.DirectoryWalker;
60  
61  import static java.lang.String.format;
62  
63  /**
64   * Abstract base class for Mojos, which are running Rat.
65   */
66  public abstract class AbstractRatMojo extends BaseRatMojo {
67      /** Report configuration for report */
68      private ReportConfiguration reportConfiguration;
69      /**
70       * The base directory, in which to search for files.
71       */
72      @Parameter(property = "rat.basedir", defaultValue = "${basedir}", required = true)
73      private File basedir;
74  
75      /**
76       * Specifies the licenses to accept. By default, these are added to the default
77       * licenses, unless you set <addDefaultLicenseMatchers> to false. Arguments should be
78       * file name of <Configs> file structure.
79       * @deprecated Use specific configuration under <configuration>.
80       * @since 0.8
81       */
82      @Parameter
83      @Deprecated
84      private String[] defaultLicenseFiles;
85  
86      /**
87       * Specifies the additional licenses file.
88       * @deprecated Use specific configuration under <configuration>.
89       */
90      @Parameter
91      @Deprecated
92      private String[] additionalLicenseFiles;
93  
94      /**
95       * Whether to add the default list of licenses.
96       * @deprecated Deprecated for removal since 0.17: Use <configurationNoDefaults> instead (note the change of state).
97       */
98      @Deprecated
99      @Parameter(property = "rat.addDefaultLicenses", name = "addDefaultLicenses")
100     public void setAddDefaultLicenses(final boolean addDefaultLicenses) {
101         setNoDefaultLicenses(!addDefaultLicenses);
102     }
103 
104     /**
105      * Whether to add the default list of license matchers.
106      * @deprecated Use specific configuration under <configuration>.
107      */
108     @Deprecated
109     @Parameter(property = "rat.addDefaultLicenseMatchers")
110     private boolean addDefaultLicenseMatchers;
111 
112     /** The list of approved licenses
113      * @deprecated Use specific configuration under <configuration>.
114      */
115     @Deprecated
116     @Parameter
117     private String[] approvedLicenses;
118 
119     /** The file of approved licenses
120      * @deprecated Use specific configuration under <configuration>.
121      */
122     @Deprecated
123     @Parameter(property = "rat.approvedFile")
124     private String approvedLicenseFile;
125 
126     /**
127      * Specifies the license families to accept.
128      *
129      * @since 0.8
130      * @deprecated Use LicenseFamily section of configuration file.
131      */
132     @Deprecated
133     @Parameter
134     private SimpleLicenseFamily[] licenseFamilies;
135 
136     /** The list of license definitions.
137      * @deprecated Deprecated for removal since 0.17: Use specific configuration under <configuration>. See configuration file documentation.
138      */
139     @Deprecated
140     @Parameter
141     private Object[] licenses;
142 
143     /** The list of family definitions.
144      * @deprecated Use specific configuration under <configuration>.
145      */
146     @Deprecated
147     @Parameter
148     private Family[] families;
149 
150     /**
151      * Specifies the include files character set.
152      * If ${project.build.sourceEncoding} is not set defaults to UTF-8.
153      */
154     @Parameter(property = "rat.includesFileCharset", defaultValue = "${project.build.sourceEncoding}")
155     private String includesFileCharset;
156 
157     /**
158      * Specifies the include files character set.
159      * If ${project.build.sourceEncoding} is not set defaults to UTF-8.
160      */
161     @Parameter(property = "rat.excludesFileCharset", defaultValue = "${project.build.sourceEncoding}")
162     private String excludesFileCharset;
163 
164     /**
165      * Whether to use the default excludes when scanning for files. The default
166      * excludes are:
167      * <ul>
168      * <li>meta data files for source code management / revision control systems,
169      * see {@link org.apache.rat.config.exclusion.StandardCollection}</li>
170      * <li>temporary files used by Maven, see
171      * <a href="#useMavenDefaultExcludes">useMavenDefaultExcludes</a></li>
172      * <li>configuration files for Eclipse, see
173      * <a href="#useEclipseDefaultExcludes">useEclipseDefaultExcludes</a></li>
174      * <li>configuration files for IDEA, see
175      * <a href="#useIdeaDefaultExcludes">useIdeaDefaultExcludes</a></li>
176      * </ul>
177      * @deprecated When set to true specifies that the STANDARD_PATTERNS are excluded, as are
178      * the STANDARD_SCMS patterns. Use the various InputExclude and InputInclude elements to
179      * explicitly specify what to include or exclude.
180      */
181     @Parameter(property = "rat.useDefaultExcludes", defaultValue = "true")
182     @Deprecated
183     private boolean useDefaultExcludes;
184 
185     /**
186      * Whether to use the Maven specific default excludes when scanning for files.
187      * Maven specific default excludes are given by the constant
188      * MAVEN_DEFAULT_EXCLUDES: The <code>target</code> directory, the
189      * <code>cobertura.ser</code> file, and so on.
190      * @deprecated When set to true specifies that the MAVEN patterns are excluded.
191      * Use "inputIncludeStd MAVEN" to override.
192      */
193     @Parameter(property = "rat.useMavenDefaultExcludes", defaultValue = "true")
194     @Deprecated
195     private boolean useMavenDefaultExcludes;
196 
197     /**
198      * Whether to parse source code management system (SCM) ignore files and use
199      * their contents as excludes. At the moment this works for the following SCMs:
200      *
201      * @see org.apache.rat.config.exclusion.StandardCollection
202      * @deprecated When set to true specifies that the STANDARD_SCMS exclusion file
203      * processors are used to exclude files and directories (e.g. ".gitignore" or ".hgignore").
204      * Use "inputIncludeStd STANDARD_SCMS" to override.
205      */
206     @Parameter(property = "rat.parseSCMIgnoresAsExcludes", defaultValue = "true")
207     @Deprecated
208     private boolean parseSCMIgnoresAsExcludes;
209 
210     /**
211      * Whether to use the Eclipse specific default excludes when scanning for files.
212      * Eclipse specific default excludes are given by the constant
213      * ECLIPSE_DEFAULT_EXCLUDES: The <code>.classpath</code> and
214      * <code>.project</code> files, the <code>.settings</code> directory, and so on.
215      * @deprecated When set to true specifies that the ECLIPSE patterns are excluded.
216      * Use "inputIncludeStd ECLIPSE" to override.
217      */
218     @Parameter(property = "rat.useEclipseDefaultExcludes", defaultValue = "true")
219     @Deprecated
220     private boolean useEclipseDefaultExcludes;
221 
222     /**
223      * Whether to use the IDEA specific default excludes when scanning for files.
224      * IDEA specific default excludes are given by the constant
225      * IDEA_DEFAULT_EXCLUDES: The <code>*.iml</code>, <code>*.ipr</code> and
226      * <code>*.iws</code> files and the <code>.idea</code> directory.
227      * @deprecated When set to true specifies that the IDEA patterns are excluded.
228      * Use "inputIncludeStd IDEA" to override.
229      */
230     @Deprecated
231     @Parameter(property = "rat.useIdeaDefaultExcludes", defaultValue = "true")
232     private boolean useIdeaDefaultExcludes;
233 
234     /**
235      * Whether to exclude subprojects. This is recommended, if you want a separate
236      * apache-rat-plugin report for each subproject.
237      */
238     @Parameter(property = "rat.excludeSubprojects", defaultValue = "true")
239     private boolean excludeSubProjects;
240 
241     /**
242      * Will skip the plugin execution, e.g. for technical builds that do not take
243      * license compliance into account.
244      *
245      * @since 0.11
246      */
247     @Parameter(property = "rat.skip", defaultValue = "false")
248     protected boolean skip;
249 
250     /**
251      * Holds the maven-internal project to allow resolution of artifact properties
252      * during mojo runs.
253      */
254     @Parameter(defaultValue = "${project}", required = true, readonly = true)
255     protected MavenProject project;
256 
257     protected AbstractRatMojo() {
258         DefaultLog.setInstance(makeLog());
259     }
260 
261     /**
262      * @return the Maven project.
263      */
264     protected MavenProject getProject() {
265         return project;
266     }
267 
268     protected Defaults.Builder getDefaultsBuilder() {
269         Defaults.Builder result = Defaults.builder();
270         if (defaultLicenseFiles != null) {
271             for (String defaultLicenseFile : defaultLicenseFiles) {
272                 result.add(defaultLicenseFile);
273             }
274         }
275         return result;
276     }
277 
278     @Deprecated // remove this for version 1.0
279     private Stream<License> getLicenses() {
280         if (licenses == null) {
281             return Stream.empty();
282         }
283         return Arrays.stream(licenses).filter(s -> s instanceof License).map(License.class::cast);
284     }
285 
286     @Deprecated // remove this for version 1.0
287     private Stream<DeprecatedConfig> getDeprecatedConfigs() {
288         if (licenses == null) {
289             return Stream.empty();
290         }
291         return Arrays.stream(licenses).filter(s -> s instanceof DeprecatedConfig).map(DeprecatedConfig.class::cast);
292     }
293 
294     @Deprecated // remove this for version 1.0
295     private void reportDeprecatedProcessing() {
296         if (getDeprecatedConfigs().findAny().isPresent()) {
297             DefaultLog.getInstance().warn("Configuration uses deprecated configuration. You need to upgrade to v0.17 configuration options.");
298         }
299     }
300 
301     @Deprecated // remove this for version 1.0
302     private void processLicenseFamilies(final ReportConfiguration config) {
303         List<ILicenseFamily> families = getDeprecatedConfigs().map(DeprecatedConfig::getLicenseFamily).filter(Objects::nonNull).collect(Collectors.toList());
304         if (licenseFamilies != null) {
305             for (SimpleLicenseFamily slf : licenseFamilies) {
306                 if (StringUtils.isBlank(slf.getFamilyCategory())) {
307                     families.stream().filter(f -> f.getFamilyName().equalsIgnoreCase(slf.getFamilyName())).findFirst()
308                     .ifPresent(config::addApprovedLicenseCategory);
309                 } else {
310                     config.addApprovedLicenseCategory(ILicenseFamily.builder().setLicenseFamilyCategory(slf.getFamilyCategory())
311                     .setLicenseFamilyName(StringUtils.defaultIfBlank(slf.getFamilyName(), slf.getFamilyCategory()))
312                     .build());
313                 }
314             }
315         }
316     }
317 
318     /**
319      * Reads values for the Arg.
320      *
321      * @param arg The Arg to get the values for.
322      * @return The list of values or an empty list.
323      */
324     protected List<String> getValues(final Arg arg) {
325         List<String> result = new ArrayList<>();
326         for (Option option : arg.group().getOptions()) {
327             if (option.getLongOpt() != null) {
328                 List<String> args = getArg(option.getLongOpt());
329                 if (args != null) {
330                     result.addAll(args);
331                 }
332             }
333         }
334         return result;
335     }
336 
337     /**
338      * Removes all values for an Arg.
339      * @param arg The arg to remove values for.
340      */
341     protected void removeKey(final Arg arg) {
342         for (Option option : arg.group().getOptions()) {
343             if (option.getLongOpt() != null) {
344                 removeArg(option.getLongOpt());
345             }
346         }
347     }
348 
349     private org.apache.rat.utils.Log makeLog() {
350         return new org.apache.rat.utils.Log() {
351             @Override
352             public Level getLevel() {
353                 final org.apache.maven.plugin.logging.Log log = getLog();
354                 if (log.isDebugEnabled()) {
355                     return Level.DEBUG;
356                 }
357                 if (log.isInfoEnabled()) {
358                     return Level.INFO;
359                 }
360                 if (log.isWarnEnabled()) {
361                     return Level.WARN;
362                 }
363                 if (log.isErrorEnabled()) {
364                     return Level.ERROR;
365                 }
366                 return Level.OFF;
367             }
368 
369             @Override
370             public void log(final Level level, final String message, final Throwable throwable) {
371                 final org.apache.maven.plugin.logging.Log log = getLog();
372                 switch (level) {
373                     case DEBUG:
374                         if (throwable != null) {
375                             log.debug(message, throwable);
376                         } else {
377                             log.debug(message);
378                         }
379                         break;
380                     case INFO:
381                         if (throwable != null) {
382                             log.info(message, throwable);
383                         } else {
384                             log.info(message);
385                         }
386                         break;
387                     case WARN:
388                         if (throwable != null) {
389                             log.warn(message, throwable);
390                         } else {
391                             log.warn(message);
392                         }
393                         break;
394                     case ERROR:
395                         if (throwable != null) {
396                             log.error(message, throwable);
397                         } else {
398                             log.error(message);
399                         }
400                         break;
401                     case OFF:
402                         break;
403                 }
404             }
405 
406             @Override
407             public void log(final Level level, final String msg) {
408                 final org.apache.maven.plugin.logging.Log log = getLog();
409                 switch (level) {
410                     case DEBUG:
411                         log.debug(msg);
412                         break;
413                     case INFO:
414                         log.info(msg);
415                         break;
416                     case WARN:
417                         log.warn(msg);
418                         break;
419                     case ERROR:
420                         log.error(msg);
421                         break;
422                     case OFF:
423                         break;
424                 }
425             }
426         };
427     }
428 
429     private void setIncludeExclude() {
430 
431         if (excludeSubProjects && project != null && project.getModules() != null) {
432             List<String> subModules = new ArrayList<>();
433             project.getModules().forEach(s -> subModules.add(format("%s/**", s)));
434             setInputExcludes(subModules.toArray(new String[0]));
435         }
436 
437         List<String> values = getValues(Arg.EXCLUDE);
438         if (values.isEmpty() && useDefaultExcludes) {
439             DefaultLog.getInstance().debug("Adding plexus default exclusions...");
440             setInputExcludes(StandardCollection.STANDARD_PATTERNS.patterns().toArray(new String[0]));
441 
442             DefaultLog.getInstance().debug("Adding SCM default exclusions...");
443             setInputExcludes(StandardCollection.STANDARD_SCMS.patterns().toArray(new String[0]));
444         }
445 
446         if (useMavenDefaultExcludes) {
447             setInputExcludeStd(StandardCollection.MAVEN.name());
448         }
449         if (useEclipseDefaultExcludes) {
450             setInputExcludeStd(StandardCollection.ECLIPSE.name());
451         }
452         if (useIdeaDefaultExcludes) {
453             setInputExcludeStd(StandardCollection.IDEA.name());
454         }
455 
456         if (parseSCMIgnoresAsExcludes) {
457             setInputExcludeParsedScm(StandardCollection.STANDARD_SCMS.name());
458         }
459     }
460 
461     protected ReportConfiguration getConfiguration() throws MojoExecutionException {
462         Log log = DefaultLog.getInstance();
463         if (reportConfiguration == null) {
464             try {
465                 if (super.getLog().isDebugEnabled()) {
466                     log.debug("Start BaseRatMojo Configuration options");
467                     for (Map.Entry<String, List<String>> entry : args.entrySet()) {
468                         log.debug(format(" * %s %s", entry.getKey(), String.join(", ", entry.getValue())));
469                     }
470                     log.debug("End BaseRatMojo Configuration options");
471                 }
472 
473                 boolean helpLicenses = !getValues(Arg.HELP_LICENSES).isEmpty();
474                 removeKey(Arg.HELP_LICENSES);
475 
476                 setIncludeExclude();
477 
478                 getLog().warn("Basedir is : " + basedir);
479                 ReportConfiguration config = OptionCollection.parseCommands(basedir, args().toArray(new String[0]),
480                         o -> getLog().warn("Help option not supported"),
481                         true);
482                 reportDeprecatedProcessing();
483 
484                 if (additionalLicenseFiles != null) {
485                     for (String licenseFile : additionalLicenseFiles) {
486                         URI uri = new File(licenseFile).toURI();
487                         Format fmt = Format.from(licenseFile);
488                         MatcherReader mReader = fmt.matcherReader();
489                         if (mReader != null) {
490                             mReader.addMatchers(uri);
491                         }
492                         LicenseReader lReader = fmt.licenseReader();
493                         if (lReader != null) {
494                             lReader.addLicenses(uri);
495                             config.addLicenses(lReader.readLicenses());
496                             config.addApprovedLicenseCategories(lReader.approvedLicenseId());
497                         }
498                     }
499                 }
500                 if (families != null || getDeprecatedConfigs().findAny().isPresent()) {
501                     if (log.isEnabled(Log.Level.DEBUG)) {
502                         log.debug(format("%s license families loaded from pom", families.length));
503                     }
504                     Consumer<ILicenseFamily> logger = super.getLog().isDebugEnabled() ? l -> log.debug(format("Family: %s", l))
505                             : l -> {
506                     };
507 
508                     Consumer<ILicenseFamily> process = logger.andThen(config::addFamily);
509                     getDeprecatedConfigs().map(DeprecatedConfig::getLicenseFamily).filter(Objects::nonNull).forEach(process);
510                     if (families != null) { // TODO remove if check in v1.0
511                         Arrays.stream(families).map(Family::build).forEach(process);
512                     }
513                 }
514 
515                 processLicenseFamilies(config);
516 
517                 if (approvedLicenses != null && approvedLicenses.length > 0) {
518                     Arrays.stream(approvedLicenses).forEach(config::addApprovedLicenseCategory);
519                 }
520 
521                 if (licenses != null) {
522                     if (log.isEnabled(Log.Level.DEBUG)) {
523                         log.debug(format("%s licenses loaded from pom", licenses.length));
524                     }
525                     Consumer<ILicense> logger = log.isEnabled(Log.Level.DEBUG) ? l -> log.debug(format("License: %s", l))
526                             : l -> {
527                     };
528                     Consumer<ILicense> addApproved = (approvedLicenses == null || approvedLicenses.length == 0)
529                             ? l -> config.addApprovedLicenseCategory(l.getLicenseFamily())
530                             : l -> {
531                     };
532 
533                     Consumer<ILicense> process = logger.andThen(config::addLicense).andThen(addApproved);
534                     SortedSet<ILicenseFamily> families = config.getLicenseFamilies(LicenseFilter.ALL);
535                     getDeprecatedConfigs().map(DeprecatedConfig::getLicense).filter(Objects::nonNull)
536                             .map(x -> x.setLicenseFamilies(families).build()).forEach(process);
537                     getLicenses().map(x -> x.build(families)).forEach(process);
538                 }
539                 DocumentName dirName = DocumentName.builder(basedir).build();
540                 config.addSource(new DirectoryWalker(new FileDocument(dirName, basedir, config.getDocumentExcluder(dirName))));
541 
542                 if (helpLicenses) {
543                     new org.apache.rat.help.Licenses(config, new PrintWriter(log.asWriter())).printHelp();
544                 }
545                 reportConfiguration = config;
546             } catch (IOException e) {
547                 throw new MojoExecutionException(e);
548             }
549         }
550         return reportConfiguration;
551     }
552 
553     protected void logLicenses(final Collection<ILicense> licenses) {
554         if (getLog().isDebugEnabled()) {
555             getLog().debug("The following " + licenses.size() + " licenses are activated:");
556             for (ILicense license : licenses) {
557                 getLog().debug("* " + license.toString());
558             }
559         }
560     }
561 }