| package org.apache.rat.mp; |
| |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| import static org.apache.rat.mp.util.ConfigurationHelper.newInstance; |
| import static org.apache.rat.mp.util.ExclusionHelper.addEclipseDefaults; |
| import static org.apache.rat.mp.util.ExclusionHelper.addIdeaDefaults; |
| import static org.apache.rat.mp.util.ExclusionHelper.addMavenDefaults; |
| import static org.apache.rat.mp.util.ExclusionHelper.addPlexusAndScmDefaults; |
| |
| import java.io.BufferedInputStream; |
| import java.io.BufferedReader; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.PrintWriter; |
| import java.io.Reader; |
| import java.io.StringWriter; |
| import java.io.Writer; |
| import java.lang.reflect.UndeclaredThrowableException; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| import javax.xml.transform.TransformerConfigurationException; |
| |
| |
| import org.apache.commons.io.IOUtils; |
| import org.apache.commons.lang3.StringUtils; |
| import org.apache.maven.plugin.AbstractMojo; |
| import org.apache.maven.plugin.MojoExecutionException; |
| import org.apache.maven.plugin.MojoFailureException; |
| import org.apache.maven.plugins.annotations.Parameter; |
| import org.apache.maven.project.MavenProject; |
| import org.apache.rat.Defaults; |
| import org.apache.rat.Report; |
| import org.apache.rat.ReportConfiguration; |
| import org.apache.rat.analysis.IHeaderMatcher; |
| import org.apache.rat.analysis.util.HeaderMatcherMultiplexer; |
| import org.apache.rat.api.RatException; |
| import org.apache.rat.config.SourceCodeManagementSystems; |
| import org.apache.rat.license.ILicenseFamily; |
| import org.apache.rat.mp.util.ScmIgnoreParser; |
| import org.apache.rat.report.IReportable; |
| import org.apache.rat.report.claim.ClaimStatistic; |
| import org.codehaus.plexus.util.DirectoryScanner; |
| |
| /** |
| * Abstract base class for Mojos, which are running Rat. |
| */ |
| public abstract class AbstractRatMojo extends AbstractMojo { |
| |
| /** |
| * The base directory, in which to search for files. |
| */ |
| @Parameter(property = "rat.basedir", defaultValue = "${basedir}", required = true) |
| private File basedir; |
| |
| /** |
| * Specifies the licenses to accept. Deprecated, use {@link #licenses} |
| * instead. |
| * |
| * @deprecated Use {@link #licenses} instead. |
| */ |
| @Deprecated |
| @Parameter |
| private HeaderMatcherSpecification[] licenseMatchers; |
| |
| /** |
| * Specifies the licenses to accept. By default, these are added to the |
| * default licenses, unless you set {@link #addDefaultLicenseMatchers} to |
| * false. |
| * |
| * @since 0.8 |
| */ |
| @Parameter |
| private IHeaderMatcher[] licenses; |
| |
| /** |
| * The set of approved license family names. |
| * |
| * @deprecated Use {@link #licenseFamilies} instead. |
| */ |
| @Deprecated |
| private LicenseFamilySpecification[] licenseFamilyNames; |
| |
| /** |
| * Specifies the license families to accept. |
| * |
| * @since 0.8 |
| */ |
| @Parameter |
| private ILicenseFamily[] licenseFamilies; |
| |
| /** |
| * Whether to add the default list of license matchers. |
| */ |
| @Parameter(property = "rat.addDefaultLicenseMatchers", defaultValue = "true") |
| private boolean addDefaultLicenseMatchers; |
| |
| /** |
| * Specifies files, which are included in the report. By default, all files |
| * are included. |
| */ |
| @Parameter |
| private String[] includes; |
| |
| /** |
| * Specifies a file, from which to read includes. Basically, an alternative to |
| * specifying the includes as a list. |
| */ |
| @Parameter(property="rat.includesFile") |
| private String includesFile; |
| |
| /** |
| * Specifies the include files character set. Defaults to @code{${project.build.sourceEncoding}), |
| * or @code{UTF8}. |
| */ |
| @Parameter(property="rat.includesFileCharset", defaultValue="${project.build.sourceEncoding}") |
| private String includesFileCharset; |
| |
| /** |
| * Specifies files, which are excluded in the report. By default, no files |
| * are excluded. |
| */ |
| @Parameter |
| private String[] excludes; |
| |
| /** |
| * Specifies a file, from which to read excludes. Basically, an alternative to |
| * specifying the excludes as a list. The excludesFile is assumed to be using the |
| * UFT8 character set. |
| */ |
| @Parameter(property="rat.excludesFile") |
| private String excludesFile; |
| |
| /** |
| * Specifies the include files character set. Defaults to @code{${project.build.sourceEncoding}), |
| * or @code{UTF8}. |
| */ |
| @Parameter(property="rat.excludesFileCharset", defaultValue="${project.build.sourceEncoding}") |
| private String excludesFileCharset; |
| |
| /** |
| * Whether to use the default excludes when scanning for files. The default |
| * excludes are: |
| * <ul> |
| * <li>meta data files for source code management / revision control systems, |
| * see {@link SourceCodeManagementSystems}</li> |
| * <li>temporary files used by Maven, see <a |
| * href="#useMavenDefaultExcludes">useMavenDefaultExcludes</a></li> |
| * <li>configuration files for Eclipse, see <a |
| * href="#useEclipseDefaultExcludes">useEclipseDefaultExcludes</a></li> |
| * <li>configuration files for IDEA, see <a |
| * href="#useIdeaDefaultExcludes">useIdeaDefaultExcludes</a></li> |
| * </ul> |
| */ |
| @Parameter(property = "rat.useDefaultExcludes", defaultValue = "true") |
| private boolean useDefaultExcludes; |
| |
| /** |
| * Whether to use the Maven specific default excludes when scanning for |
| * files. Maven specific default excludes are given by the constant |
| * MAVEN_DEFAULT_EXCLUDES: The <code>target</code> directory, the |
| * <code>cobertura.ser</code> file, and so on. |
| */ |
| @Parameter(property = "rat.useMavenDefaultExcludes", defaultValue = "true") |
| private boolean useMavenDefaultExcludes; |
| |
| /** |
| * Whether to parse source code management system (SCM) ignore files and use their contents as excludes. |
| * At the moment this works for the following SCMs: |
| * |
| * @see org.apache.rat.config.SourceCodeManagementSystems |
| */ |
| @Parameter(property = "rat.parseSCMIgnoresAsExcludes", defaultValue = "true") |
| private boolean parseSCMIgnoresAsExcludes; |
| |
| /** |
| * Whether to use the Eclipse specific default excludes when scanning for |
| * files. Eclipse specific default excludes are given by the constant |
| * ECLIPSE_DEFAULT_EXCLUDES: The <code>.classpath</code> and |
| * <code>.project</code> files, the <code>.settings</code> directory, and so |
| * on. |
| */ |
| @Parameter(property = "rat.useEclipseDefaultExcludes", defaultValue = "true") |
| private boolean useEclipseDefaultExcludes; |
| |
| /** |
| * Whether to use the IDEA specific default excludes when scanning for |
| * files. IDEA specific default excludes are given by the constant |
| * IDEA_DEFAULT_EXCLUDES: The <code>*.iml</code>, <code>*.ipr</code> and |
| * <code>*.iws</code> files and the <code>.idea</code> directory. |
| */ |
| @Parameter(property = "rat.useIdeaDefaultExcludes", defaultValue = "true") |
| private boolean useIdeaDefaultExcludes; |
| |
| /** |
| * Whether to exclude subprojects. This is recommended, if you want a |
| * separate apache-rat-plugin report for each subproject. |
| */ |
| @Parameter(property = "rat.excludeSubprojects", defaultValue = "true") |
| private boolean excludeSubProjects; |
| |
| /** |
| * Will skip the plugin execution, e.g. for technical builds that do not take license compliance into account. |
| * |
| * @since 0.11 |
| */ |
| @Parameter(property = "rat.skip", defaultValue = "false") |
| protected boolean skip; |
| |
| /** |
| * Holds the maven-internal project to allow resolution of artifact properties during mojo runs. |
| */ |
| @Parameter(defaultValue = "${project}", required = true, readonly = true) |
| private MavenProject project; |
| |
| /** |
| * @return Returns the Maven project. |
| */ |
| protected MavenProject getProject() { |
| return project; |
| } |
| |
| /** |
| * Returns the set of {@link IHeaderMatcher header matchers} to use. |
| * |
| * @return list of license matchers to use |
| * @throws MojoFailureException An error in the plugin configuration was detected. |
| * @throws MojoExecutionException An error occurred while calculating the result. |
| */ |
| private List<IHeaderMatcher> mergeLicenseMatchers() |
| throws MojoFailureException, MojoExecutionException { |
| List<IHeaderMatcher> matchers = new ArrayList<>(); |
| |
| if (licenses != null) { |
| matchers.addAll(Arrays.asList(licenses)); |
| getLog().info("Added " + licenses.length + " additional default licenses."); |
| } |
| |
| if (licenseMatchers != null) { |
| for (final HeaderMatcherSpecification spec : licenseMatchers) { |
| matchers.add(newInstance(IHeaderMatcher.class, spec.getClassName())); |
| } |
| } |
| |
| if (addDefaultLicenseMatchers) { |
| getLog().info("Enabled default license matchers."); |
| matchers.addAll(Defaults.DEFAULT_MATCHERS); |
| } |
| logLicenseMatchers(matchers); |
| |
| return matchers; |
| } |
| |
| private void logLicenseMatchers(List<IHeaderMatcher> matchers) { |
| if (getLog().isDebugEnabled()) { |
| getLog().debug("The following license matchers are activated:"); |
| for (IHeaderMatcher matcher : matchers) { |
| getLog().debug("* " + matcher.toString()); |
| } |
| } |
| |
| } |
| |
| /** |
| * Creates an iterator over the files to check. |
| * |
| * @return A container of files, which are being checked. |
| * @throws MojoExecutionException in case of errors. I/O errors result in UndeclaredThrowableExceptions. |
| */ |
| protected IReportable getResources() throws MojoExecutionException { |
| final DirectoryScanner ds = new DirectoryScanner(); |
| ds.setBasedir(basedir); |
| setExcludes(ds); |
| setIncludes(ds); |
| ds.scan(); |
| whenDebuggingLogExcludedFiles(ds); |
| final String[] files = ds.getIncludedFiles(); |
| logAboutIncludedFiles(files); |
| try { |
| return new FilesReportable(basedir, files); |
| } catch (final IOException e) { |
| throw new UndeclaredThrowableException(e); |
| } |
| } |
| |
| private void logAboutIncludedFiles(final String[] files) { |
| if (files.length == 0) { |
| getLog().warn("No resources included."); |
| } else { |
| getLog().info( |
| files.length |
| + " resources included (use -debug for more details)"); |
| if (getLog().isDebugEnabled()) { |
| for (final String resource : files) { |
| getLog().debug(" - included " + resource); |
| } |
| } |
| } |
| } |
| |
| private void whenDebuggingLogExcludedFiles(final DirectoryScanner ds) { |
| if (getLog().isDebugEnabled()) { |
| final String[] excludedFiles = ds.getExcludedFiles(); |
| if (excludedFiles.length == 0) { |
| getLog().debug("No excluded resources."); |
| } else { |
| getLog().debug( |
| "Excluded " + excludedFiles.length + " resources:"); |
| for (final String resource : excludedFiles) { |
| getLog().debug(" - excluded " + resource); |
| } |
| } |
| } |
| } |
| |
| private void setIncludes(DirectoryScanner ds) throws MojoExecutionException { |
| if ((includes != null && includes.length > 0) || includesFile != null) { |
| final List<String> includeList = new ArrayList<>(); |
| if (includes != null) { |
| includeList.addAll(Arrays.asList(includes)); |
| } |
| if (includesFile != null) { |
| final String charset = includesFileCharset == null ? "UTF8" : includesFileCharset; |
| final File f = new File(includesFile); |
| if (!f.isFile()) { |
| getLog().error("IncludesFile not found: " + f.getAbsolutePath()); |
| } else { |
| getLog().info("Includes loaded from file " + includesFile + ", using character set " + charset); |
| } |
| includeList.addAll(getPatternsFromFile(f, charset)); |
| } |
| ds.setIncludes(includeList.toArray(new String[includeList.size()])); |
| } |
| } |
| |
| private List<String> getPatternsFromFile(File pFile, String pCharset) throws MojoExecutionException { |
| InputStream is = null; |
| BufferedInputStream bis = null; |
| Reader r = null; |
| BufferedReader br = null; |
| Throwable th = null; |
| final List<String> patterns = new ArrayList<>(); |
| try { |
| is = new FileInputStream(pFile); |
| bis = new BufferedInputStream(is); |
| r = new InputStreamReader(bis, pCharset); |
| br = new BufferedReader(r); |
| for (;;) { |
| final String s = br.readLine(); |
| if (s == null) { |
| break; |
| } |
| patterns.add(s); |
| } |
| br.close(); |
| br = null; |
| r.close(); |
| r = null; |
| bis.close(); |
| bis = null; |
| is.close(); |
| is = null; |
| } catch (Throwable t) { |
| th = t; |
| } finally { |
| if (br != null) { try { br.close(); } catch (Throwable t) { if (th == null) { th = t; } } } |
| if (r != null) { try { r.close(); } catch (Throwable t) { if (th == null) { th = t; } } } |
| if (bis != null) { try { bis.close(); } catch (Throwable t) { if (th == null) { th = t; } } } |
| if (is != null) { try { is.close(); } catch (Throwable t) { if (th == null) { th = t; } } } |
| } |
| if (th != null) { |
| if (th instanceof RuntimeException) { throw (RuntimeException) th; } |
| if (th instanceof Error) { throw (Error) th; } |
| throw new MojoExecutionException(th.getMessage(), th); |
| } |
| return patterns; |
| } |
| |
| private void setExcludes(DirectoryScanner ds) throws MojoExecutionException { |
| final List<String> excludeList = mergeDefaultExclusions(); |
| if (excludes == null || excludes.length == 0) { |
| getLog().info("No excludes explicitly specified."); |
| } else { |
| getLog().info(excludes.length + " explicit excludes (use -debug for more details)."); |
| for (final String exclude : excludes) { |
| getLog().debug("Exclude: " + exclude); |
| } |
| } |
| if (excludes != null) { |
| Collections.addAll(excludeList, excludes); |
| } |
| if (!excludeList.isEmpty()) { |
| final String[] allExcludes = excludeList.toArray(new String[excludeList |
| .size()]); |
| ds.setExcludes(allExcludes); |
| } |
| } |
| |
| private List<String> mergeDefaultExclusions() throws MojoExecutionException { |
| final Set<String> results = new HashSet<>(); |
| |
| addPlexusAndScmDefaults(getLog(), useDefaultExcludes, results); |
| addMavenDefaults(getLog(), useMavenDefaultExcludes, results); |
| addEclipseDefaults(getLog(), useEclipseDefaultExcludes, results); |
| addIdeaDefaults(getLog(), useIdeaDefaultExcludes, results); |
| |
| if (parseSCMIgnoresAsExcludes) { |
| getLog().info("Will parse SCM ignores for exclusions..."); |
| results.addAll(ScmIgnoreParser.getExclusionsFromSCM(getLog(), project.getBasedir())); |
| getLog().info("Finished adding exclusions from SCM ignore files."); |
| } |
| |
| if (excludeSubProjects && project != null |
| && project.getModules() != null) { |
| for (final Object o : project.getModules()) { |
| final String moduleSubPath = (String) o; |
| if (new File( basedir, moduleSubPath ).isDirectory()) { |
| results.add(moduleSubPath + "/**/*"); |
| } |
| else { |
| results.add(StringUtils.substringBeforeLast( moduleSubPath, "/" ) + "/**/*"); |
| } |
| } |
| } |
| |
| getLog().debug("Finished creating list of implicit excludes."); |
| if (results.isEmpty()) { |
| getLog().info("No excludes implicitly specified."); |
| } else { |
| getLog().info( |
| results.size() |
| + " implicit excludes (use -debug for more details)."); |
| for (final String exclude : results) { |
| getLog().debug("Implicit exclude: " + exclude); |
| } |
| } |
| if (excludesFile != null) { |
| final File f = new File(excludesFile); |
| if (!f.isFile()) { |
| getLog().error("Excludes file not found: " + f.getAbsolutePath()); |
| } |
| if (!f.canRead()) { |
| getLog().error("Excludes file not readable: " + f.getAbsolutePath()); |
| } |
| final String charset = excludesFileCharset == null ? "UTF8" : excludesFileCharset; |
| getLog().info("Loading excludes from file " + f + ", using character set " + charset); |
| results.addAll(getPatternsFromFile(f, charset)); |
| } |
| |
| return new ArrayList<>(results); |
| } |
| |
| /** |
| * Creates the report as a string. |
| * |
| * @param styleSheet The style sheet to use when formatting the report |
| * @return Report contents |
| * @throws MojoFailureException An error in the plugin configuration was detected. |
| * @throws MojoExecutionException An error occurred while creating the report. |
| */ |
| protected String createReport(InputStream styleSheet) |
| throws MojoExecutionException, MojoFailureException { |
| StringWriter sw = new StringWriter(); |
| PrintWriter pw = null; |
| try { |
| pw = new PrintWriter(sw); |
| createReport(new PrintWriter(sw), styleSheet); |
| final String result = sw.toString(); |
| pw.close(); |
| pw = null; |
| sw.close(); |
| sw = null; |
| return result; |
| } catch (IOException e) { |
| throw new MojoExecutionException(e.getMessage(), e); |
| } finally { |
| IOUtils.closeQuietly(pw); |
| IOUtils.closeQuietly(sw); |
| } |
| } |
| |
| /** |
| * Writes the report to the given stream. |
| * |
| * @param out The target writer, to which the report is being written. |
| * @param style The stylesheet to use, or <code>null</code> for raw XML |
| * @return the current statistic. |
| * @throws MojoFailureException An error in the plugin configuration was detected. |
| * @throws MojoExecutionException Another error occurred while creating the report. |
| */ |
| protected ClaimStatistic createReport(Writer out, InputStream style) |
| throws MojoExecutionException, MojoFailureException { |
| final ReportConfiguration configuration = getConfiguration(); |
| try { |
| if (style != null) { |
| return new Report(basedir).report(out, getResources(), style, configuration); |
| } else { |
| return new Report(basedir).report(getResources(), out, configuration); |
| } |
| } catch (final TransformerConfigurationException | RatException | InterruptedException | IOException e) { |
| throw new MojoExecutionException(e.getMessage(), e); |
| } |
| } |
| |
| protected ReportConfiguration getConfiguration() |
| throws MojoFailureException, MojoExecutionException { |
| final ReportConfiguration configuration = new ReportConfiguration(); |
| configuration.setHeaderMatcher(new HeaderMatcherMultiplexer( |
| mergeLicenseMatchers())); |
| configuration.setApprovedLicenseNames(mergeApprovedLicenseNames()); |
| configuration.setApproveDefaultLicenses(addDefaultLicenseMatchers); |
| return configuration; |
| } |
| |
| private List<ILicenseFamily> mergeApprovedLicenseNames() |
| throws MojoExecutionException, MojoFailureException { |
| final List<ILicenseFamily> list = new ArrayList<>(); |
| if (licenseFamilies != null) { |
| getLog().info("Added " + licenseFamilies.length + " custom approved licenses."); |
| list.addAll(Arrays.asList(licenseFamilies)); |
| } |
| if (licenseFamilyNames != null) { |
| for (final LicenseFamilySpecification spec : licenseFamilyNames) { |
| list.add(newInstance(ILicenseFamily.class, spec.getClassName())); |
| } |
| } |
| return list; |
| } |
| } |