001/*
002 * Copyright 2017-2023 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.testresources;
017
018import io.micronaut.testresources.buildtools.ServerFactory;
019import io.micronaut.testresources.buildtools.ServerUtils;
020import org.apache.maven.execution.MavenSession;
021import org.apache.maven.plugin.logging.Log;
022import org.apache.maven.toolchain.ToolchainManager;
023
024import java.io.File;
025import java.time.Duration;
026import java.util.ArrayList;
027import java.util.List;
028import java.util.Map;
029import java.util.concurrent.TimeUnit;
030import java.util.concurrent.atomic.AtomicBoolean;
031import java.util.stream.Collectors;
032
033import static io.micronaut.maven.MojoUtils.findJavaExecutable;
034
035/**
036 * Default implementation for {@link ServerFactory}.
037 *
038 * @author Álvaro Sánchez-Mariscal
039 * @since 4.0.0
040 */
041public class DefaultServerFactory implements ServerFactory {
042
043    private final Log log;
044    private final ToolchainManager toolchainManager;
045    private final MavenSession mavenSession;
046    private final AtomicBoolean serverStarted;
047    private final String testResourcesVersion;
048    private final boolean debugServer;
049    private final boolean foreground;
050    private final Map<String, String> testResourcesSystemProperties;
051
052    private Process process;
053
054    public DefaultServerFactory(Log log,
055                                ToolchainManager toolchainManager,
056                                MavenSession mavenSession,
057                                AtomicBoolean serverStarted,
058                                String testResourcesVersion,
059                                boolean debugServer,
060                                boolean foreground, final Map<String, String> testResourcesSystemProperties) {
061        this.log = log;
062        this.toolchainManager = toolchainManager;
063        this.mavenSession = mavenSession;
064        this.serverStarted = serverStarted;
065        this.testResourcesVersion = testResourcesVersion;
066        this.debugServer = debugServer;
067        this.foreground = foreground;
068        this.testResourcesSystemProperties = testResourcesSystemProperties;
069    }
070
071    @Override
072    public void startServer(ServerUtils.ProcessParameters processParameters) {
073        log.info("Starting Micronaut Test Resources service, version " + testResourcesVersion);
074        var cli = computeCliArguments(processParameters);
075
076        if (log.isDebugEnabled()) {
077            log.debug(String.format("Command parameters: %s", String.join(" ", cli)));
078        }
079
080        var builder = new ProcessBuilder(cli);
081        try {
082            process = builder.inheritIO().start();
083            if (foreground) {
084                log.info("Test Resources Service started in foreground. Press Ctrl+C to stop.");
085                process.waitFor();
086            }
087        } catch (InterruptedException e) {
088            log.error("Failed to start server", e);
089            Thread.currentThread().interrupt();
090        } catch (Exception e) {
091            log.error("Failed to start server", e);
092            serverStarted.set(false);
093            if (process != null) {
094                process.destroyForcibly();
095            }
096        } finally {
097            if (process != null) {
098                if (process.isAlive()) {
099                    serverStarted.set(true);
100                } else {
101                    process.destroyForcibly();
102                }
103            }
104        }
105    }
106
107    /**
108     * Computes the command-line arguments required to run the server based on the provided process parameters.
109     *
110     * @param processParameters the process parameters containing information about JVM arguments, system properties,
111     *                          classpath, main class, and program arguments
112     * @return a list of command-line arguments as strings
113     * @throws IllegalStateException if the Java executable cannot be found, or if the main class is not set
114     */
115    List<String> computeCliArguments(ServerUtils.ProcessParameters processParameters) {
116        var cli = new ArrayList<String>();
117
118        String javaBin = findJavaExecutable(toolchainManager, mavenSession);
119        if (javaBin == null) {
120            throw new IllegalStateException("Java executable not found");
121        }
122        cli.add(javaBin);
123        cli.addAll(processParameters.getJvmArguments());
124        if (debugServer) {
125            cli.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8000");
126        }
127        processParameters.getSystemProperties().forEach((key, value) -> cli.add("-D" + key + "=" + value));
128        if (testResourcesSystemProperties != null && !testResourcesSystemProperties.isEmpty()) {
129            testResourcesSystemProperties.forEach((key, value) -> cli.add("-D" + key + "=" + value));
130        }
131        cli.add("-cp");
132        cli.add(processParameters.getClasspath().stream()
133                .map(File::getAbsolutePath)
134                .collect(Collectors.joining(File.pathSeparator)));
135        String mainClass = processParameters.getMainClass();
136        if (mainClass == null) {
137            throw new IllegalStateException("Main class is not set");
138        }
139        cli.add(mainClass);
140        cli.addAll(processParameters.getArguments());
141
142        return cli;
143    }
144
145    @Override
146    public void waitFor(Duration duration) throws InterruptedException {
147        if (process != null) {
148            process.waitFor(duration.toMillis(), TimeUnit.MILLISECONDS);
149        }
150    }
151}