001/*
002 * Copyright 2017-2022 original authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package io.micronaut.maven.services;
017
018import io.micronaut.core.util.StringUtils;
019import io.micronaut.maven.InvocationResultWithOutput;
020import io.micronaut.maven.InvocationResultWithOutput.PerGoalOutputHandler;
021import org.apache.maven.execution.MavenSession;
022import org.apache.maven.model.Plugin;
023import org.apache.maven.model.PluginExecution;
024import org.apache.maven.plugin.BuildPluginManager;
025import org.apache.maven.plugin.MojoExecutionException;
026import org.apache.maven.project.MavenProject;
027import org.apache.maven.shared.invoker.DefaultInvocationRequest;
028import org.apache.maven.shared.invoker.Invoker;
029import org.apache.maven.shared.invoker.MavenInvocationException;
030import org.codehaus.plexus.util.xml.Xpp3Dom;
031import org.slf4j.Logger;
032import org.slf4j.LoggerFactory;
033import org.twdata.maven.mojoexecutor.MojoExecutor;
034
035import javax.inject.Inject;
036import javax.inject.Singleton;
037import java.io.File;
038import java.util.Arrays;
039import java.util.Optional;
040import java.util.Properties;
041import java.util.concurrent.atomic.AtomicReference;
042
043import static io.micronaut.maven.testresources.TestResourcesConfiguration.TEST_RESOURCES_ENABLED_PROPERTY;
044import static org.twdata.maven.mojoexecutor.MojoExecutor.configuration;
045import static org.twdata.maven.mojoexecutor.MojoExecutor.executeMojo;
046import static org.twdata.maven.mojoexecutor.MojoExecutor.executionEnvironment;
047import static org.twdata.maven.mojoexecutor.MojoExecutor.goal;
048import static org.twdata.maven.mojoexecutor.MojoExecutor.plugin;
049
050/**
051 * Provides methods to execute goals on the current project.
052 *
053 * @author Álvaro Sánchez-Mariscal
054 * @since 1.1
055 */
056@Singleton
057public class ExecutorService {
058
059    private static final Logger LOG = LoggerFactory.getLogger(ExecutorService.class);
060
061    private final MojoExecutor.ExecutionEnvironment executionEnvironment;
062    private final MavenProject mavenProject;
063    private final MavenSession mavenSession;
064    private final Invoker invoker;
065
066    @SuppressWarnings("CdiInjectionPointsInspection")
067    @Inject
068    public ExecutorService(MavenProject mavenProject, MavenSession mavenSession, BuildPluginManager pluginManager,
069                           Invoker invoker) {
070        this.executionEnvironment = executionEnvironment(mavenProject, mavenSession, pluginManager);
071        this.mavenProject = mavenProject;
072        this.mavenSession = mavenSession;
073        this.invoker = invoker;
074    }
075
076    /**
077     * Executes the given goal from the given plugin coordinates.
078     *
079     * @param pluginKey The plugin coordinates in the format groupId:artifactId:version
080     * @param goal The goal to execute
081     * @throws MojoExecutionException If the goal execution fails
082     */
083    public void executeGoal(String pluginKey, String goal) throws MojoExecutionException {
084        final Plugin plugin = mavenProject.getPlugin(pluginKey);
085        if (plugin != null) {
086            var executionId = new AtomicReference<>(goal);
087            if (goal != null && goal.indexOf('#') > -1) {
088                int pos = goal.indexOf('#');
089                executionId.set(goal.substring(pos + 1));
090                goal = goal.substring(0, pos);
091            }
092            Optional<PluginExecution> execution = plugin
093                .getExecutions()
094                .stream()
095                .filter(e -> e.getId().equals(executionId.get()))
096                .findFirst();
097            Xpp3Dom configuration;
098            if (execution.isPresent()) {
099                configuration = (Xpp3Dom) execution.get().getConfiguration();
100            } else if (plugin.getConfiguration() != null) {
101                configuration = (Xpp3Dom) plugin.getConfiguration();
102            } else {
103                configuration = configuration();
104            }
105            executeMojo(plugin, goal(goal), configuration, executionEnvironment);
106        } else {
107            throw new MojoExecutionException("Plugin not found: " + pluginKey);
108        }
109    }
110
111    /**
112     * Executes a goal using the given arguments.
113     *
114     * @param pluginGroup plugin group id
115     * @param pluginArtifact plugin artifact id
116     * @param pluginVersion plugin version
117     * @param goal goal to execute
118     * @param configuration configuration for the goal
119     * @throws MojoExecutionException if the goal execution fails
120     */
121    public void executeGoal(String pluginGroup, String pluginArtifact, String pluginVersion, String goal, Xpp3Dom configuration) throws MojoExecutionException {
122        final Plugin plugin = plugin(pluginGroup, pluginArtifact, pluginVersion);
123        executeMojo(plugin, goal(goal), configuration, executionEnvironment);
124    }
125
126    /**
127     * Executes a goal using the Maven shared invoker.
128     *
129     * @param pluginKey The plugin coordinates in the format groupId:artifactId
130     * @param goal The goal to execute
131     * @return The result of the invocation
132     * @throws MavenInvocationException If the goal execution fails
133     */
134    public InvocationResultWithOutput invokeGoal(String pluginKey, String goal) throws MavenInvocationException {
135        return invokeGoals(pluginKey + ":" + goal);
136    }
137
138    /**
139     * Executes a goal using the Maven shared invoker.
140     *
141     * @param goals The goals to execute
142     * @return The result of the invocation
143     * @throws MavenInvocationException If the goal execution fails
144     */
145    public InvocationResultWithOutput invokeGoals(String... goals) throws MavenInvocationException {
146        return invokeGoals(mavenProject, goals);
147    }
148
149    /**
150     * Executes a goal using the Maven shared invoker.
151     *
152     * @param project The Maven project
153     * @param goals The goals to execute
154     * @return The result of the invocation
155     * @throws MavenInvocationException If the goal execution fails
156     */
157    public InvocationResultWithOutput invokeGoals(MavenProject project, String... goals) throws MavenInvocationException {
158        var request = new DefaultInvocationRequest();
159        request.setPomFile(project.getFile());
160        File settingsFile = mavenSession.getRequest().getUserSettingsFile();
161        if (settingsFile.exists()) {
162            request.setUserSettingsFile(settingsFile);
163        }
164
165        var properties = new Properties();
166        //Pass through Jib properties
167        System.getProperties()
168                .entrySet()
169                .stream()
170                .filter(e -> e.getKey().toString().startsWith("jib"))
171                .forEach(e -> properties.put(e.getKey(), e.getValue()));
172        properties.put(TEST_RESOURCES_ENABLED_PROPERTY, StringUtils.FALSE);
173        request.setProperties(properties);
174
175        request.setLocalRepositoryDirectory(new File(mavenSession.getLocalRepository().getBasedir()));
176        request.setGoals(Arrays.asList(goals));
177        request.setBatchMode(true);
178        request.setQuiet(false);
179        request.setAlsoMake(true);
180
181        var outputHandler = new PerGoalOutputHandler();
182        request.setOutputHandler(outputHandler);
183        request.setErrorHandler(outputHandler);
184
185        var result = invoker.execute(request);
186        return new InvocationResultWithOutput(result, outputHandler);
187    }
188}