From a615636bca41d2310dcf062d377282521167dfec Mon Sep 17 00:00:00 2001 From: "James R. Perkins" Date: Fri, 19 Jul 2024 10:38:24 -0700 Subject: [PATCH] [54] Refactor the ServerManager.start() methods and configuration. Signed-off-by: James R. Perkins --- README.adoc | 11 +- .../plugin/tools/server/Configuration.java | 303 +++++++++++++++++- .../tools/server/DomainConfiguration.java | 29 ++ .../plugin/tools/server/ServerManager.java | 77 ++--- .../tools/server/ServerStartException.java | 20 +- .../tools/server/StandaloneConfiguration.java | 29 ++ .../org/wildfly/plugin/tools/Environment.java | 8 +- 7 files changed, 386 insertions(+), 91 deletions(-) create mode 100644 src/main/java/org/wildfly/plugin/tools/server/DomainConfiguration.java create mode 100644 src/main/java/org/wildfly/plugin/tools/server/StandaloneConfiguration.java diff --git a/README.adoc b/README.adoc index 18e0c65..61001da 100644 --- a/README.adoc +++ b/README.adoc @@ -90,10 +90,8 @@ and `org.wildfly.plugin.tools.server.StandaloneManager` utilities to interact wi [source,java] ---- -final Path wildflyHome = Paths.get(System.getProperty("user.home"), "servers", "wildfly-10.0.0.Final"); -final Process process = Launcher.of(StandaloneCommandBuilder.of(wildflyHome)).launch(); -try (final ModelControllerClient client = ModelControllerClient.Factory.create(InetAddress.getLocalHost(), 9990)) { - final StandaloneManager serverManager = ServerManager.builder().client(client).process(process).standalone(); +final Path wildflyHome = Paths.get(System.getProperty("user.home"), "servers", "wildfly-33.0.0.Final"); +try (StandaloneManager serverManager = ServerManager.start(Configuration.create(StandaloneCommandBuilder.of(wildflyHome)))) { // Wait at the maximum 30 seconds for the server to start if (!serverManager.waitFor(30, TimeUnit.SECONDS)) { throw new RuntimeException("Server did not start within 30 seconds."); @@ -102,10 +100,5 @@ try (final ModelControllerClient client = ModelControllerClient.Factory.create(I final DeploymentManager deploymentManager = serverManager.deploymentManager(); final Deployment deployment = Deployment.of(deploymentPath); deploymentManager.forceDeploy(deployment).assertSuccess(); - - // Shutdown the standalone server - serverManager.shutdown(); -} finally { - process.destroy(); } ---- \ No newline at end of file diff --git a/src/main/java/org/wildfly/plugin/tools/server/Configuration.java b/src/main/java/org/wildfly/plugin/tools/server/Configuration.java index fef2a34..3751bbf 100644 --- a/src/main/java/org/wildfly/plugin/tools/server/Configuration.java +++ b/src/main/java/org/wildfly/plugin/tools/server/Configuration.java @@ -5,24 +5,81 @@ package org.wildfly.plugin.tools.server; +import java.io.File; import java.io.UncheckedIOException; +import java.lang.ProcessBuilder.Redirect; import java.net.UnknownHostException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.LinkedHashMap; +import java.util.Map; import org.jboss.as.controller.client.ModelControllerClient; +import org.wildfly.core.launcher.BootableJarCommandBuilder; +import org.wildfly.core.launcher.CommandBuilder; +import org.wildfly.core.launcher.DomainCommandBuilder; +import org.wildfly.core.launcher.Launcher; +import org.wildfly.core.launcher.StandaloneCommandBuilder; /** * The configuration used when starting a server. * * @author James R. Perkins */ -public class Configuration { +@SuppressWarnings({ "unused", "UnusedReturnValue" }) +public abstract class Configuration> { + protected enum LaunchType { + DOMAIN, + STANDALONE + } + + private final CommandBuilder commandBuilder; + private final Map env; + private boolean redirectErrorStream; + private Redirect outputDestination; + private Redirect errorDestination; + private File workingDirectory; private ModelControllerClient client; private String managementAddress; private int managementPort; private boolean shutdownOnClose; - public static Configuration create() { - return new Configuration(); + protected Configuration(final CommandBuilder commandBuilder) { + this.commandBuilder = commandBuilder; + this.env = new LinkedHashMap<>(); + } + + /** + * Creates a standalone configuration to launch a standalone server. + * + * @param commandBuilder the standalone command builder used to launch the server + * + * @return a new standalone configuration + */ + public static StandaloneConfiguration create(final StandaloneCommandBuilder commandBuilder) { + return new StandaloneConfiguration(commandBuilder); + } + + /** + * Creates a standalone configuration to launch a standalone server via the bootable JAR. + * + * @param commandBuilder the bootable JAR command builder used to launch the server + * + * @return a new standalone configuration + */ + public static StandaloneConfiguration create(final BootableJarCommandBuilder commandBuilder) { + return new StandaloneConfiguration(commandBuilder); + } + + /** + * Creates a domain configuration to launch a domain server + * + * @param commandBuilder the domain command builder used to launch the server + * + * @return a new domain configuration + */ + public static DomainConfiguration create(final DomainCommandBuilder commandBuilder) { + return new DomainConfiguration(commandBuilder); } /** @@ -35,9 +92,9 @@ public static Configuration create() { * * @return this configuration */ - public Configuration client(final ModelControllerClient client) { + public T client(final ModelControllerClient client) { this.client = client; - return this; + return self(); } /** @@ -64,9 +121,9 @@ protected ModelControllerClient client() { * * @return this configuration */ - public Configuration managementAddress(final String managementAddress) { + public T managementAddress(final String managementAddress) { this.managementAddress = managementAddress; - return this; + return self(); } /** @@ -86,9 +143,9 @@ protected String managementAddress() { * * @return this configuration */ - public Configuration managementPort(final int managementPort) { + public T managementPort(final int managementPort) { this.managementPort = managementPort; - return this; + return self(); } /** @@ -108,9 +165,9 @@ protected int managementPort() { * * @return this configuration */ - public Configuration shutdownOnClose(final boolean shutdownOnClose) { + public T shutdownOnClose(final boolean shutdownOnClose) { this.shutdownOnClose = shutdownOnClose; - return this; + return self(); } /** @@ -121,4 +178,228 @@ public Configuration shutdownOnClose(final boolean shutdownOnClose) { protected boolean shutdownOnClose() { return shutdownOnClose; } + + /** + * Set to {@code true} if the error stream should be redirected to the output stream. + * + * @param redirectErrorStream {@code true} to merge the error stream into the output stream, otherwise {@code false} + * to keep the streams separate + * + * @return the Configuration + */ + public T redirectErrorStream(final boolean redirectErrorStream) { + this.redirectErrorStream = redirectErrorStream; + return self(); + } + + /** + * Redirects the output of the process to a file. + * + * @param file the file to redirect the output to + * + * @return the Configuration + * + * @see Redirect#to(java.io.File) + */ + public T redirectOutput(final File file) { + outputDestination = Redirect.to(file); + return self(); + } + + /** + * Redirects the output of the process to a file. + * + * @param path the path to redirect the output to + * + * @return the Configuration + * + * @see Redirect#to(java.io.File) + */ + public T redirectOutput(final Path path) { + return redirectOutput(path.toFile()); + } + + /** + * Redirects the output of the process to the destination provided. + * + * @param destination the output destination + * + * @return the Configuration + * + * @see java.lang.ProcessBuilder#redirectOutput(Redirect) + */ + public T redirectOutput(final Redirect destination) { + outputDestination = destination; + return self(); + } + + /** + * Checks if the output stream ({@code stdout}) needs to be consumed. + * + * @return {@code true} if the output stream should be consumed, otherwise {@code false} + */ + protected boolean consumeStdout() { + return outputDestination == Redirect.PIPE || outputDestination == null; + } + + /** + * Redirects the error stream of the process to a file. + * + * @param file the file to redirect the error stream to + * + * @return the Configuration + * + * @see Redirect#to(java.io.File) + */ + public T redirectError(final File file) { + errorDestination = Redirect.to(file); + return self(); + } + + /** + * Redirects the error stream of the process to the destination provided. + * + * @param destination the error stream destination + * + * @return the Configuration + * + * @see java.lang.ProcessBuilder#redirectError(Redirect) + */ + public T redirectError(final Redirect destination) { + errorDestination = destination; + return self(); + } + + /** + * Checks if the error stream ({@code stderr}) needs to be consumed. + * + * @return {@code true} if the error stream should be consumed, otherwise {@code false} + */ + protected boolean consumeStderr() { + return !redirectErrorStream && (errorDestination == Redirect.PIPE || errorDestination == null); + } + + /** + * Sets the working directory for the process created. + * + * @param path the path to the working directory + * + * @return the Configuration + * + * @see java.lang.ProcessBuilder#directory(java.io.File) + */ + public T directory(final Path path) { + workingDirectory = (path == null ? null : path.toAbsolutePath().normalize().toFile()); + return self(); + } + + /** + * Sets the working directory for the process created. + * + * @param dir the working directory + * + * @return the Configuration + * + * @see java.lang.ProcessBuilder#directory(java.io.File) + */ + public T directory(final File dir) { + workingDirectory = dir; + return self(); + } + + /** + * Sets the working directory for the process created. + * + * @param dir the working directory + * + * @return the Configuration + * + * @see java.lang.ProcessBuilder#directory(java.io.File) + */ + public T directory(final String dir) { + if (dir == null) + return self(); + Path path = Path.of(dir); + if (Files.notExists(path)) { + throw new IllegalArgumentException(String.format("Directory '%s' does not exist", dir)); + } + if (!Files.isDirectory(path)) { + throw new IllegalArgumentException(String.format("Directory '%s' is not a directory.", dir)); + } + return directory(path.toAbsolutePath().normalize()); + } + + /** + * Adds an environment variable to the process being created. If the key or value is {@code null}, the environment + * variable will not be added. + * + * @param key they key for the variable + * @param value the value for the variable + * + * @return the Configuration + * + * @see ProcessBuilder#environment() + */ + public T addEnvironmentVariable(final String key, final String value) { + if (key != null && value != null) { + env.put(key, value); + } + return self(); + } + + /** + * Adds the environment variables to the process being created. Note that {@code null} keys or values will not be + * added. + * + * @param env the environment variables to add + * + * @return the Configuration + * + * @see ProcessBuilder#environment() + */ + public T addEnvironmentVariables(final Map env) { + env.forEach((key, value) -> { + if (key != null && value != null) { + addEnvironmentVariable(key, value); + } + }); + return self(); + } + + /** + * The command builder used to create the launcher. + * + * @return the command builder + */ + protected CommandBuilder commandBuilder() { + return commandBuilder; + } + + /** + * A configured launcher for create the server process. + * + * @return the configured launcher + */ + protected Launcher launcher() { + return Launcher.of(commandBuilder) + .addEnvironmentVariables(env) + .redirectError(errorDestination) + .redirectOutput(outputDestination) + .setDirectory(workingDirectory) + .setRedirectErrorStream(redirectErrorStream); + } + + /** + * The type of the server to launch. + * + * @return the type of the server + */ + protected abstract LaunchType launchType(); + + /** + * This instance. + * + * @return this instance + */ + protected abstract T self(); } diff --git a/src/main/java/org/wildfly/plugin/tools/server/DomainConfiguration.java b/src/main/java/org/wildfly/plugin/tools/server/DomainConfiguration.java new file mode 100644 index 0000000..e6f5558 --- /dev/null +++ b/src/main/java/org/wildfly/plugin/tools/server/DomainConfiguration.java @@ -0,0 +1,29 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.wildfly.plugin.tools.server; + +import org.wildfly.core.launcher.CommandBuilder; + +/** + * Represents the configuration used to boot a domain server. + * + * @author James R. Perkins + */ +public class DomainConfiguration extends Configuration { + protected DomainConfiguration(final CommandBuilder commandBuilder) { + super(commandBuilder); + } + + @Override + protected LaunchType launchType() { + return LaunchType.DOMAIN; + } + + @Override + protected DomainConfiguration self() { + return this; + } +} diff --git a/src/main/java/org/wildfly/plugin/tools/server/ServerManager.java b/src/main/java/org/wildfly/plugin/tools/server/ServerManager.java index d8d937e..88fd168 100644 --- a/src/main/java/org/wildfly/plugin/tools/server/ServerManager.java +++ b/src/main/java/org/wildfly/plugin/tools/server/ServerManager.java @@ -19,10 +19,9 @@ import org.jboss.as.controller.client.helpers.domain.DomainClient; import org.jboss.dmr.ModelNode; import org.jboss.logging.Logger; -import org.wildfly.core.launcher.BootableJarCommandBuilder; import org.wildfly.core.launcher.DomainCommandBuilder; -import org.wildfly.core.launcher.Launcher; import org.wildfly.core.launcher.StandaloneCommandBuilder; +import org.wildfly.plugin.tools.ConsoleConsumer; import org.wildfly.plugin.tools.ContainerDescription; import org.wildfly.plugin.tools.DeploymentManager; import org.wildfly.plugin.tools.OperationExecutionException; @@ -42,14 +41,14 @@ public interface ServerManager extends AutoCloseable { * A builder used to build a {@link ServerManager}. */ class Builder { - private final Configuration configuration; + private final Configuration configuration; private ProcessHandle process; public Builder() { - this(new Configuration()); + this(new StandaloneConfiguration(null)); } - protected Builder(final Configuration configuration) { + protected Builder(final Configuration configuration) { this.configuration = configuration; } @@ -324,61 +323,28 @@ static Optional launchType(final ModelControllerClient client) { * } * * - * @param commandBuilder the command builder used to start the server - * @param configuration the configuration used for starting and managing the server + * @param configuration the configuration used for starting and managing the server * * @return the server manager * * @throws ServerStartException if an error occurs starting the server */ - static StandaloneManager start(final StandaloneCommandBuilder commandBuilder, - final Configuration configuration) throws ServerStartException { - final Launcher launcher = Launcher.of(commandBuilder) - .inherit(); + static StandaloneManager start(final StandaloneConfiguration configuration) throws ServerStartException { Process process = null; try { - process = launcher.launch(); - return new Builder(configuration).process(process).standalone(); - } catch (Throwable t) { - if (process != null) { - process.destroyForcibly(); + process = configuration.launcher().launch(); + if (configuration.consumeStderr()) { + ConsoleConsumer.start(process.getErrorStream(), System.err); + } + if (configuration.consumeStdout()) { + ConsoleConsumer.start(process.getInputStream(), System.out); } - throw new ServerStartException(commandBuilder, t); - } - } - - /** - * Starts a standalone server based on the {@link BootableJarCommandBuilder command builder} and waits until the - * server is started. - * - *
-     * final ServerManager serverManager = ServerManager.start(BootableJarCommandBuilder.of("web-app-bootable.jar"));
-     * if (!serverManager.waitFor(10L, TimeUnit.SECONDS)) {
-     *     serverManager.kill();
-     *     throw new RuntimeException("Failed to start bootable JAR");
-     * }
-     * 
- * - * @param commandBuilder the command builder used to start the server - * @param configuration the configuration used for starting and managing the server - * - * @return the server manager - * - * @throws ServerStartException if an error occurs starting the server - */ - static StandaloneManager start(final BootableJarCommandBuilder commandBuilder, - final Configuration configuration) throws ServerStartException { - final Launcher launcher = Launcher.of(commandBuilder) - .inherit(); - Process process = null; - try { - process = launcher.launch(); return new Builder(configuration).process(process).standalone(); } catch (Throwable t) { if (process != null) { process.destroyForcibly(); } - throw new ServerStartException(commandBuilder, t); + throw new ServerStartException(configuration, t); } } @@ -394,26 +360,29 @@ static StandaloneManager start(final BootableJarCommandBuilder commandBuilder, * } * * - * @param commandBuilder the command builder used to start the server - * @param configuration the configuration used for starting and managing the server + * @param configuration the configuration used for starting and managing the server * * @return the server manager * * @throws ServerStartException if an error occurs starting the server */ - static DomainManager start(final DomainCommandBuilder commandBuilder, final Configuration configuration) + static DomainManager start(final DomainConfiguration configuration) throws ServerStartException { - final Launcher launcher = Launcher.of(commandBuilder) - .inherit(); Process process = null; try { - process = launcher.launch(); + process = configuration.launcher().launch(); + if (configuration.consumeStderr()) { + ConsoleConsumer.start(process.getErrorStream(), System.err); + } + if (configuration.consumeStdout()) { + ConsoleConsumer.start(process.getInputStream(), System.out); + } return new Builder(configuration).process(process).domain(); } catch (Throwable t) { if (process != null) { process.destroyForcibly(); } - throw new ServerStartException(commandBuilder, t); + throw new ServerStartException(configuration, t); } } diff --git a/src/main/java/org/wildfly/plugin/tools/server/ServerStartException.java b/src/main/java/org/wildfly/plugin/tools/server/ServerStartException.java index 53bb603..75a82cb 100644 --- a/src/main/java/org/wildfly/plugin/tools/server/ServerStartException.java +++ b/src/main/java/org/wildfly/plugin/tools/server/ServerStartException.java @@ -5,27 +5,21 @@ package org.wildfly.plugin.tools.server; -import org.wildfly.core.launcher.CommandBuilder; - /** * Represents a failure when attempting to start a server. * * @author James R. Perkins */ public class ServerStartException extends RuntimeException { - private final CommandBuilder commandBuilder; - ServerStartException(final CommandBuilder commandBuilder, final Throwable cause) { - super("Failed to start server with command: " + commandBuilder.buildArguments(), cause); - this.commandBuilder = commandBuilder; + ServerStartException(final Configuration configuration, final Throwable cause) { + super(createMessage(configuration), cause); } - /** - * The command builder used which caused a boot failure. - * - * @return the command builder which failed to boot - */ - public CommandBuilder getCommandBuilder() { - return commandBuilder; + private static String createMessage(final Configuration configuration) { + if (configuration.commandBuilder() != null) { + return "Failed to start server with command: " + configuration.commandBuilder().build(); + } + return String.format("Failed to start %s server.", configuration.launchType().name().toLowerCase()); } } diff --git a/src/main/java/org/wildfly/plugin/tools/server/StandaloneConfiguration.java b/src/main/java/org/wildfly/plugin/tools/server/StandaloneConfiguration.java new file mode 100644 index 0000000..9a8b034 --- /dev/null +++ b/src/main/java/org/wildfly/plugin/tools/server/StandaloneConfiguration.java @@ -0,0 +1,29 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.wildfly.plugin.tools.server; + +import org.wildfly.core.launcher.CommandBuilder; + +/** + * Represents the configuration used to boot a standalone server. + * + * @author James R. Perkins + */ +public class StandaloneConfiguration extends Configuration { + protected StandaloneConfiguration(final CommandBuilder commandBuilder) { + super(commandBuilder); + } + + @Override + protected LaunchType launchType() { + return LaunchType.STANDALONE; + } + + @Override + protected StandaloneConfiguration self() { + return this; + } +} diff --git a/src/test/java/org/wildfly/plugin/tools/Environment.java b/src/test/java/org/wildfly/plugin/tools/Environment.java index 8614f22..c07adbd 100644 --- a/src/test/java/org/wildfly/plugin/tools/Environment.java +++ b/src/test/java/org/wildfly/plugin/tools/Environment.java @@ -151,8 +151,8 @@ public static StandaloneManager launchStandalone() { } public static StandaloneManager launchStandalone(final boolean shutdownOnClose) { - final StandaloneManager serverManager = ServerManager.start(StandaloneCommandBuilder.of(Environment.WILDFLY_HOME), - Configuration.create() + final StandaloneManager serverManager = ServerManager + .start(Configuration.create(StandaloneCommandBuilder.of(Environment.WILDFLY_HOME)) .shutdownOnClose(shutdownOnClose) .managementAddress(HOSTNAME) .managementPort(PORT)); @@ -168,8 +168,8 @@ public static StandaloneManager launchStandalone(final boolean shutdownOnClose) } public static DomainManager launchDomain() { - final DomainManager serverManager = ServerManager.start(DomainCommandBuilder.of(Environment.WILDFLY_HOME), - Configuration.create() + final DomainManager serverManager = ServerManager + .start(Configuration.create(DomainCommandBuilder.of(Environment.WILDFLY_HOME)) .shutdownOnClose(true) .managementAddress(HOSTNAME) .managementPort(PORT));