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

com.exasol.errorcodecrawlermavenplugin.ErrorCodeCrawlerMojo Maven / Gradle / Ivy

package com.exasol.errorcodecrawlermavenplugin;

import static java.util.stream.Collectors.toList;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.stream.Collectors;

import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.*;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.xml.Xpp3Dom;

import com.exasol.errorcodecrawlermavenplugin.config.*;
import com.exasol.errorcodecrawlermavenplugin.crawler.ErrorMessageDeclarationCrawler;
import com.exasol.errorcodecrawlermavenplugin.validation.ErrorMessageDeclarationValidator;
import com.exasol.errorcodecrawlermavenplugin.validation.ErrorMessageDeclarationValidatorFactory;
import com.exasol.errorreporting.ExaError;
import com.exsol.errorcodemodel.*;

/**
 * This class is the entry point of the plugin.
 */
// [impl->dsn~mvn-verify-goal~1]
@Mojo(name = "verify", requiresDependencyResolution = ResolutionScope.TEST, defaultPhase = LifecyclePhase.VERIFY)
public class ErrorCodeCrawlerMojo extends AbstractMojo {
    private static final Path REPORT_PATH = Path.of("target", "error_code_report.json");

    @Parameter(defaultValue = "${project}", required = true, readonly = true)
    private MavenProject project;

    // [impl->dsn~skip-execution~1]
    @Parameter(property = "error-code-crawler.skip", defaultValue = "false")
    String skip;

    /**
     * Glob patterns for files that should be excluded from validation.
     */
    @Parameter(name = "excludes")
    private List excludes;// this variable must have the same name as the parameter

    @Parameter(name = "sourcePaths")
    private List sourcePaths;

    // [impl->dsn~src-directories]
    // [impl->dsn~src-directory-override]
    private List getSourcePaths() {
        if (this.sourcePaths == null || this.sourcePaths.isEmpty()) {
            return List.of(Path.of("src/main/java"));
        } else {
            return this.sourcePaths.stream().map(Path::of).collect(toList());
        }
    }

    private boolean hasCustomSourcePath() {
        return this.sourcePaths != null && !this.sourcePaths.isEmpty();
    }

    /**
     * Check if the plugin is enabled and should run.
     * 
     * @return {@code true} if the plugin is enabled, else {@code false}
     */
    // [impl->dsn~skip-execution~1]
    protected boolean isEnabled() {
        if ("true".equals(this.skip)) {
            getLog().info("Skipping error-code crawling.");
            return false;
        } else if ("false".equals(this.skip)) {
            return true;
        } else {
            throw new IllegalArgumentException(ExaError.messageBuilder("E-ECM-51")
                    .message("Invalid value {{value}} for property 'error-code-crawler.skip'.", this.skip)
                    .mitigation("Please set the property to 'true' or 'false'.").toString());
        }
    }

    @Override
    public void execute() throws MojoFailureException {
        if (isEnabled()) {
            final var projectDir = this.project.getBasedir().toPath();
            final ErrorCodeConfig config = readConfig(projectDir);
            final List classpath = getClasspath();
            getLog().debug("Using classpath " + classpath);
            final var crawler = new ErrorMessageDeclarationCrawler(projectDir, classpath, getJavaSourceVersion(),
                    Objects.requireNonNullElse(this.excludes, Collections.emptyList()));
            final List absoluteSourcePaths = getSourcePaths().stream().map(projectDir::resolve).collect(toList());
            getLog().debug("Crawling " + absoluteSourcePaths.size() + " paths: " + absoluteSourcePaths);
            final var crawlResult = crawler.crawl(absoluteSourcePaths);
            final List findings = validateErrorDeclarations(config, crawlResult);
            createTargetDirIfNotExists();
            List errorMessageDeclarations = crawlResult.getErrorMessageDeclarations();
            if (hasCustomSourcePath()) {
                // [impl->dsn~no-src-location-in-report-for-custom-source-path~1]
                errorMessageDeclarations = removeSourcePositions(errorMessageDeclarations);
            }
            // [impl->dsn~report-writer~1]
            new ErrorCodeReportWriter().writeReport(new ErrorCodeReport(this.project.getArtifactId(),
                    this.project.getVersion(), errorMessageDeclarations), projectDir.resolve(REPORT_PATH));
            reportResult(errorMessageDeclarations.size(), findings);
        }
    }

    private List removeSourcePositions(final List declarations) {
        return declarations.stream().map(ErrorMessageDeclaration::withoutSourcePosition).collect(Collectors.toList());
    }

    private void createTargetDirIfNotExists() {
        final Path targetDir = REPORT_PATH.getParent();
        if (!Files.exists(targetDir)) {
            try {
                Files.createDirectories(targetDir);
            } catch (final IOException exception) {
                throw new IllegalStateException(
                        ExaError.messageBuilder("E-ECM-24")
                                .message("Failed to create 'target/' directory for error-code-report.").toString(),
                        exception);
            }
        }
    }

    private void reportResult(final int numErrorDeclaration, final List findings) throws MojoFailureException {
        final var log = getLog();
        if (!findings.isEmpty()) {
            findings.forEach(finding -> log.error(finding.getMessage()));
            throw new MojoFailureException(ExaError.messageBuilder("E-ECM-3")
                    .message("Error code validation had errors (see previous errors).").toString());
        }
        log.info("Found " + numErrorDeclaration + " valid error message declarations.");
    }

    private List validateErrorDeclarations(final ErrorCodeConfig config,
            final ErrorMessageDeclarationCrawler.Result crawlResult) {
        final List findings = new LinkedList<>();
        findings.addAll(crawlResult.getFindings());
        final ErrorMessageDeclarationValidator validator = new ErrorMessageDeclarationValidatorFactory()
                .getValidator(config);
        findings.addAll(validator.validate(crawlResult.getErrorMessageDeclarations()));
        return findings;
    }

    private ErrorCodeConfig readConfig(final Path projectDir) throws MojoFailureException {
        try {
            return new ErrorCodeConfigReader(projectDir).read();
        } catch (final ErrorCodeConfigException exception) {
            throw new MojoFailureException(exception.getMessage(), exception.getCause());
        }
    }

    private int getJavaSourceVersion() {
        try {
            final var compilerPlugin = this.project.getPlugin("org.apache.maven.plugins:maven-compiler-plugin");
            final Xpp3Dom configuration = (Xpp3Dom) compilerPlugin.getConfiguration();
            final String value = configuration.getChild("source").getValue();
            return Integer.parseInt(value);
        } catch (final Exception exception) {
            final var sourceVersion = 5;
            getLog().warn(ExaError.messageBuilder("W-ECM-14")
                    .message("Failed to read java source version from POM file. Falling back to {{version}}.")
                    .mitigation(
                            "This plugin reads the java source version from the configuration of the maven-compiler-plugin. Check that the version is defined there correctly.")
                    .parameter("version", sourceVersion).toString());
            return sourceVersion;
        }
    }

    /**
     * Get the class path of project under test.
     * 
     * @implNote We skip the first entry of the compile classpath since this are the built classes.
     * 
     * @return the class path
     */
    private List getClasspath() {
        try {
            final List compileClasspath = this.project.getCompileClasspathElements();
            return compileClasspath.stream().skip(1) //
                    .map(Path::of) //
                    .collect(toList());
        } catch (final DependencyResolutionRequiredException exception) {
            throw new IllegalStateException(
                    ExaError.messageBuilder("E-ECM-6").message("Failed to extract project's class path.").toString(),
                    exception);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy