/* * JBoss, Home of Professional Open Source. * Copyright 2015, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.wildfly.core.launcher; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.wildfly.core.launcher.Arguments.Argument; import org.wildfly.core.launcher.logger.LauncherMessages; /** * Builds a list of commands to create a new process for a CLI instance. * <p> * This builder is not thread safe and the same instance should not be used in multiple threads. * </p> * * @author <a href="mailto:jperkins@redhat.com">James R. Perkins</a> */ @SuppressWarnings("unused") public class CliCommandBuilder implements CommandBuilder { enum CliArgument { CONNECT("--connect", "-c"), CONTROLLER("--controller", "controller"), GUI("--gui"), FILE("--file"), COMMAND("--command"), COMMANDS("--commands"), USER("--user", "-u"), PASSWORD("--password", "-p"), TIMEOUT("--timeout"),; private static final Map<String, CliArgument> ENTRIES; static { final Map<String, CliArgument> map = new HashMap<>(); for (CliArgument arg : values()) { map.put(arg.key, arg); if (arg.altKey != null) { map.put(arg.altKey, arg); } } ENTRIES = Collections.unmodifiableMap(map); } public static CliArgument find(final String key) { return ENTRIES.get(key); } public static CliArgument find(final Argument argument) { return ENTRIES.get(argument.getKey()); } private final String key; private final String altKey; CliArgument(final String key) { this(key, null); } CliArgument(final String key, final String altKey) { this.key = key; this.altKey = altKey; } } private final Environment environment; private final Arguments javaOpts; private final Arguments cliArgs; private CliCommandBuilder(final Environment environment) { this.environment = environment; javaOpts = new Arguments(); cliArgs = new Arguments(); // Add the default logging.properties file javaOpts.add("-Dlogging.configuration=file:" + environment.resolvePath("bin", "jboss-cli-logging.properties")); } /** * Creates a command builder for a CLI instance. * * @param wildflyHome the path to the WildFly home directory * * @return a new builder */ public static CliCommandBuilder of(final Path wildflyHome) { return new CliCommandBuilder(new Environment(wildflyHome)); } /** * Creates a command builder for a CLI instance. * * @param wildflyHome the path to the WildFly home directory * * @return a new builder */ public static CliCommandBuilder of(final String wildflyHome) { return new CliCommandBuilder(new Environment(wildflyHome)); } /** * Sets the hostname and port to connect to. * <p> * This sets both the {@code --connect} and {@code --controller} arguments. * </p> * * @param controller the controller argument to use * * @return the builder */ public CliCommandBuilder setConnection(final String controller) { addCliArgument(CliArgument.CONNECT); setController(controller); return this; } /** * Sets the hostname and port to connect to. * <p> * This sets both the {@code --connect} and {@code --controller} arguments. * </p> * * @param hostname the host name * @param port the port * * @return the builder */ public CliCommandBuilder setConnection(final String hostname, final int port) { addCliArgument(CliArgument.CONNECT); setController(hostname, port); return this; } /** * Sets the protocol, hostname and port to connect to. * <p> * This sets both the {@code --connect} and {@code --controller} arguments. * </p> * * @param protocol the protocol to use * @param hostname the host name * @param port the port * * @return the builder */ public CliCommandBuilder setConnection(final String protocol, final String hostname, final int port) { addCliArgument(CliArgument.CONNECT); setController(protocol, hostname, port); return this; } /** * Sets the hostname and port to connect to. * * @param controller the controller argument to use * * @return the builder */ public CliCommandBuilder setController(final String controller) { addCliArgument(CliArgument.CONTROLLER, controller); return this; } /** * Sets the hostname and port to connect to. * * @param hostname the host name * @param port the port * * @return the builder */ public CliCommandBuilder setController(final String hostname, final int port) { setController(formatAddress(null, hostname, port)); return this; } /** * Sets the protocol, hostname and port to connect to. * * @param protocol the protocol to use * @param hostname the host name * @param port the port * * @return the builder */ public CliCommandBuilder setController(final String protocol, final String hostname, final int port) { setController(formatAddress(protocol, hostname, port)); return this; } /** * Sets the user to use when establishing a connection. * * @param user the user to use * * @return the builder */ public CliCommandBuilder setUser(final String user) { addCliArgument(CliArgument.USER, user); return this; } /** * Sets the password to use when establishing a connection. * * @param password the password to use * * @return the builder */ public CliCommandBuilder setPassword(final String password) { addCliArgument(CliArgument.PASSWORD, password); return this; } /** * Sets the path to the script file to execute. * * @param path the path to the script file to execute * * @return the builder */ public CliCommandBuilder setScriptFile(final String path) { if (path == null) { addCliArgument(CliArgument.FILE, null); return this; } return setScriptFile(Paths.get(path)); } /** * Sets the path to the script file to execute. * * @param path the path to the script file to execute * * @return the builder */ public CliCommandBuilder setScriptFile(final Path path) { if (path == null) { addCliArgument(CliArgument.FILE, null); } else { // Make sure the path exists if (Files.notExists(path)) { throw LauncherMessages.MESSAGES.pathDoesNotExist(path); } addCliArgument(CliArgument.FILE, path.toString()); } return this; } /** * Sets the command to execute. * * @param command the command to execute * * @return the builder */ public CliCommandBuilder setCommand(final String command) { addCliArgument(CliArgument.COMMAND, command); return this; } /** * Sets the commands to execute. * * @param commands the commands to execute * * @return the builder */ public CliCommandBuilder setCommands(final String... commands) { if (commands == null || commands.length == 0) { addCliArgument(CliArgument.COMMANDS, null); return this; } return setCommands(Arrays.asList(commands)); } /** * Sets the commands to execute. * * @param commands the commands to execute * * @return the builder */ public CliCommandBuilder setCommands(final Iterable<String> commands) { if (commands == null) { addCliArgument(CliArgument.COMMANDS, null); return this; } final StringBuilder cmds = new StringBuilder(); for (final Iterator<String> iterator = commands.iterator(); iterator.hasNext(); ) { cmds.append(iterator.next()); if (iterator.hasNext()) cmds.append(','); } addCliArgument(CliArgument.COMMANDS, cmds.toString()); return this; } /** * Sets the timeout used when connecting to the server. * * @param timeout the time out to use * * @return the builder */ public CliCommandBuilder setTimeout(final int timeout) { if (timeout > 0) { addCliArgument(CliArgument.TIMEOUT, Integer.toString(timeout)); } else { addCliArgument(CliArgument.TIMEOUT, null); } return this; } /** * Sets the command argument to use the GUI CLI client. * * @return the builder */ public CliCommandBuilder setUseGui() { if (Environment.isMac()) { addJavaOption("-Djboss.modules.system.pkgs=com.apple.laf,com.apple.laf.resources"); } else { addJavaOption("-Djboss.modules.system.pkgs=com.sun.java.swing"); } addCliArgument(CliArgument.GUI); return this; } /** * Adds a JVM argument to the command ignoring {@code null} arguments. * * @param jvmArg the JVM argument to add * * @return the builder */ public CliCommandBuilder addJavaOption(final String jvmArg) { if (jvmArg != null && !jvmArg.trim().isEmpty()) { javaOpts.add(jvmArg); } return this; } /** * Adds the array of JVM arguments to the command. * * @param javaOpts the array of JVM arguments to add, {@code null} arguments are ignored * * @return the builder */ public CliCommandBuilder addJavaOptions(final String... javaOpts) { if (javaOpts != null) { for (String javaOpt : javaOpts) { addJavaOption(javaOpt); } } return this; } /** * Adds the collection of JVM arguments to the command. * * @param javaOpts the collection of JVM arguments to add, {@code null} arguments are ignored * * @return the builder */ public CliCommandBuilder addJavaOptions(final Iterable<String> javaOpts) { if (javaOpts != null) { for (String javaOpt : javaOpts) { addJavaOption(javaOpt); } } return this; } /** * Sets the JVM arguments to use. This overrides any default JVM arguments that would normally be added and ignores * {@code null} values in the collection. * <p/> * If the collection is {@code null} the JVM arguments will be cleared and no new arguments will be added. * * @param javaOpts the JVM arguments to use * * @return the builder */ public CliCommandBuilder setJavaOptions(final Iterable<String> javaOpts) { this.javaOpts.clear(); return addJavaOptions(javaOpts); } /** * Sets the JVM arguments to use. This overrides any default JVM arguments that would normally be added and ignores * {@code null} values in the array. * <p/> * If the array is {@code null} the JVM arguments will be cleared and no new arguments will be added. * * @param javaOpts the JVM arguments to use * * @return the builder */ public CliCommandBuilder setJavaOptions(final String... javaOpts) { this.javaOpts.clear(); return addJavaOptions(javaOpts); } /** * Returns the JVM arguments. * * @return the JVM arguments */ public List<String> getJavaOptions() { return javaOpts.asList(); } /** * Adds an argument to be passed to the CLI command ignore the argument if {@code null}. * * @param arg the argument to pass * * @return the builder */ public CliCommandBuilder addCliArgument(final String arg) { if (arg != null) { final Argument argument = Arguments.parse(arg); final CliArgument cliArgument = CliArgument.find(argument.getKey()); if (cliArgument != null) { // Remove the alternate key if required if (cliArgument.altKey != null) { cliArgs.remove(cliArgument.altKey); } } cliArgs.set(argument); } return this; } /** * Adds the arguments to the collection of arguments that will be passed to the CLI command ignoring any {@code * null} arguments. * * @param args the arguments to add * * @return the builder */ public CliCommandBuilder addCliArguments(final String... args) { if (args != null) { for (String arg : args) { addCliArgument(arg); } } return this; } /** * Adds the arguments to the collection of arguments that will be passed to the CLI command ignoring any {@code * null} arguments. * * @param args the arguments to add * * @return the builder */ public CliCommandBuilder addCliArguments(final Iterable<String> args) { if (args != null) { for (String arg : args) { addCliArgument(arg); } } return this; } /** * Adds a directory to the collection of module paths. * * @param moduleDir the module directory to add * * @return the builder * * @throws java.lang.IllegalArgumentException if the path is {@code null} */ public CliCommandBuilder addModuleDir(final String moduleDir) { environment.addModuleDir(moduleDir); return this; } /** * Adds all the module directories to the collection of module paths. * * @param moduleDirs an array of module paths to add * * @return the builder * * @throws java.lang.IllegalArgumentException if any of the module paths are invalid or {@code null} */ public CliCommandBuilder addModuleDirs(final String... moduleDirs) { environment.addModuleDirs(moduleDirs); return this; } /** * Adds all the module directories to the collection of module paths. * * @param moduleDirs a collection of module paths to add * * @return the builder * * @throws java.lang.IllegalArgumentException if any of the module paths are invalid or {@code null} */ public CliCommandBuilder addModuleDirs(final Iterable<String> moduleDirs) { environment.addModuleDirs(moduleDirs); return this; } /** * Replaces any previously set module directories with the collection of module directories. * <p/> * The default module directory will <i>NOT</i> be used if this method is invoked. * * @param moduleDirs the collection of module directories to use * * @return the builder * * @throws java.lang.IllegalArgumentException if any of the module paths are invalid or {@code null} */ public CliCommandBuilder setModuleDirs(final Iterable<String> moduleDirs) { environment.setModuleDirs(moduleDirs); return this; } /** * Replaces any previously set module directories with the array of module directories. * <p/> * The default module directory will <i>NOT</i> be used if this method is invoked. * * @param moduleDirs the array of module directories to use * * @return the builder * * @throws java.lang.IllegalArgumentException if any of the module paths are invalid or {@code null} */ public CliCommandBuilder setModuleDirs(final String... moduleDirs) { environment.setModuleDirs(moduleDirs); return this; } /** * Returns the modules paths used on the command line. * * @return the paths separated by the {@link java.io.File#pathSeparator path separator} */ public String getModulePaths() { return environment.getModulePaths(); } /** * Sets the Java home where the Java executable can be found. * * @param javaHome the Java home or {@code null} to use te system property {@code java.home} * * @return the builder */ public CliCommandBuilder setJavaHome(final String javaHome) { environment.setJavaHome(javaHome); return this; } /** * Sets the Java home where the Java executable can be found. * * @param javaHome the Java home or {@code null} to use te system property {@code java.home} * * @return the builder */ public CliCommandBuilder setJavaHome(final Path javaHome) { environment.setJavaHome(javaHome); return this; } /** * Returns the Java home directory where the java executable command can be found. * <p/> * If the directory was not set the system property value, {@code java.home}, should be used. * * @return the path to the Java home directory */ public Path getJavaHome() { return environment.getJavaHome(); } @Override public List<String> buildArguments() { final List<String> cmd = new ArrayList<>(); cmd.addAll(getJavaOptions()); cmd.add("-jar"); cmd.add(environment.getModuleJar().toString()); cmd.add("-mp"); cmd.add(getModulePaths()); cmd.add("org.jboss.as.cli"); cmd.add("-D" + Environment.HOME_DIR + "=" + environment.getWildflyHome()); cmd.addAll(cliArgs.asList()); return cmd; } @Override public List<String> build() { final List<String> cmd = new ArrayList<>(); cmd.add(environment.getJavaCommand()); cmd.addAll(buildArguments()); return cmd; } private CliCommandBuilder addCliArgument(final CliArgument cliArgument) { if (cliArgument.altKey != null) { cliArgs.remove(cliArgument.altKey); } cliArgs.set(Arguments.parse(cliArgument.key)); return this; } private CliCommandBuilder addCliArgument(final CliArgument cliArgument, final String value) { if (cliArgument.altKey != null) { cliArgs.remove(cliArgument.altKey); } cliArgs.set(cliArgument.key, value); return this; } private static String formatAddress(final String protocol, final String hostname, final int port) { final char first = hostname.charAt(0); if (first == '[' && hostname.charAt(hostname.length() - 1) != ']') { throw LauncherMessages.MESSAGES.invalidHostname(hostname); } boolean wrapIpv6 = false; if (first != '[' && (first == ':' || Character.digit(first, 16) != -1)) { int counter = 0; for (char c : hostname.toCharArray()) { if (c == ':') { counter++; } else if (Character.digit(c, 16) == -1) { // Not IPV6 break; } if (counter > 1) { wrapIpv6 = true; break; } } } if (protocol == null) { if (wrapIpv6) { return String.format("[%s]:%d", hostname, port); } return String.format("%s:%d", hostname, port); } if (wrapIpv6) { return String.format("%s://[%s]:%d", protocol, hostname, port); } return String.format("%s://%s:%d", protocol, hostname, port); } }