/*
* JBoss, Home of Professional Open Source.
* Copyright 2014, 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.Collection;
import java.util.List;
import org.wildfly.core.launcher.logger.LauncherMessages;
/**
* @author <a href="mailto:jperkins@redhat.com">James R. Perkins</a>
*/
@SuppressWarnings("unused")
abstract class AbstractCommandBuilder<T extends AbstractCommandBuilder<T>> implements CommandBuilder {
static final String HOME_DIR = Environment.HOME_DIR;
static final String SECURITY_MANAGER_ARG = "-secmgr";
static final String SECURITY_MANAGER_PROP = "java.security.manager";
static final String[] DEFAULT_VM_ARGUMENTS;
static {
final Collection<String> javaOpts = new ArrayList<>();
// Default JVM parameters for all versions
javaOpts.add("-Xms64m");
javaOpts.add("-Xmx512m");
javaOpts.add("-Djava.net.preferIPv4Stack=true");
javaOpts.add("-Djava.awt.headless=true");
javaOpts.add("-Djboss.modules.system.pkgs=org.jboss.byteman");
// Versions below 8 should add a MaxPermSize
if (Environment.supportsMaxPermSize()) {
javaOpts.add("-XX:MaxPermSize=256m");
}
DEFAULT_VM_ARGUMENTS = javaOpts.toArray(new String[javaOpts.size()]);
}
protected final Environment environment;
private boolean useSecMgr;
private Path logDir;
private Path configDir;
private final Arguments serverArgs;
protected AbstractCommandBuilder(final Path wildflyHome) {
this(wildflyHome, null);
}
protected AbstractCommandBuilder(final Path wildflyHome, final Path javaHome) {
environment = new Environment(wildflyHome);
environment.setJavaHome(javaHome);
useSecMgr = false;
serverArgs = new Arguments();
}
/**
* Sets whether or not the security manager option, {@code -secmgr}, should be used.
*
* @param useSecMgr {@code true} to use the a security manager, otherwise {@code false}
*
* @return the builder
*/
public T setUseSecurityManager(final boolean useSecMgr) {
this.useSecMgr = useSecMgr;
return getThis();
}
/**
* Indicates whether or no a security manager should be used for the server launched.
*
* @return {@code true} if a security manager should be used, otherwise {@code false}
*/
public boolean useSecurityManager() {
return useSecMgr;
}
/**
* 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 T addModuleDir(final String moduleDir) {
environment.addModuleDir(moduleDir);
return getThis();
}
/**
* 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 T addModuleDirs(final String... moduleDirs) {
environment.addModuleDirs(moduleDirs);
return getThis();
}
/**
* 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 T addModuleDirs(final Iterable<String> moduleDirs) {
environment.addModuleDirs(moduleDirs);
return getThis();
}
/**
* 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 T setModuleDirs(final Iterable<String> moduleDirs) {
environment.setModuleDirs(moduleDirs);
return getThis();
}
/**
* 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 T setModuleDirs(final String... moduleDirs) {
environment.setModuleDirs(moduleDirs);
return getThis();
}
/**
* 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();
}
/**
* Returns the log directory for the server.
*
* @return the log directory
*/
public Path getLogDirectory() {
if (logDir == null) {
return normalizePath(getBaseDirectory(), "log");
}
return logDir;
}
/**
* Sets the log directory to be used for log files.
* <p/>
* If set to {@code null}, the default, the log directory will be resolved.
*
* @param path the path to the log directory or {@code null} to have the log directory resolved
*
* @return the builder
*
* @throws java.lang.IllegalArgumentException if the directory is not a valid directory
*/
public T setLogDirectory(final String path) {
if (path == null) {
logDir = null;
return getThis();
}
return setLogDirectory(Paths.get(path));
}
/**
* Sets the log directory to be used for log files.
* <p/>
* If set to {@code null}, the default, the log directory will be resolved.
*
* @param path the path to the log directory or {@code null} to have the log directory resolved
*
* @return the builder
*
* @throws java.lang.IllegalArgumentException if the directory is not a valid directory
*/
public T setLogDirectory(final Path path) {
if (path == null) {
logDir = null;
} else {
if (Files.exists(path) && !Files.isDirectory(path)) {
throw LauncherMessages.MESSAGES.invalidDirectory(path);
}
logDir = path.toAbsolutePath().normalize();
}
return getThis();
}
/**
* Returns the configuration directory for the server.
*
* @return the configuration directory
*/
public Path getConfigurationDirectory() {
if (configDir == null) {
return normalizePath(getBaseDirectory(), "configuration");
}
return configDir;
}
/**
* Sets the configuration directory to be used for configuration files.
* <p/>
* If set to {@code null}, the default, the configuration directory will be resolved.
*
* @param path the path to the configuration directory or {@code null} to have the configuration directory resolved
*
* @return the builder
*
* @throws java.lang.IllegalArgumentException if the directory is not a valid directory
*/
public T setConfigurationDirectory(final String path) {
configDir = validateAndNormalizeDir(path, true);
return getThis();
}
/**
* Sets the configuration directory to be used for configuration files.
* <p/>
* If set to {@code null}, the default, the configuration directory will be resolved.
*
* @param path the path to the configuration directory or {@code null} to have the configuration directory resolved
*
* @return the builder
*
* @throws java.lang.IllegalArgumentException if the directory is not a valid directory
*/
public T setConfigurationDirectory(final Path path) {
configDir = validateAndNormalizeDir(path, true);
return getThis();
}
/**
* Adds an argument to be passed to the server ignore the argument if {@code null}.
*
* @param arg the argument to pass
*
* @return the builder
*/
public T addServerArgument(final String arg) {
if (arg != null) {
if (SECURITY_MANAGER_ARG.equals(arg)) {
setUseSecurityManager(true);
} else {
serverArgs.add(arg);
}
}
return getThis();
}
/**
* Adds the arguments to the collection of arguments that will be passed to the server ignoring any {@code null}
* arguments.
*
* @param args the arguments to add
*
* @return the builder
*/
public T addServerArguments(final String... args) {
if (args != null) {
for (String arg : args) {
addServerArgument(arg);
}
}
return getThis();
}
/**
* Adds the arguments to the collection of arguments that will be passed to the server ignoring any {@code null}
* arguments.
*
* @param args the arguments to add
*
* @return the builder
*/
public T addServerArguments(final Iterable<String> args) {
if (args != null) {
for (String arg : args) {
addServerArgument(arg);
}
}
return getThis();
}
/**
* Sets the server argument {@code --admin-only}.
*
* @return the builder
*/
public T setAdminOnly() {
return addServerArgument("--admin-only");
}
/**
* Sets the server argument {@code --start-mode=suspend}.
*
* @return the builder
*/
public T setStartSuspended() {
return addServerArgument("--start-mode=suspend");
}
/**
* Sets the system property {@code jboss.bind.address} to the address given.
* <p/>
* This will override any previous value set via {@link #addServerArgument(String)}.
* <p/>
* <b>Note:</b> This option only works if the standard system property has not been removed from the interface. If
* the system property was removed the address provided has no effect.
*
* @param address the address to set the bind address to
*
* @return the builder
*/
public T setBindAddressHint(final String address) {
setSingleServerArg("-b", address);
return getThis();
}
/**
* Sets the system property {@code jboss.bind.address.$INTERFACE} to the address given where {@code $INTERFACE} is
* the {@code interfaceName} parameter. For example in the default configuration passing {@code management} for the
* {@code interfaceName} parameter would result in the system property {@code jboss.bind.address.management} being
* set to the address provided.
* <p/>
* This will override any previous value set via {@link #addServerArgument(String)}.
* <p/>
* <b>Note:</b> This option only works if the standard system property has not been removed from the interface. If
* the system property was removed the address provided has no effect.
*
* @param interfaceName the name of the interface of the binding address
* @param address the address to bind the management interface to
*
* @return the builder
*/
public T setBindAddressHint(final String interfaceName, final String address) {
if (interfaceName == null) {
throw LauncherMessages.MESSAGES.nullParam("interfaceName");
}
setSingleServerArg("-b" + interfaceName, address);
return getThis();
}
/**
* Sets the system property {@code jboss.default.multicast.address} to the address given.
* <p/>
* This will override any previous value set via {@link #addServerArgument(String)}.
* <p/>
* <b>Note:</b> This option only works if the standard system property has not been removed from the interface. If
* the system property was removed the address provided has no effect.
*
* @param address the address to set the multicast system property to
*
* @return the builder
*/
public T setMulticastAddressHint(final String address) {
setSingleServerArg("-u", address);
return getThis();
}
/**
* Adds a properties file to be passed to the server. Note that the file must exist.
*
* @param file the file to add
*
* @return the builder
*
* @throws java.lang.IllegalArgumentException if the file does not exist or is not a regular file
*/
public T addPropertiesFile(final String file) {
if (file != null) {
addPropertiesFile(Paths.get(file));
}
return getThis();
}
/**
* Adds a properties file to be passed to the server. Note that the file must exist.
*
* @param file the file to add
*
* @return the builder
*
* @throws java.lang.IllegalArgumentException if the file does not exist or is not a regular file
*/
public T addPropertiesFile(final Path file) {
if (file != null) {
if (Files.notExists(file)) {
throw LauncherMessages.MESSAGES.pathDoesNotExist(file);
}
if (!Files.isRegularFile(file)) {
throw LauncherMessages.MESSAGES.pathNotAFile(file);
}
addServerArg("-P", file.toAbsolutePath().normalize().toString());
}
return getThis();
}
/**
* Sets the properties file to use for the server or {@code null} to remove the file. Note that the file must exist.
* <p>
* This will override any previous values set.
* </p>
*
* @param file the properties file to use or {@code null}
*
* @return the builder
*
* @throws java.lang.IllegalArgumentException if the file does not exist or is not a regular file
*/
public T setPropertiesFile(final String file) {
final Path path;
if (file == null) {
path = null;
} else {
path = Paths.get(file);
}
return setPropertiesFile(path);
}
/**
* Sets the properties file to use for the server or {@code null} to remove the file. Note that the file must exist.
* <p>
* This will override any previous values set.
* </p>
*
* @param file the properties file to use or {@code null}
*
* @return the builder
*
* @throws java.lang.IllegalArgumentException if the file does not exist or is not a regular file
*/
public T setPropertiesFile(final Path file) {
serverArgs.remove("-P");
return addPropertiesFile(file);
}
/**
* Returns the home directory used.
*
* @return the home directory
*/
public Path getWildFlyHome() {
return environment.getWildflyHome();
}
/**
* 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 abstract Path getJavaHome();
/**
* The java executable command found in the {@link #getJavaHome() Java home} directory.
*
* @return the java executable command
*/
protected String getJavaCommand() {
return getJavaCommand(getJavaHome());
}
/**
* The java executable command found in the Java home directory.
* <p/>
* If the directory contains a space the command is returned with quotes surrounding it.
*
* @param javaHome the path to the Java home directory
*
* @return the java executable command
*/
protected String getJavaCommand(final Path javaHome) {
return environment.getJavaCommand(javaHome);
}
/**
* Returns the command line argument that specifies the logging configuration.
*
* @param fileName the name of the configuration file
*
* @return the command line argument
*/
protected String getLoggingPropertiesArgument(final String fileName) {
return "-Dlogging.configuration=file:" + normalizePath(getConfigurationDirectory(), fileName);
}
/**
* Returns the command line argument that specifies the path the log file that should be used.
* <p/>
* This is generally only used on the initial boot of the server in standalone. The server will rewrite the
* configuration file with hard-coded values.
*
* @param fileName the name of the file
*
* @return the command line argument
*/
protected String getBootLogArgument(final String fileName) {
return "-Dorg.jboss.boot.log.file=" + normalizePath(getLogDirectory(), fileName);
}
/**
* Returns the normalized path to the {@code jboss-modules.jar} for launching the server.
*
* @return the path to {@code jboss-modules.jar}
*/
public String getModulesJarName() {
return environment.getModuleJar().toString();
}
/**
* Resolves the path to the path relative to the WildFly home directory.
*
* @param path the name of the relative path
*
* @return the normalized path
*/
protected Path normalizePath(final String path) {
return environment.resolvePath(path).toAbsolutePath().normalize();
}
/**
* Resolves the path relative to the parent and normalizes it.
*
* @param parent the parent path
* @param path the path
*
* @return the normalized path
*/
protected Path normalizePath(final Path parent, final String path) {
return parent.resolve(path).toAbsolutePath().normalize();
}
/**
* Returns the base directory for the server.
* <p/>
* Example:
* For standalone the base directory would be {@code $JBOSS_HOME/standalone}.
*
* @return the base directory
*/
public abstract Path getBaseDirectory();
/**
* A collection of server command line arguments.
*
* @return the server arguments
*/
public List<String> getServerArguments() {
return serverArgs.asList();
}
/**
* Returns the concrete builder.
*
* @return the concrete builder
*/
protected abstract T getThis();
protected void setSingleServerArg(final String key, final String value) {
serverArgs.set(key, value);
}
protected void addServerArg(final String key, final String value) {
serverArgs.add(key, value);
}
/**
* Returns the first argument from the server arguments.
*
* @param key the key to the argument
*
* @return the first argument or {@code null}
*/
protected String getServerArg(final String key) {
return serverArgs.get(key);
}
@Deprecated
protected static Path resolveJavaHome(final Path javaHome) {
if (javaHome == null) {
return validateJavaHome(Environment.getDefaultJavaHome());
}
return validateJavaHome(javaHome);
}
protected static Path validateWildFlyDir(final String wildflyHome) {
return Environment.validateWildFlyDir(wildflyHome);
}
protected static Path validateWildFlyDir(final Path wildflyHome) {
return Environment.validateWildFlyDir(wildflyHome);
}
protected static Path validateJavaHome(final String javaHome) {
return Environment.validateJavaHome(javaHome);
}
protected static Path validateJavaHome(final Path javaHome) {
return Environment.validateJavaHome(javaHome);
}
protected static Path validateAndNormalizeDir(final String path, final boolean allowNull) {
if (path == null) {
if (allowNull) return null;
throw LauncherMessages.MESSAGES.pathDoesNotExist(null);
}
return validateAndNormalizeDir(Paths.get(path), allowNull);
}
protected static Path validateAndNormalizeDir(final Path path, final boolean allowNull) {
if (allowNull && path == null) return null;
if (path == null || Files.notExists(path)) {
throw LauncherMessages.MESSAGES.pathDoesNotExist(path);
}
if (!Files.isDirectory(path)) {
throw LauncherMessages.MESSAGES.invalidDirectory(path);
}
return path.toAbsolutePath().normalize();
}
protected static void addSystemPropertyArg(final List<String> cmd, final String key, final Object value) {
if (value != null) {
cmd.add("-D" + key + "=" + value);
}
}
}