From e13225eee6f60d63f8c1f4f5d17f24f9b12773ae Mon Sep 17 00:00:00 2001 From: Jeremy Long Date: Sat, 30 Aug 2014 07:50:27 -0400 Subject: [PATCH] initial version of aggreation completed for issue #19 - some cleanup still needs to happen before final release Former-commit-id: 98c9af3004e2c725d0dca5d6847b65a4646c6a73 --- .../maven/DependencyCheckMojo.java | 446 +++++++++++------- .../maven/ReportAggregationMojo.java | 213 ++++++--- 2 files changed, 417 insertions(+), 242 deletions(-) diff --git a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/DependencyCheckMojo.java b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/DependencyCheckMojo.java index be970e3d2..6797df8a1 100644 --- a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/DependencyCheckMojo.java +++ b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/DependencyCheckMojo.java @@ -19,9 +19,12 @@ package org.owasp.dependencycheck.maven; import java.io.BufferedOutputStream; import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.ObjectInputStream; import java.io.ObjectOutput; import java.io.ObjectOutputStream; import java.io.OutputStream; @@ -31,11 +34,8 @@ import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.maven.artifact.Artifact; -import org.apache.maven.doxia.sink.Sink; -import org.apache.maven.doxia.sink.SinkFactory; 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.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; @@ -45,6 +45,8 @@ import org.apache.maven.reporting.MavenReport; import org.apache.maven.reporting.MavenReportException; import org.apache.maven.settings.Proxy; import org.owasp.dependencycheck.Engine; +import org.owasp.dependencycheck.analyzer.DependencyBundlingAnalyzer; +import org.owasp.dependencycheck.analyzer.exception.AnalysisException; import org.owasp.dependencycheck.data.nvdcve.DatabaseException; import org.owasp.dependencycheck.dependency.Dependency; import org.owasp.dependencycheck.dependency.Identifier; @@ -62,11 +64,11 @@ import org.owasp.dependencycheck.utils.Settings; requiresOnline = true) public class DependencyCheckMojo extends ReportAggregationMojo { + // /** * Logger field reference. */ private static final Logger logger = Logger.getLogger(DependencyCheckMojo.class.getName()); - /** * The properties file location. */ @@ -83,24 +85,14 @@ public class DependencyCheckMojo extends ReportAggregationMojo { * The dependency-check engine used to scan the project. */ private Engine engine = null; + // // - /** - * The Maven Project Object. - */ - @Component - private MavenProject project; /** * The path to the verbose log. */ @Parameter(property = "logfile", defaultValue = "") private String logFile; - /** - * Specifies the destination directory for the generated Dependency-Check report. This generally maps to - * "target/site". - */ - @Parameter(property = "reportOutputDirectory", defaultValue = "${project.reporting.outputDirectory}", required = true) - private File reportOutputDirectory; /** * The output directory. This generally maps to "target". */ @@ -124,123 +116,108 @@ public class DependencyCheckMojo extends ReportAggregationMojo { * The report format to be generated (HTML, XML, VULN, ALL). This configuration option has no affect if using this * within the Site plugin unless the externalReport is set to true. Default is HTML. */ - @SuppressWarnings({"CanBeFinal", "FieldCanBeLocal"}) + @SuppressWarnings("CanBeFinal") @Parameter(property = "format", defaultValue = "HTML", required = true) private String format = "HTML"; - /** - * Sets whether or not the external report format should be used. - */ - @SuppressWarnings({"CanBeFinal", "FieldCanBeLocal"}) - @Parameter(property = "externalReport", defaultValue = "false", required = true) - private boolean externalReport = false; - /** * The maven settings. */ - @SuppressWarnings({"CanBeFinal", "FieldCanBeLocal"}) @Parameter(property = "mavenSettings", defaultValue = "${settings}", required = false) private org.apache.maven.settings.Settings mavenSettings; /** * The maven settings proxy id. */ - @SuppressWarnings({"CanBeFinal", "FieldCanBeLocal"}) + @SuppressWarnings("CanBeFinal") @Parameter(property = "mavenSettingsProxyId", required = false) private String mavenSettingsProxyId; /** * The Connection Timeout. */ - @SuppressWarnings({"CanBeFinal", "FieldCanBeLocal"}) + @SuppressWarnings("CanBeFinal") @Parameter(property = "connectionTimeout", defaultValue = "", required = false) private String connectionTimeout = null; /** * The path to the suppression file. */ - @SuppressWarnings({"CanBeFinal", "FieldCanBeLocal"}) + @SuppressWarnings("CanBeFinal") @Parameter(property = "suppressionFile", defaultValue = "", required = false) private String suppressionFile = null; /** * Flag indicating whether or not to show a summary in the output. */ - @SuppressWarnings({"CanBeFinal", "FieldCanBeLocal"}) + @SuppressWarnings("CanBeFinal") @Parameter(property = "showSummary", defaultValue = "true", required = false) private boolean showSummary = true; /** * Whether or not the Jar Analyzer is enabled. */ - @SuppressWarnings({"CanBeFinal", "FieldCanBeLocal"}) + @SuppressWarnings("CanBeFinal") @Parameter(property = "jarAnalyzerEnabled", defaultValue = "true", required = false) private boolean jarAnalyzerEnabled = true; /** * Whether or not the Archive Analyzer is enabled. */ - @SuppressWarnings({"CanBeFinal", "FieldCanBeLocal"}) + @SuppressWarnings("CanBeFinal") @Parameter(property = "archiveAnalyzerEnabled", defaultValue = "true", required = false) private boolean archiveAnalyzerEnabled = true; /** * Whether or not the .NET Assembly Analyzer is enabled. */ - @SuppressWarnings({"CanBeFinal", "FieldCanBeLocal"}) + @SuppressWarnings("CanBeFinal") @Parameter(property = "assemblyAnalyzerEnabled", defaultValue = "true", required = false) private boolean assemblyAnalyzerEnabled = true; /** * Whether or not the .NET Nuspec Analyzer is enabled. */ - @SuppressWarnings({"CanBeFinal", "FieldCanBeLocal"}) + @SuppressWarnings("CanBeFinal") @Parameter(property = "nuspecAnalyzerEnabled", defaultValue = "true", required = false) private boolean nuspecAnalyzerEnabled = true; /** * Whether or not the Nexus Analyzer is enabled. */ - @SuppressWarnings({"CanBeFinal", "FieldCanBeLocal"}) + @SuppressWarnings("CanBeFinal") @Parameter(property = "nexusAnalyzerEnabled", defaultValue = "true", required = false) private boolean nexusAnalyzerEnabled = true; /** * Whether or not the Nexus Analyzer is enabled. */ - @SuppressWarnings({"CanBeFinal", "FieldCanBeLocal"}) @Parameter(property = "nexusUrl", defaultValue = "", required = false) private String nexusUrl; /** * Whether or not the configured proxy is used to connect to Nexus. */ - @SuppressWarnings({"CanBeFinal", "FieldCanBeLocal"}) @Parameter(property = "nexusUsesProxy", defaultValue = "true", required = false) private boolean nexusUsesProxy = true; /** * The database connection string. */ - @SuppressWarnings({"CanBeFinal", "FieldCanBeLocal"}) @Parameter(property = "connectionString", defaultValue = "", required = false) private String connectionString; /** * The database driver name. An example would be org.h2.Driver. */ - @SuppressWarnings({"CanBeFinal", "FieldCanBeLocal"}) @Parameter(property = "databaseDriverName", defaultValue = "", required = false) private String databaseDriverName; /** * The path to the database driver if it is not on the class path. */ - @SuppressWarnings({"CanBeFinal", "FieldCanBeLocal"}) @Parameter(property = "databaseDriverPath", defaultValue = "", required = false) private String databaseDriverPath; /** * The database user name. */ - @SuppressWarnings({"CanBeFinal", "FieldCanBeLocal"}) @Parameter(property = "databaseUser", defaultValue = "", required = false) private String databaseUser; /** * The password to use when connecting to the database. */ - @SuppressWarnings({"CanBeFinal", "FieldCanBeLocal"}) @Parameter(property = "databasePassword", defaultValue = "", required = false) private String databasePassword; /** @@ -303,12 +280,20 @@ public class DependencyCheckMojo extends ReportAggregationMojo { * * @deprecated Please use mavenSettings instead */ - @SuppressWarnings({"CanBeFinal", "FieldCanBeLocal"}) + @SuppressWarnings("CanBeFinal") @Parameter(property = "proxyUrl", defaultValue = "", required = false) @Deprecated private String proxyUrl = null; // + /** + * Constructs a new dependency-check-mojo. + */ + public DependencyCheckMojo() { + final InputStream in = DependencyCheckMojo.class.getClassLoader().getResourceAsStream(LOG_PROPERTIES_FILE); + LogUtils.prepareLogger(in, logFile); + } + /** * Executes the Dependency-Check on the dependent libraries. * @@ -316,61 +301,58 @@ public class DependencyCheckMojo extends ReportAggregationMojo { * @throws DatabaseException thrown if there is an exception connecting to the database */ private Engine executeDependencyCheck() throws DatabaseException { - - final InputStream in = DependencyCheckMojo.class.getClassLoader().getResourceAsStream(LOG_PROPERTIES_FILE); - LogUtils.prepareLogger(in, logFile); - - populateSettings(); - final Engine engine = new Engine(); - - final Set artifacts = project.getArtifacts(); - for (Artifact a : artifacts) { - if (skipTestScope && Artifact.SCOPE_TEST.equals(a.getScope())) { - continue; - } - - if (skipProvidedScope && Artifact.SCOPE_PROVIDED.equals(a.getScope())) { - continue; - } - - if (skipRuntimeScope && !Artifact.SCOPE_RUNTIME.equals(a.getScope())) { - continue; - } - - engine.scan(a.getFile().getAbsolutePath()); - } - engine.analyzeDependencies(); - - return engine; + return executeDependencyCheck(getProject()); } /** - * Returns the maven proxy. + * Executes the Dependency-Check on the dependent libraries. * - * @return the maven proxy + * @param project the project to run dependency-check on + * @return the Engine used to scan the dependencies. + * @throws DatabaseException thrown if there is an exception connecting to the database */ - private Proxy getMavenProxy() { - if (mavenSettings != null) { - final List proxies = mavenSettings.getProxies(); - if (proxies != null && proxies.size() > 0) { - if (mavenSettingsProxyId != null) { - for (Proxy proxy : proxies) { - if (mavenSettingsProxyId.equalsIgnoreCase(proxy.getId())) { - return proxy; - } - } - } else if (proxies.size() == 1) { - return proxies.get(0); - } else { - logger.warning("Multiple proxy defentiions exist in the Maven settings. In the dependency-check " - + "configuration set the maveSettingsProxyId so that the correct proxy will be used."); - throw new IllegalStateException("Ambiguous proxy definition"); - } + private Engine executeDependencyCheck(MavenProject project) throws DatabaseException { + Engine localEngine = initializeEngine(); + + final Set artifacts = project.getArtifacts(); + for (Artifact a : artifacts) { + if (excludeFromScan(a)) { + continue; } + + localEngine.scan(a.getFile().getAbsolutePath()); } - return null; + localEngine.analyzeDependencies(); + + return localEngine; } + private Engine initializeEngine() throws DatabaseException { + populateSettings(); + final Engine localEngine = new Engine(); + return localEngine; + } + + /** + * Tests is the artifact should be included in the scan (i.e. is the dependency in a scope that is being scanned). + * + * @param a the Artifact to test + * @return true if the artifact is in an excluded scope; otherwise false + */ + private boolean excludeFromScan(Artifact a) { + if (skipTestScope && Artifact.SCOPE_TEST.equals(a.getScope())) { + return true; + } + if (skipProvidedScope && Artifact.SCOPE_PROVIDED.equals(a.getScope())) { + return true; + } + if (skipRuntimeScope && !Artifact.SCOPE_RUNTIME.equals(a.getScope())) { + return true; + } + return false; + } + + // /** * Takes the properties supplied and updates the dependency-check settings. Additionally, this sets the system * properties required to change the proxy url, port, and connection timeout. @@ -485,6 +467,34 @@ public class DependencyCheckMojo extends ReportAggregationMojo { } } + /** + * Returns the maven proxy. + * + * @return the maven proxy + */ + private Proxy getMavenProxy() { + if (mavenSettings != null) { + final List proxies = mavenSettings.getProxies(); + if (proxies != null && proxies.size() > 0) { + if (mavenSettingsProxyId != null) { + for (Proxy proxy : proxies) { + if (mavenSettingsProxyId.equalsIgnoreCase(proxy.getId())) { + return proxy; + } + } + } else if (proxies.size() == 1) { + return proxies.get(0); + } else { + logger.warning("Multiple proxy defentiions exist in the Maven settings. In the dependency-check " + + "configuration set the maveSettingsProxyId so that the correct proxy will be used."); + throw new IllegalStateException("Ambiguous proxy definition"); + } + } + } + return null; + } + // + /** * Executes the dependency-check and generates the report. * @@ -495,7 +505,7 @@ public class DependencyCheckMojo extends ReportAggregationMojo { protected void performExecute() throws MojoExecutionException, MojoFailureException { try { engine = executeDependencyCheck(); - ReportingUtil.generateExternalReports(engine, outputDirectory, project.getName(), format); + ReportingUtil.generateExternalReports(engine, outputDirectory, getProject().getName(), format); if (this.showSummary) { showSummary(engine.getDependencies()); } @@ -511,50 +521,103 @@ public class DependencyCheckMojo extends ReportAggregationMojo { @Override protected void postExecute() throws MojoExecutionException, MojoFailureException { - super.postExecute(); - Settings.cleanup(true); - if (engine != null) { - engine.cleanup(); - engine = null; + try { + super.postExecute(); + } finally { + cleanupEngine(); } } @Override protected void postGenerate() throws MavenReportException { - super.postGenerate(); - Settings.cleanup(true); + try { + super.postGenerate(); + } finally { + cleanupEngine(); + } + } + + private void cleanupEngine() { if (engine != null) { engine.cleanup(); engine = null; } + Settings.cleanup(true); } /** * 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 */ @Override - protected void executeNonAggregateReport(Sink sink, SinkFactory sinkFactory, Locale locale) throws MavenReportException { - try { - //TODO figure out if the serialized data is present from THIS build and use it instead? - engine = executeDependencyCheck(); - if (this.externalReport) { - ReportingUtil.generateExternalReports(engine, reportOutputDirectory, project.getName(), format); - } else { - ReportingUtil.generateMavenSiteReport(engine, sink, project.getName()); + protected void executeNonAggregateReport(Locale locale) throws MavenReportException { + + List deps = readDataFile(); + if (deps != null) { + try { + engine = initializeEngine(); + engine.getDependencies().addAll(deps); + } catch (DatabaseException ex) { + final String msg = String.format("An unrecoverable exception with the dependency-check initialization occured while scanning %s", + getProject().getName()); + throw new MavenReportException(msg, ex); } - } catch (DatabaseException ex) { - logger.log(Level.SEVERE, - "Unable to connect to the dependency-check database; analysis has stopped"); - logger.log(Level.FINE, "", ex); + } else { + try { + engine = executeDependencyCheck(); + } catch (DatabaseException ex) { + final String msg = String.format("An unrecoverable exception with the dependency-check scan occured while scanning %s", + getProject().getName()); + throw new MavenReportException(msg, ex); + } + } + ReportingUtil.generateExternalReports(engine, getReportOutputDirectory(), getProject().getName(), format); + } + + @Override + protected void executeAggregateReport(MavenProject project, Locale locale) throws MavenReportException { + List deps = readDataFile(project); + if (deps != null) { + try { + engine = initializeEngine(); + engine.getDependencies().addAll(deps); + } catch (DatabaseException ex) { + final String msg = String.format("An unrecoverable exception with the dependency-check initialization occured while scanning %s", project.getName()); + throw new MavenReportException(msg, ex); + } + } else { + try { + engine = executeDependencyCheck(project); + } catch (DatabaseException ex) { + final String msg = String.format("An unrecoverable exception with the dependency-check scan occured while scanning %s", project.getName()); + throw new MavenReportException(msg, ex); + } + } + for (MavenProject child : getAllChildren(project)) { + deps = readDataFile(child); + if (deps == null) { + final String msg = String.format("Unable to include information on %s in the dependency-check aggregate report", child.getName()); + logger.severe(msg); + } else { + engine.getDependencies().addAll(deps); + } + } + DependencyBundlingAnalyzer bundler = new DependencyBundlingAnalyzer(); + try { + bundler.analyze(null, engine); + } catch (AnalysisException ex) { + logger.log(Level.WARNING, "An error occured grouping the dependencies; duplicate entries may exist in the report", ex); + logger.log(Level.FINE, "Bundling Exception", ex); + } + File outputDir = getReportOutputDirectory(project); + if (outputDir != null) { + ReportingUtil.generateExternalReports(engine, outputDir, project.getName(), format); } } - // + // /** * Returns the output name. * @@ -593,24 +656,6 @@ public class DependencyCheckMojo extends ReportAggregationMojo { return "dependency-check"; } - /** - * Sets the Reporting output directory. - * - * @param directory the output directory - */ - public void setReportOutputDirectory(File directory) { - reportOutputDirectory = directory; - } - - /** - * Returns the output directory. - * - * @return the output directory - */ - public File getReportOutputDirectory() { - return reportOutputDirectory; - } - /** * Gets the description of the Dependency-Check report to be displayed in the Maven Generated Reports page. * @@ -624,24 +669,58 @@ public class DependencyCheckMojo extends ReportAggregationMojo { } /** - * Returns whether this is an external report. - * - * @return true or false; - */ - public boolean isExternalReport() { - return externalReport; - } - - /** - * Returns whether or not the plugin can generate a report. + * Returns whether or not a report can be generated. * * @return true if a report can be generated; otherwise false */ public boolean canGenerateReport() { - return canGenerateNonAggregateReport() || canGenerateAggregateReport(); + if (canGenerateAggregateReport() || (isAggregate() && isMultiModule())) { + return true; + } + if (canGenerateNonAggregateReport()) { + return true; + } else { + final String msg; + if (getProject().getArtifacts().size() > 0) { + msg = "No project dependencies exist in the included scope - dependency-check:check is unable to generate a report."; + } else { + msg = "No project dependencies exist - dependency-check:check is unable to generate a report."; + } + logger.warning(msg); + } + + return false; + } + + /** + * Returns whether or not a non-aggregate report can be generated. + * + * @return true if a non-aggregate report can be generated; otherwise false + */ + @Override + protected boolean canGenerateNonAggregateReport() { + boolean ability = false; + for (Artifact a : getProject().getArtifacts()) { + if (!excludeFromScan(a)) { + ability = true; + break; + } + } + return ability; + } + + /** + * Returns whether or not an aggregate report can be generated. + * + * @return true if an aggregate report can be generated; otherwise false + */ + @Override + protected boolean canGenerateAggregateReport() { + return isAggregate() && isLastProject(); } // + // /** * Checks to see if a vulnerability has been identified with a CVSS score that is above the threshold set in the * configuration. @@ -712,47 +791,86 @@ public class DependencyCheckMojo extends ReportAggregationMojo { logger.log(Level.WARNING, msg); } } + // + // + /** + * Writes the scan data to disk. This is used to serialize the scan data between the "check" and "aggregate" phase. + * + * @return the File object referencing the data file that was written + */ @Override - protected void executeAggregateReport(Sink sink, SinkFactory sinkFactory, Locale locale) throws MavenReportException { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - protected boolean canGenerateNonAggregateReport() { - return true; - } - - @Override - protected boolean canGenerateAggregateReport() { - return isAggregate() && isLastProject(); - } - - @Override - protected String getDataFileName() { - return "dependency-check.ser"; - } - - @Override - protected void writeDataFile() { - if (engine != null) { - File file = new File(project.getBuild().getDirectory(), getDataFileName()); + protected File writeDataFile() { + File file = null; + if (engine != null && getProject().getContextValue(this.getDataFileContextKey()) == null) { + file = new File(getProject().getBuild().getDirectory(), getDataFileName()); try { OutputStream os = new FileOutputStream(file); OutputStream bos = new BufferedOutputStream(os); ObjectOutput out = new ObjectOutputStream(bos); try { - out.writeObject(engine); + out.writeObject(engine.getDependencies()); out.flush(); } finally { out.close(); } - project.setContextValue("dependency-check-path", file.getAbsolutePath()); + //getProject().setContextValue(this.getDataFileContextKey(), file.getAbsolutePath()); } catch (IOException ex) { logger.log(Level.WARNING, "Unable to create data file used for report aggregation; " + "if report aggregation is being used the results may be incomplete."); logger.log(Level.FINE, ex.getMessage(), ex); } } + return file; } + + /** + * Reads the serialized scan data from disk. This is used to serialize the scan data between the "check" and + * "aggregate" phase. + * + * @return a Engine object populated with dependencies if the serialized data file exists; otherwise + * null is returned + */ + protected List readDataFile() { + return readDataFile(getProject()); + } + + /** + * Reads the serialized scan data from disk. This is used to serialize the scan data between the "check" and + * "aggregate" phase. + * + * @param project the Maven project to read the data file from + * @return a Engine object populated with dependencies if the serialized data file exists; otherwise + * null is returned + */ + protected List readDataFile(MavenProject project) { + Object oPath = project.getContextValue(this.getDataFileContextKey()); + if (oPath == null) { + return null; + } + List ret = null; + String path = (String) oPath; + ObjectInputStream ois = null; + try { + ois = new ObjectInputStream(new FileInputStream(path)); + ret = (List) ois.readObject(); + } catch (FileNotFoundException ex) { + //TODO fix logging + logger.log(Level.SEVERE, null, ex); + } catch (IOException ex) { + logger.log(Level.SEVERE, null, ex); + } catch (ClassNotFoundException ex) { + logger.log(Level.SEVERE, null, ex); + } finally { + if (ois != null) { + try { + ois.close(); + } catch (IOException ex) { + logger.log(Level.SEVERE, null, ex); + } + } + } + return ret; + } + // } 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 index 83c00782a..eed113cac 100644 --- 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 @@ -21,19 +21,20 @@ import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Set; 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.MavenReport; import org.apache.maven.reporting.MavenReportException; /** @@ -56,7 +57,7 @@ import org.apache.maven.reporting.MavenReportException; * * @author Jeremy Long */ -public abstract class ReportAggregationMojo extends AbstractMojo implements MavenMultiPageReport { +public abstract class ReportAggregationMojo extends AbstractMojo implements MavenReport { /** * The Maven Project Object. @@ -81,18 +82,80 @@ public abstract class ReportAggregationMojo extends AbstractMojo implements Mave @Parameter(property = "aggregate", defaultValue = "false") private boolean aggregate; - private Map< MavenProject, List< MavenProject>> projectChildren; + /** + * Sets whether or not the external report format should be used. + */ + @Parameter(property = "metaFileName", defaultValue = "dependency-check.ser", required = true) + private String dataFileName; + /** + * Specifies the destination directory for the generated Dependency-Check report. This generally maps to + * "target/site". + */ + @Parameter(property = "reportOutputDirectory", defaultValue = "${project.reporting.outputDirectory}", required = true) + private File reportOutputDirectory; + + /** + * Sets the Reporting output directory. + * + * @param directory the output directory + */ + @Override + public void setReportOutputDirectory(File directory) { + reportOutputDirectory = directory; + } + + /** + * Returns the output directory. + * + * @return the output directory + */ + @Override + public File getReportOutputDirectory() { + return reportOutputDirectory; + } + + public File getReportOutputDirectory(MavenProject project) { + Object o = project.getContextValue(getOutputDirectoryContextKey()); + if (o != null && o instanceof File) { + return (File) o; + } + return null; + } + + /** + * Returns whether this is an external report. This method always returns true. + * + * @return true + */ + @Override + public final boolean isExternalReport() { + return true; + } + + /** + * The collection of child projects. + */ + private final Map< MavenProject, Set> projectChildren = new HashMap>(); protected void preExecute() throws MojoExecutionException, MojoFailureException { - if (this.canGenerateAggregateReport()) { - buildAggregateInfo(); - } + buildAggregateInfo(); } protected abstract void performExecute() throws MojoExecutionException, MojoFailureException; protected void postExecute() throws MojoExecutionException, MojoFailureException { - writeDataFile(); + File written = writeDataFile(); + if (written != null) { + project.setContextValue(getDataFileContextKey(), written.getAbsolutePath()); + } + } + + protected String getDataFileContextKey() { + return "dependency-check-path-" + this.getDataFileName(); + } + + protected String getOutputDirectoryContextKey() { + return "dependency-output-dir-" + this.getDataFileName(); } public final void execute() throws MojoExecutionException, MojoFailureException { @@ -110,9 +173,9 @@ public abstract class ReportAggregationMojo extends AbstractMojo implements Mave * @throws MavenReportException if a maven report exception occurs */ protected void preGenerate() throws MavenReportException { - if (canGenerateAggregateReport()) { - buildAggregateInfo(); - } + buildAggregateInfo(); + + project.setContextValue(getOutputDirectoryContextKey(), getReportOutputDirectory()); } /** @@ -121,28 +184,28 @@ public abstract class ReportAggregationMojo extends AbstractMojo implements Mave * @throws MavenReportException if a maven report exception occurs */ protected void postGenerate() throws MavenReportException { - writeDataFile(); + File written = writeDataFile(); + if (written != null) { + project.setContextValue(getDataFileContextKey(), written.getAbsolutePath()); + } } /** * 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; + protected abstract void executeNonAggregateReport(Locale locale) throws MavenReportException; /** * Generates the aggregate Site Report. * - * @param sink the sink to write the report to - * @param sinkFactory the sink factory + * @param project the maven project used to generate the aggregate report * @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; + protected abstract void executeAggregateReport(MavenProject project, Locale locale) throws MavenReportException; /** * Generates the Dependency-Check Site Report. @@ -150,11 +213,11 @@ public abstract class ReportAggregationMojo extends AbstractMojo implements Mave * @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 - * @deprecated use {@link #generate(org.apache.maven.doxia.sink.Sink, org.apache.maven.doxia.sink.SinkFactory, java.util.Locale) instead. + * @deprecated use {@link #generate(org.apache.maven.doxia.sink.Sink, java.util.Locale) instead. */ @Deprecated public final void generate(@SuppressWarnings("deprecation") org.codehaus.doxia.sink.Sink sink, Locale locale) throws MavenReportException { - generate((Sink) sink, null, locale); + generate((Sink) sink, locale); } /** @@ -163,30 +226,21 @@ public abstract class ReportAggregationMojo extends AbstractMojo implements Mave * @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 - * @deprecated use {@link #generate(org.apache.maven.doxia.sink.Sink, org.apache.maven.doxia.sink.SinkFactory, java.util.Locale) instead. */ - @Deprecated public final void generate(Sink sink, Locale locale) throws MavenReportException { - generate(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); + executeNonAggregateReport(locale); } if (canGenerateAggregateReport()) { - executeAggregateReport(sink, sinkFactory, locale); + for (MavenProject proj : reactorProjects) { + if (!isMultiModule(proj)) { + continue; + } + executeAggregateReport(proj, locale); + } } } finally { postGenerate(); @@ -208,36 +262,30 @@ public abstract class ReportAggregationMojo extends AbstractMojo implements Mave protected abstract boolean canGenerateAggregateReport(); /** - * Returns the data file's names. + * Returns the name of the data file that contains the serialized data. * - * @return the data file's name + * @return the name of the data file that contains the serialized data */ - protected abstract String getDataFileName(); + protected String getDataFileName() { + return dataFileName; + } /** * Writes the data file to disk in the target directory. * + * @return the File object referencing the data file that was written */ - protected abstract void writeDataFile(); + protected abstract File 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()); + Set depList = projectChildren.get(proj.getParent()); if (depList == null) { - depList = new ArrayList(); + depList = new HashSet(); projectChildren.put(proj.getParent(), depList); } depList.add(proj); @@ -259,8 +307,8 @@ public abstract class ReportAggregationMojo extends AbstractMojo implements Mave * @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); + protected List getAllChildren(MavenProject parentProject) { + Set children = projectChildren.get(parentProject); if (children == null) { return Collections.emptyList(); } @@ -276,15 +324,9 @@ public abstract class ReportAggregationMojo extends AbstractMojo implements Mave 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); + protected List getAllChildDataFiles(MavenProject project) { + List children = getAllChildren(project); + return getDataFiles(children); } /** @@ -296,18 +338,21 @@ public abstract class ReportAggregationMojo extends AbstractMojo implements Mave protected List getDataFiles(List projects) { List files = new ArrayList(); for (MavenProject proj : projects) { - if (isMultiModule(proj)) { - continue; - } - //TODO can we get the path from the context? - File outputFile = new File(proj.getBuild().getDirectory(), getDataFileName()); - if (outputFile.exists()) { - files.add(outputFile); + Object path = project.getContextValue(getDataFileContextKey()); + if (path == null) { + final String msg = String.format("Unable to aggregate data for '%s' - aggregate data file was not generated", + proj.getName()); + logger.warning(msg); } 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); + File outputFile = new File((String) path); + 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); + } } } } @@ -318,20 +363,20 @@ public abstract class ReportAggregationMojo extends AbstractMojo implements Mave * Test if the project has pom packaging * * @param mavenProject Project to test - * @return True if it has a pom packaging + * @return true if it has a pom packaging; otherwise false */ - private boolean isMultiModule(MavenProject mavenProject) { + protected boolean isMultiModule(MavenProject mavenProject) { return "pom".equals(mavenProject.getPackaging()); } /** - * Test if the project has pom packaging + * Test if the current project has pom packaging * * @param mavenProject Project to test - * @return True if it has a pom packaging + * @return true if it has a pom packaging; otherwise false */ protected boolean isMultiModule() { - return "pom".equals(project.getPackaging()); + return isMultiModule(project); } /** @@ -353,4 +398,16 @@ public abstract class ReportAggregationMojo extends AbstractMojo implements Mave public boolean isAggregate() { return aggregate; } + + /** + * Returns a reference to the current project. This method is used instead of auto-binding the project via component + * annotation in concrete implementations of this. If the child has a @Component MavenProject project; + * defined then the abstract class (i.e. this class) will not have access to the current project (just the way Maven + * works with the binding). + * + * @return + */ + protected MavenProject getProject() { + return project; + } }