All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.puresoltechnologies.maven.plugins.license.ReportMojo Maven / Gradle / Ivy

There is a newer version: 1.4.0
Show newest version
package com.puresoltechnologies.maven.plugins.license;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;

import org.apache.maven.doxia.module.xhtml.decoration.render.RenderingContext;
import org.apache.maven.doxia.siterenderer.sink.SiteRendererSink;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Execute;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.reporting.MavenReport;
import org.apache.maven.reporting.MavenReportException;
import org.codehaus.doxia.sink.Sink;

import com.puresoltechnologies.maven.plugins.license.internal.DependencyTree;
import com.puresoltechnologies.maven.plugins.license.internal.IOUtilities;
import com.puresoltechnologies.maven.plugins.license.parameter.ArtifactInformation;
import com.puresoltechnologies.maven.plugins.license.parameter.KnownLicense;
import com.puresoltechnologies.maven.plugins.license.parameter.ValidationResult;

/**
 * This maven mojo generates a license report out of information generated by a
 * former 'validate' goal run.
 * 
 * @author Rick-Rainer Ludwig
 */
@Mojo(//
name = "generate-report", //
requiresDirectInvocation = false, //
requiresProject = true, //
requiresReports = true, //
requiresOnline = false, //
inheritByDefault = true, //
threadSafe = true,//
requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME,//
requiresDependencyCollection = ResolutionScope.COMPILE_PLUS_RUNTIME//
)
@Execute(//
goal = "generate-report",//
phase = LifecyclePhase.GENERATE_SOURCES//
)
@SuppressWarnings("deprecation")
public class ReportMojo extends AbstractValidationMojo implements MavenReport {

	/**
	 * Specifies the destination directory where documentation is to be saved
	 * to.
	 */
	@Parameter(property = "destDir", alias = "destDir", defaultValue = "${project.build.directory}/licenses", required = true)
	protected File outputDirectory;

	/**
	 * Specifies the results directory where the results of the validate run
	 * were saved.
	 */
	@Parameter(alias = "resultsDirectory", required = false, defaultValue = "${project.build.directory}/licenses")
	private File resultsDirectory;

	/**
	 * Specified whether or not to skip archetypes with optional flag.
	 */
	@Parameter(alias = "skipOptionals", required = false, defaultValue = "false")
	private boolean skipOptionals;

	/**
	 * This field is filled in {@link #readResults()} started from
	 * {@link #generate(Sink)} with {@link ValidationResult}s.
	 */
	private final Map> results = new HashMap<>();

	/**
	 * This filed is filled with the {@link DependencyTree}.
	 */
	private DependencyTree dependencyTree = null;

	/**
	 * Specified whether or not the dependencies should be checked recursively.
	 * This flag is set with {@link #readSettings()}.
	 */
	private boolean recursive = true;

	/**
	 * Specified whether or not test scope dependencies should be skipped. This
	 * flag is set with {@link #readSettings()}.
	 */
	private boolean skipTestScope = true;

	/**
	 * Specified whether or not provided scope dependencies should be skipped.
	 * This flag is set with {@link #readSettings()}.
	 */
	private boolean skipProvidedScope = true;

	@Override
	public void execute() throws MojoExecutionException, MojoFailureException {
		try {
			RenderingContext context = new RenderingContext(outputDirectory,
					getOutputName() + ".html");
			SiteRendererSink sink = new SiteRendererSink(context);
			Locale locale = Locale.getDefault();
			generate(sink, locale);
		} catch (MavenReportException e) {
			throw new MojoFailureException("An error has occurred in "
					+ getName(Locale.ENGLISH) + " report generation", e);
		}
	}

	@Override
	public boolean canGenerateReport() {
		return true;
	}

	@Override
	public void generate(Sink sink, Locale locale) throws MavenReportException {
		try {
			readSettings();
			readResults();
			dependencyTree = loadArtifacts(recursive, skipTestScope,
					skipProvidedScope, skipOptionals);
			generate(sink);
		} catch (MojoExecutionException e) {
			throw new MavenReportException("Could not generate report.", e);
		}
	}

	/**
	 * Reads the settings from the former validate run and fills
	 * {@link #recursive} and {@link #skipTestScope}.
	 * 
	 * @throws MojoExecutionException
	 */
	private void readSettings() throws MojoExecutionException {
		File file = IOUtilities.getSettingsFile(getLog(), resultsDirectory);
		try (FileInputStream fileOutputStream = new FileInputStream(file);
				InputStreamReader propertiesReader = new InputStreamReader(
						fileOutputStream, Charset.defaultCharset())) {
			Properties properties = new Properties();
			properties.load(propertiesReader);
			recursive = Boolean.valueOf(properties.getProperty("recursive",
					"true"));
			skipTestScope = Boolean.valueOf(properties.getProperty(
					"skipTestScope", "true"));
			skipProvidedScope = Boolean.valueOf(properties.getProperty(
					"skipProvidedScope", "true"));
			skipOptionals = Boolean.valueOf(properties.getProperty(
					"skipOptionals", "true"));
		} catch (IOException e) {
			throw new MojoExecutionException(
					"Could not write settings.properties.", e);
		}
	}

	/**
	 * Reads the results off the validation results file into field
	 * {@link #results}.
	 * 
	 * @throws MojoExecutionException
	 */
	private void readResults() throws MojoExecutionException {
		File resultsFile = IOUtilities.getResultsFile(getLog(),
				resultsDirectory);
		try (FileInputStream fileInputStream = new FileInputStream(resultsFile);
				InputStreamReader inputStreamReader = new InputStreamReader(
						fileInputStream, Charset.defaultCharset());
				BufferedReader bufferedReader = new BufferedReader(
						inputStreamReader);) {
			results.clear();
			for (;;) {
				ValidationResult validationResult = IOUtilities
						.readResult(bufferedReader);
				if (validationResult == null) {
					break;
				}
				ArtifactInformation artifactInformation = validationResult
						.getArtifactInformation();
				List artifactResults = results
						.get(artifactInformation);
				if (artifactResults == null) {
					artifactResults = new ArrayList<>();
					results.put(artifactInformation, artifactResults);
				}
				if (!artifactResults.contains(validationResult)) {
					artifactResults.add(validationResult);
				}
			}
		} catch (IOException e) {
			throw new MojoExecutionException(
					"Could not read license validation resultsf from file '"
							+ resultsFile + "'.");
		}
	}

	/**
	 * This method start the actual generation of the report.
	 * 
	 * @param sink
	 * @throws MavenReportException
	 */
	private void generate(Sink sink) throws MavenReportException {
		try {
			getLog().info("Creating report for licenses.");
			generateHead(sink);
			generateBody(sink);
			sink.flush();
		} finally {
			sink.close();
		}
	}

	/**
	 * Generated the report's HTML head.
	 * 
	 * @param sink
	 */
	private void generateHead(Sink sink) {
		sink.head();
		sink.title();
		sink.text("Licenses Report");
		sink.title_();
		sink.head_();
	}

	/**
	 * Generated the HTML's body.
	 * 
	 * @param sink
	 * @throws MavenReportException
	 */
	private void generateBody(Sink sink) throws MavenReportException {
		sink.body();
		sink.section1();
		sink.sectionTitle1();
		sink.text("Licenses Report");
		sink.sectionTitle1_();
		sink.paragraph();
		sink.text("This report contains an overview of all licenses related to the project and a validation whether these licenses are approved or not.");
		sink.paragraph_();

		sink.section2();
		sink.sectionTitle2();
		sink.text("Directly Used Licenses");
		sink.sectionTitle2_();
		generateDirectDependencyTable(sink);
		sink.section2_();

		sink.section2();
		sink.sectionTitle2();
		sink.text("Transitively Used Licenses");
		sink.sectionTitle2_();
		generateTransitiveDependencyTable(sink);
		sink.section2_();

		sink.section2();
		sink.sectionTitle2();
		sink.text("Dependency Hierarchy");
		sink.sectionTitle2_();
		generateDependencyHierachy(sink);
		sink.section2_();

		sink.section1_();

		sink.body_();
	}

	/**
	 * This method generated the table with the direct dependencies.
	 * 
	 * @param sink
	 * @throws MavenReportException
	 */
	private void generateDirectDependencyTable(Sink sink)
			throws MavenReportException {
		sink.paragraph();
		sink.text("This section contains a list of all licenses which are directly referenced to with dependencies of this maven project.");
		sink.paragraph_();

		sink.table();
		sink.tableCaption();
		sink.text("Licenses");
		sink.tableCaption_();
		generateTableHead(sink);
		generateDirectDependenciesTableContent(sink);
		sink.table_();
	}

	private void generateTableHead(Sink sink) {
		sink.tableRow();
		sink.tableHeaderCell();
		sink.text("License from Artifact");
		sink.tableHeaderCell_();
		sink.tableHeaderCell();
		sink.text("License");
		sink.tableHeaderCell_();
		sink.tableHeaderCell();
		sink.text("Validation");
		sink.tableHeaderCell_();
		sink.tableRow_();
	}

	private void generateDirectDependenciesTableContent(Sink sink)
			throws MavenReportException {
		List dependencies = dependencyTree.getDependencies();
		Map directLicenses = new HashMap<>();
		getLicenses(dependencies, directLicenses);

		for (Entry license : directLicenses
				.entrySet()) {
			String originalLicenseName = license.getKey();
			ValidationResult validationResult = license.getValue();
			URL originalLicenseURL = validationResult.getOriginalLicenseURL();
			sink.tableRow();
			sink.tableCell();
			if (originalLicenseURL == null) {
				sink.text(originalLicenseName);
			} else {
				sink.link(originalLicenseURL.toString());
				sink.text(originalLicenseName);
				sink.link_();
			}
			sink.tableCell_();
			sink.tableCell();
			sink.link(validationResult.getLicense().getUrl().toString());
			sink.text(validationResult.getLicense().getName());
			sink.link_();
			sink.tableCell_();
			sink.tableCell();
			sink.text(validationResult.isValid() ? "valid" : "invalid");
			sink.tableCell_();
			sink.tableRow_();
		}
	}

	private void getLicenses(List dependencyTree,
			Map licenses) {
		for (DependencyTree dependency : dependencyTree) {
			ArtifactInformation artifactInformation = new ArtifactInformation(
					dependency.getArtifact());
			List validationResults = results
					.get(artifactInformation);
			for (ValidationResult validationResult : validationResults) {
				String originalLicenseName = validationResult
						.getOriginalLicenseName();
				if (!licenses.containsKey(originalLicenseName)) {
					licenses.put(originalLicenseName, validationResult);
				}
			}

		}
	}

	/**
	 * This method generated the table with the transitive dependencies.
	 * 
	 * @param sink
	 * @throws MavenReportException
	 */
	private void generateTransitiveDependencyTable(Sink sink)
			throws MavenReportException {
		sink.paragraph();
		sink.text("This section contains a list of all licenses which are "
				+ "not directly referenced to with dependencies of this "
				+ "maven project. All these licenses are coming in "
				+ "transitively via dependencies of the direct project "
				+ "dependencies.");
		sink.paragraph_();

		sink.table();
		sink.tableCaption();
		sink.text("Licenses");
		sink.tableCaption_();
		generateTableHead(sink);
		generateTransitiveDependenciesTableContent(sink);
		sink.table_();
	}

	private void generateTransitiveDependenciesTableContent(Sink sink)
			throws MavenReportException {
		List dependencies = dependencyTree.getDependencies();
		Map transitiveLicenses = new HashMap<>();
		for (DependencyTree dependency : dependencies) {
			for (DependencyTree dependency2 : dependency.getDependencies()) {
				getLicenses(dependency2.getAllDependencies(),
						transitiveLicenses);
			}
		}

		for (Entry license : transitiveLicenses
				.entrySet()) {
			String originalLicenseName = license.getKey();
			ValidationResult validationResult = license.getValue();
			URL originalLicenseURL = validationResult.getOriginalLicenseURL();
			sink.tableRow();
			sink.tableCell();
			if (originalLicenseURL == null) {
				sink.text(originalLicenseName);
			} else {
				sink.link(originalLicenseURL.toString());
				sink.text(originalLicenseName);
				sink.link_();
			}
			sink.tableCell_();
			sink.tableCell();
			sink.link(validationResult.getLicense().getUrl().toString());
			sink.text(validationResult.getLicense().getName());
			sink.link_();
			sink.tableCell_();
			sink.tableCell();
			sink.text(validationResult.isValid() ? "valid" : "invalid");
			sink.tableCell_();
			sink.tableRow_();
		}
	}

	/**
	 * Generates the hierarchy of the dependencies.
	 * 
	 * @param sink
	 */
	private void generateDependencyHierachy(Sink sink) {
		sink.paragraph();
		sink.text("This section contains the full hierarchy of dependencies, its licenses and their validation result.");
		sink.paragraph_();
		generateDependency(sink, dependencyTree);
	}

	/**
	 * Generates the hierarchy of the dependencies recursively.
	 * 
	 * @param sink
	 */
	private void generateDependency(Sink sink, DependencyTree parentDependency) {
		sink.list();
		for (DependencyTree dependency : parentDependency.getDependencies()) {
			sink.listItem();
			ArtifactInformation artifactInformation = new ArtifactInformation(
					dependency.getArtifact());
			getLog().debug("Hierarchy for " + artifactInformation.toString());
			sink.bold();
			sink.text(artifactInformation.toString());
			sink.bold_();
			for (ValidationResult result : results.get(artifactInformation)) {
				KnownLicense license = result.getLicense();
				String originalLicenseName = result.getOriginalLicenseName();
				URL originalLicenseURL = result.getOriginalLicenseURL();
				String valid = result.isValid() ? "valid" : "invalid";
				sink.lineBreak();
				sink.italic();
				sink.text(valid);
				sink.text(": ");
				if (originalLicenseURL == null) {
					sink.text(originalLicenseName);
				} else {
					sink.link(originalLicenseURL.toString());
					sink.text(originalLicenseName);
					sink.link_();
				}
				sink.text(" / ");
				sink.link(license.getUrl().toString());
				sink.text(license.getName());
				sink.link_();
				sink.italic_();
			}
			sink.lineBreak();
			generateDependency(sink, dependency);
			sink.listItem_();
		}
		sink.list_();
	}

	@Override
	public String getCategoryName() {
		return CATEGORY_PROJECT_REPORTS;
	}

	@Override
	public String getDescription(Locale locale) {
		return "Reports all licenses for all dependencies for audit.";
	}

	@Override
	public String getName(Locale locale) {
		return "Licenses Report";
	}

	@Override
	public String getOutputName() {
		return "dependency-licenses-report";
	}

	@Override
	public File getReportOutputDirectory() {
		return outputDirectory;
	}

	@Override
	public boolean isExternalReport() {
		return false;
	}

	@Override
	public void setReportOutputDirectory(File outputDirectory) {
		this.outputDirectory = outputDirectory;
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy