From 52097a6867ebbd2c51e9b5d8357bf5bb165fbc85 Mon Sep 17 00:00:00 2001 From: Jeremy Long Date: Fri, 15 Aug 2014 06:02:04 -0400 Subject: [PATCH] initial version of the ReportAggregationMojo Former-commit-id: e15575413d625c6b5c5f3d73f5a739e1890eec27 --- .../maven/ReportAggregationMojo.java | 340 ++++++++++++++++++ 1 file changed, 340 insertions(+) create mode 100644 dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/ReportAggregationMojo.java diff --git a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/ReportAggregationMojo.java b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/ReportAggregationMojo.java new file mode 100644 index 000000000..6da3dc6c1 --- /dev/null +++ b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/ReportAggregationMojo.java @@ -0,0 +1,340 @@ +/* + * This file is part of dependency-check-maven. + * + * Licensed 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. + * + * Copyright (c) 2014 Jeremy Long. All Rights Reserved. + */ +package org.owasp.dependencycheck.maven; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.logging.Logger; +import org.apache.maven.doxia.sink.Sink; +import org.apache.maven.doxia.sink.SinkFactory; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.Component; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.project.MavenProject; +import org.apache.maven.reporting.MavenMultiPageReport; +import org.apache.maven.reporting.MavenReportException; + +/** + *

+ * This is an abstract reporting mojo that enables report aggregation. Some of the code in the this class was copied + * from the CoberturaReportMojo (http://mojo.codehaus.org/cobertura-maven-plugin/, version 2.6). The authors of the + * CoberturaReportMojo were Will Gwaltney and + * Joakim Erdfelt. There working example of how to do report aggregation was + * invaluable.

+ *

+ * An important point about using this abstract class is that it is intended for one to write some form of serialized + * data (via the {@link org.owasp.dependencycheck.maven.ReportAggregationMojo#writeDataFile() }; note that the + * writeDataFile() function is called automatically after either {@link org.owasp.dependencycheck.maven.ReportAggregationMojo#executeNonAggregateReport(org.apache.maven.doxia.sink.Sink, + * org.apache.maven.doxia.sink.SinkFactory, java.util.Locale) + * } or {@link org.owasp.dependencycheck.maven.ReportAggregationMojo#executeAggregateReport(org.apache.maven.doxia.sink.Sink, + * org.apache.maven.doxia.sink.SinkFactory, java.util.Locale) + * } are called. When executeAggregateReport() is implemented, one can call {@link org.owasp.dependencycheck.maven.ReportAggregationMojo#getChildDataFiles() + * } to obtain a list of the data files to aggregate.

+ * + * + * @author Jeremy Long + */ +public abstract class ReportAggregationMojo extends AbstractMojo implements MavenMultiPageReport { + + /** + * The Maven Project Object. + */ + @Component + private MavenProject project; + + /** + * Logger field reference. + */ + private static final Logger logger = Logger.getLogger(ReportAggregationMojo.class.getName()); + + /** + * List of Maven project of the current build + */ + @Parameter(readonly = true, required = true, property = "reactorProjects") + private List reactorProjects; + + /** + * Generate aggregate reports in multi-module projects. + */ + @Parameter(property = "aggregate", defaultValue = "false") + private boolean aggregate; + + private Map< MavenProject, List< MavenProject>> projectChildren; + + protected void preExecute() throws MojoExecutionException, MojoFailureException { + if (this.canGenerateAggregateReport()) { + buildAggregateInfo(); + } + } + + protected abstract void performExecute() throws MojoExecutionException, MojoFailureException; + + protected void postExecute() throws MojoExecutionException, MojoFailureException { + writeDataFile(); + } + + public final void execute() throws MojoExecutionException, MojoFailureException { + try { + preExecute(); + performExecute(); + } finally { + postExecute(); + } + } + + /** + * Runs prior to the site report generation. + * + * @throws MavenReportException if a maven report exception occurs + */ + protected void preGenerate() throws MavenReportException { + if (canGenerateAggregateReport()) { + buildAggregateInfo(); + } + } + + /** + * Executes after the site report has been generated. + * + * @throws MavenReportException if a maven report exception occurs + */ + protected void postGenerate() throws MavenReportException { + writeDataFile(); + } + + /** + * Generates the non aggregate report. + * + * @param sink the sink to write the report to + * @param sinkFactory the sink factory + * @param locale the locale to use when generating the report + * @throws MavenReportException if a maven report exception occurs + */ + protected abstract void executeNonAggregateReport(Sink sink, SinkFactory sinkFactory, Locale locale) throws MavenReportException; + + /** + * Generates the aggregate Site Report. + * + * @param sink the sink to write the report to + * @param sinkFactory the sink factory + * @param locale the locale to use when generating the report + * @throws MavenReportException if a maven report exception occurs + */ + protected abstract void executeAggregateReport(Sink sink, SinkFactory sinkFactory, Locale locale) throws MavenReportException; + + /** + * Generates the Dependency-Check Site Report. + * + * @param sink the sink to write the report to + * @param locale the locale to use when generating the report + * @throws MavenReportException if a maven report exception occurs + */ + public final void generate(@SuppressWarnings("deprecation") org.codehaus.doxia.sink.Sink sink, Locale locale) throws MavenReportException { + generate((Sink) sink, null, locale); + } + + /** + * Generates the Dependency-Check Site Report. + * + * @param sink the sink to write the report to + * @param sinkFactory the sink factory + * @param locale the locale to use when generating the report + * @throws MavenReportException if a maven report exception occurs + */ + public final void generate(Sink sink, SinkFactory sinkFactory, Locale locale) throws MavenReportException { + try { + preGenerate(); + if (canGenerateNonAggregateReport()) { + executeNonAggregateReport(sink, sinkFactory, locale); + } + + if (canGenerateAggregateReport()) { + executeAggregateReport(sink, sinkFactory, locale); + } + } finally { + postGenerate(); + } + } + + /** + * Returns whether or not the mojo can generate a non-aggregate report for this project. + * + * @return true if a non-aggregate report can be generated, otherwise false + */ + protected abstract boolean canGenerateNonAggregateReport(); + + /** + * Returns whether or not we can generate any aggregate reports at this time. + * + * @return true if an aggregate report can be generated, otherwise false + */ + protected abstract boolean canGenerateAggregateReport(); + + /** + * Returns the data file's names. + * + * @return the data file's name + */ + protected abstract String getDataFileName(); + + /** + * Writes the data file to disk in the target directory. + * + */ + protected abstract void writeDataFile(); + + /** + * Collects the information needed for building aggregate reports. + */ + private void buildAggregateInfo() { + if (projectChildren != null) { + // already did this work + return; + } + logger.warning("building aggregate info"); + boolean test = reactorProjects == null; + logger.warning("Reactor is " + test); + + // build parent-child map + projectChildren = new HashMap>(); + for (MavenProject proj : reactorProjects) { + List depList = projectChildren.get(proj.getParent()); + if (depList == null) { + depList = new ArrayList(); + projectChildren.put(proj.getParent(), depList); + } + depList.add(proj); + } + } + + /** + * Returns a list containing all the recursive, non-pom children of the given project, never null. + * + * @return a list of child projects + */ + protected List getAllChildren() { + return getAllChildren(project); + } + + /** + * Returns a list containing all the recursive, non-pom children of the given project, never null. + * + * @param parentProject the parent project to collect the child project references + * @return a list of child projects + */ + private List getAllChildren(MavenProject parentProject) { + List children = projectChildren.get(parentProject); + if (children == null) { + return Collections.emptyList(); + } + + List result = new ArrayList(); + for (MavenProject child : children) { + if (isMultiModule(child)) { + result.addAll(getAllChildren(child)); + } else { + result.add(child); + } + } + return result; + } + + /** + * Returns any existing output files from the current projects children. + * + * @param projects the list of projects to obtain the output files from + * @return a list of output files + */ + protected List getChildDataFiles() { + List projects = getAllChildren(); + return getDataFiles(projects); + } + + /** + * Returns any existing output files from the given list of projects. + * + * @param projects the list of projects to obtain the output files from + * @return a list of output files + */ + protected List getDataFiles(List projects) { + List files = new ArrayList(); + for (MavenProject proj : projects) { + if (isMultiModule(proj)) { + continue; + } + File outputFile = new File(proj.getBasedir(), getDataFileName()); + if (outputFile.exists()) { + files.add(outputFile); + } else { + if (!isMultiModule(project)) { + final String msg = String.format("Unable to aggregate data for '%s' - missing data file '%s'", + proj.getName(), outputFile.getPath()); + logger.warning(msg); + } + } + } + return files; + } + + /** + * Test if the project has pom packaging + * + * @param mavenProject Project to test + * @return True if it has a pom packaging + */ + private boolean isMultiModule(MavenProject mavenProject) { + return "pom".equals(mavenProject.getPackaging()); + } + + /** + * Test if the project has pom packaging + * + * @param mavenProject Project to test + * @return True if it has a pom packaging + */ + protected boolean isMultiModule() { + return "pom".equals(project.getPackaging()); + } + + /** + * Check whether the current project is the last project in a multi-module build. If the maven build is not a + * multi-module project then this will always return true. + * + * @return true if the current project is the last project in a multi-module build; otherwise + * false + */ + protected boolean isLastProject() { + return project.equals(reactorProjects.get(reactorProjects.size() - 1)); + } + + /** + * Returns whether or not the mojo is configured to perform report aggregation. + * + * @return true if report aggregation is enabled; otherwise false + */ + public boolean isAggregate() { + return aggregate; + } +}