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