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

com.exadel.aem.toolkit.plugin.maven.PluginMojo Maven / Gradle / Ivy

Go to download

Maven plugin for storing AEM (Granite UI) markup created with Exadel Toolbox Authoring Kit

There is a newer version: 2.5.3
Show newest version
/*
 * 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.
 */
package com.exadel.aem.toolkit.plugin.maven;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.apache.commons.lang3.RegExUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.ConfigurationContainer;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
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;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import org.apache.maven.toolchain.ToolchainManager;
import org.codehaus.plexus.util.cli.CommandLineException;
import org.codehaus.plexus.util.cli.CommandLineUtils;
import org.codehaus.plexus.util.cli.Commandline;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.exadel.aem.toolkit.core.CoreConstants;
import com.exadel.aem.toolkit.plugin.exceptions.PluginException;
import com.exadel.aem.toolkit.plugin.sources.ComponentSource;
import com.exadel.aem.toolkit.plugin.utils.DialogConstants;
import com.exadel.aem.toolkit.plugin.writers.PackageWriter;

/**
 * Represents the entry point of the EToolbox Authoring Kit (the ToolKit) Maven plugin execution
 */
@Mojo(
    name = PluginMojo.PLUGIN_GOAL,
    defaultPhase = LifecyclePhase.PACKAGE,
    requiresDependencyCollection = ResolutionScope.COMPILE
)
@SuppressWarnings({"unused", "MismatchedQueryAndUpdateOfCollection"})
public class PluginMojo extends AbstractMojo {
    private static final Logger LOG = LoggerFactory.getLogger(DialogConstants.ARTIFACT_NAME);

    static final String PLUGIN_GOAL = "aem-authoring";
    private static final String PLUGIN_ARTIFACT_ID = "etoolbox-authoring-kit-plugin";
    private static final String PLUGIN_GROUP = "com.exadel.etoolbox";

    private static final String PROJECT_TYPE_PACKAGE = "content-package";

    private static final String CONFIG_KEY_CLASSPATH_ELEMENTS = "classpathElements";
    private static final String CONFIG_KEY_PATH_BASE = "componentsPathBase";
    private static final String CONFIG_KEY_REFERENCE_BASE = "componentsReferenceBase";
    private static final String CONFIG_KEY_TERMINATE_ON = "terminateOn";

    private static final String DEPENDENCY_RESOLUTION_EXCEPTION_MESSAGE = "Could not resolve dependencies of project %s: %s";
    private static final String PLUGIN_EXECUTION_EXCEPTION_MESSAGE = "%s in module %s: %s";
    private static final String PLUGIN_COMPLETION_MESSAGE = "Execution completed.";
    private static final String PLUGIN_COMPLETION_STATISTICS_MESSAGE = PLUGIN_COMPLETION_MESSAGE + " {} component(-s) processed.";

    private static final String PATTERN_COLOR_CODE = "[^A-Za-z0-0]\\[[0-9;]*m";
    private static final String PATTERN_LOG_LEVEL = "^\\s*\\[[A-Z]+]\\s+";
    private static final Pattern PATTERN_SPLITTER = Pattern.compile(CoreConstants.SEPARATOR_COMMA);

    @Component
    private ToolchainManager toolchainManager;

    // Automatic parameters

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

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

    @Parameter(defaultValue = "${plugin.artifacts}", readonly = true)
    private List pluginDependencies;

    // Customizable parameters

    @Parameter(property = "classpathElements", readonly = true)
    private String classpathElements;

    @Parameter(property = "componentsPathBase", readonly = true)
    private String componentsPathBase;

    @Parameter(property = "componentsReferenceBase", readonly = true)
    private String componentsReferenceBase;

    @Parameter(defaultValue = "java.io.IOException", property = "terminateOn", readonly = true)
    private String terminateOn;

    /**
     * Executes the ToolKit Maven plugin. This is done by initializing {@link PluginRuntime} and then enumerating
     * classpath entries present in the Maven reactor. Relevant AEM component classes (POJOs or Sling models) are
     * extracted and processed with {@link PackageWriter} instance created for a particular Maven project; the result is
     * written down to the AEM package zip file. The method is run once for each package module that has the ToolKit
     * plugin included in the POM file
     * @throws MojoExecutionException if work on a package cannot proceed (due to, e.g., file system failure or improper
     *                                initialization) or in case an internal exception is thrown that corresponds to the
     *                                {@code terminateOn} setting
     */
    public void execute() throws MojoExecutionException {
        if (JvmHelper.shouldRelaunch(toolchainManager, session)) {
            String toolchainJavaExec = JvmHelper.getJavaExecutable(toolchainManager, session);
            LOG.info(
                "Current JVM is {}. Will switch to {}",
                JvmHelper.getJavaHome(),
                JvmHelper.getJavaHome(toolchainJavaExec));
            fork(toolchainJavaExec);
            return;
        }

        PluginSettings.Builder settingsBuilder = PluginSettings.builder()
            .terminateOn(terminateOn)
            .defaultPathBase(componentsPathBase);
        populateReferenceEntries(settingsBuilder);
        PluginSettings pluginSettings = settingsBuilder.build();

        PluginRuntime.contextBuilder()
            .classPathElements(getClasspathElements())
            .settings(pluginSettings)
            .build();

        int processedCount = 0;
        try (PackageWriter packageWriter = PackageWriter.forMavenProject(project)) {
            packageWriter.writeInfo(PluginInfo.getInstance());
            for (ComponentSource component : PluginRuntime.context().getReflection().getComponents(componentsReferenceBase)) {
                processedCount += packageWriter.write(component) ? 1 : 0;
            }
        } catch (PluginException e) {
            throw new MojoExecutionException(String.format(PLUGIN_EXECUTION_EXCEPTION_MESSAGE,
                e.getCause() != null ? e.getCause().getClass().getSimpleName() : e.getClass().getSimpleName(),
                project.getBuild().getFinalName(),
                e.getMessage()), e);
        }

        PluginRuntime.close();

        if (processedCount > 0) {
            LOG.info(PLUGIN_COMPLETION_STATISTICS_MESSAGE, processedCount);
        } else {
            LOG.info(PLUGIN_COMPLETION_MESSAGE);
        }
    }

    /**
     * Restarts the ToolKit's plugin execution with the JDK specified in the current Maven toolchain
     * @param executable Path to Java executable; a non-blank string is expected
     * @throws MojoExecutionException if the plugin process cannot be restarted due to, e.g., missing properties or
     * dependencies
     */
    private void fork(String executable) throws MojoExecutionException {
        Commandline commandline = JvmHelper
            .commandLine()
            .executable(executable)
            .directory(project.getFile().getParent())
            .pluginCommand(String.join(CoreConstants.SEPARATOR_COLON, PLUGIN_GROUP, PLUGIN_ARTIFACT_ID, PLUGIN_GOAL))
            .argument(CONFIG_KEY_CLASSPATH_ELEMENTS, String.join(CoreConstants.SEPARATOR_COMMA, getClasspathElements()))
            .argument(CONFIG_KEY_PATH_BASE, componentsPathBase)
            .argument(CONFIG_KEY_REFERENCE_BASE, componentsReferenceBase)
            .argument(CONFIG_KEY_TERMINATE_ON, terminateOn)
            .build();
        LOG.info("Relaunching plugin with {}", commandline);
        try {
            CommandLineUtils.executeCommandLine(
                commandline,
                line -> relayLogLine(LOG::info, line),
                line -> relayLogLine(LOG::error, line));
        } catch (CommandLineException e) {
            throw new MojoExecutionException("Could not relaunch plugin process", e);
        }
    }

    /**
     * Retrieves the list of classpath elements for the current Maven project
     * @return {@code List} of {@code String} values
     * @throws MojoExecutionException if required dependencies cannot be resolved
     */
    private List getClasspathElements() throws MojoExecutionException {
        Set result;
        try {
            result = new HashSet<>(project.getCompileClasspathElements());
        } catch (DependencyResolutionRequiredException e) {
            throw new MojoExecutionException(String.format(DEPENDENCY_RESOLUTION_EXCEPTION_MESSAGE,
                project.getBuild().getFinalName(),
                e.getMessage()), e);
        }
        if (StringUtils.isNotBlank(this.classpathElements)) {
            PATTERN_SPLITTER
                .splitAsStream(this.classpathElements)
                .filter(StringUtils::isNotBlank)
                .map(String::trim)
                .forEach(result::add);
        }
        pluginDependencies.stream().findFirst().ifPresent(d -> result.add(d.getFile().getPath()));
        return new ArrayList<>(result);
    }

    /**
     * Scans the module structure of the current Maven installation to retrieve the ToolKit's plugin configurations and
     * stores the matches between AEM component Java packages and repository paths. The references are passed to the
     * {@link PluginSettings} builder
     * @param builder {@link PluginSettings.Builder} instance
     */
    private void populateReferenceEntries(PluginSettings.Builder builder) {
        List contentPackages = session
            .getProjectDependencyGraph()
            .getAllProjects()
            .stream()
            .filter(p -> StringUtils.equals(p.getPackaging(), PROJECT_TYPE_PACKAGE))
            .collect(Collectors.toList());

        for (MavenProject contentPackage : contentPackages) {
            Xpp3Dom pluginConfig = contentPackage
                .getBuildPlugins()
                .stream()
                .filter(plugin -> PLUGIN_ARTIFACT_ID.equals(plugin.getArtifactId()))
                .map(ConfigurationContainer::getConfiguration)
                .map(Xpp3Dom.class::cast)
                .findFirst()
                .orElse(null);
            if (pluginConfig == null) {
                continue;
            }
            String pathBase = Optional.ofNullable(pluginConfig.getChild(CONFIG_KEY_PATH_BASE))
                .map(Xpp3Dom::getValue)
                .orElse(null);
            String referenceBase = Optional.ofNullable(pluginConfig.getChild(CONFIG_KEY_REFERENCE_BASE))
                .map(Xpp3Dom::getValue)
                .orElse(null);
            builder.referenceEntry(pathBase, referenceBase);
        }
    }

    /**
     * Transfers to the main logger a line retrieved from the secondary Maven process
     * @param logger A logging method such as {@code .info()} or {@code .error()}
     * @param line   A string value representing the line to log
     */
    private static void relayLogLine(Consumer logger, String line) {
        String effectiveLine = RegExUtils.removePattern(line, PATTERN_LOG_LEVEL);
        effectiveLine = RegExUtils.removePattern(effectiveLine, PATTERN_COLOR_CODE);
        if (StringUtils.isEmpty(effectiveLine)) {
            return;
        }
        logger.accept(effectiveLine.trim());
    }
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy