blob: eafc775e8bb20b3924063a481b8c4fbf8e2afc17 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.reef.runtime.common.launch;
import org.apache.commons.lang.StringUtils;
import org.apache.reef.runtime.common.REEFLauncher;
import org.apache.reef.util.EnvironmentUtils;
import org.apache.reef.util.Optional;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Build the launch command for Java REEF processes.
*/
public final class JavaLaunchCommandBuilder implements LaunchCommandBuilder {
private static final Logger LOG = Logger.getLogger(JavaLaunchCommandBuilder.class.getName());
private static final String DEFAULT_JAVA_PATH = "{{JAVA_HOME}}/bin/java";
private static final String[] DEFAULT_OPTIONS = {"-XX:PermSize=128m", "-XX:MaxPermSize=128m"};
private final Map<String, JVMOption> options = new HashMap<>();
private final List<String> commandPrefixList;
private final Class launcherClass;
private String stderrPath = null;
private String stdoutPath = null;
private Optional<List<String>> evaluatorConfigurationPaths = Optional.empty();
private String javaPath = null;
private String classPath = null;
private Boolean assertionsEnabled = null;
/**
* Constructor that populates default options, using the default Launcher
* class {@link REEFLauncher}.
*/
public JavaLaunchCommandBuilder() {
this(REEFLauncher.class, null);
}
/**
* Constructor that uses the default Launcher class, {@link REEFLauncher}.
* @param commandPrefixList
*/
public JavaLaunchCommandBuilder(final List<String> commandPrefixList) {
this(REEFLauncher.class, commandPrefixList);
}
/**
* Constructor that populates prefix and uses a custom Launcher class.
*/
public JavaLaunchCommandBuilder(final Class launcherClass, final List<String> commandPrefixList) {
for (final String defaultOption : DEFAULT_OPTIONS) {
addOption(defaultOption);
}
this.launcherClass = launcherClass;
this.commandPrefixList = commandPrefixList;
}
@Override
public List<String> build() {
return new ArrayList<String>() {{
if (commandPrefixList != null) {
for (final String cmd : commandPrefixList) {
add(cmd);
}
}
if (javaPath == null || javaPath.isEmpty()) {
add(DEFAULT_JAVA_PATH);
} else {
add(javaPath);
}
if (assertionsEnabled != null && assertionsEnabled
|| EnvironmentUtils.areAssertionsEnabled()) {
addOption("-ea");
}
for (final JVMOption jvmOption : options.values()) {
add(jvmOption.toString());
}
if (classPath != null && !classPath.isEmpty()) {
add("-classpath");
add(classPath);
}
propagateProperties(this, true, "proc_reef");
propagateProperties(this, false,
"java.util.logging.config.file", "java.util.logging.config.class");
add(launcherClass.getName());
if (evaluatorConfigurationPaths.isPresent()) {
for (final String configurationPath : evaluatorConfigurationPaths.get()) {
add(configurationPath);
}
}
if (stdoutPath != null && !stdoutPath.isEmpty()) {
add("1>");
add(stdoutPath);
}
if (stderrPath != null && !stderrPath.isEmpty()) {
add("2>");
add(stderrPath);
}
}};
}
@Override
@SuppressWarnings("checkstyle:hiddenfield")
public JavaLaunchCommandBuilder setMemory(final int megaBytes) {
return addOption(JVMOption.parse("-Xmx" + megaBytes + "m"));
}
@Override
public JavaLaunchCommandBuilder setConfigurationFilePaths(final List<String> configurationPaths) {
this.evaluatorConfigurationPaths = Optional.of(configurationPaths);
return this;
}
@Override
public JavaLaunchCommandBuilder setStandardOut(final String standardOut) {
this.stdoutPath = standardOut;
return this;
}
@Override
public JavaLaunchCommandBuilder setStandardErr(final String standardErr) {
this.stderrPath = standardErr;
return this;
}
/**
* Set the path to the java executable. Will default to a heuristic search if not set.
*
* @param path Path to the java executable.
* @return this
*/
public JavaLaunchCommandBuilder setJavaPath(final String path) {
this.javaPath = path;
return this;
}
public JavaLaunchCommandBuilder setClassPath(final String classPath) {
this.classPath = classPath;
return this;
}
public JavaLaunchCommandBuilder setClassPath(final Collection<String> classPathElements) {
this.classPath = StringUtils.join(classPathElements, File.pathSeparatorChar);
return this;
}
/**
* Add a JVM option.
* @param option The full option, e.g. "-XX:+PrintGCDetails"
* @return this
*/
public JavaLaunchCommandBuilder addOption(final String option) {
return addOption(JVMOption.parse(option));
}
/**
* Pass values of the properties specified in the propNames array as <code>-D...</code>
* command line parameters. Currently used only to pass logging configuration to child JVMs processes.
*
* @param vargs List of command line parameters to append to.
* @param copyNull create an empty parameter if the property is missing in current process.
* @param propNames property names.
*/
private static void propagateProperties(
final Collection<String> vargs, final boolean copyNull, final String... propNames) {
for (final String propName : propNames) {
final String propValue = System.getProperty(propName);
if (propValue == null || propValue.isEmpty()) {
if (copyNull) {
vargs.add("-D" + propName);
}
} else {
vargs.add(String.format("-D%s=%s", propName, propValue));
}
}
}
private JavaLaunchCommandBuilder addOption(final JVMOption jvmOption) {
if (options.containsKey(jvmOption.option)) {
LOG.warning("Replaced option " + options.get(jvmOption.option) + " with " + jvmOption);
}
options.put(jvmOption.option, jvmOption);
return this;
}
/**
* Enable or disable assertions on the child process.
* If not set, the setting is taken from the JVM that executes the code.
*
* @param assertionsEnabled If true, enable assertions.
* @return this
*/
@SuppressWarnings("checkstyle:hiddenfield")
public JavaLaunchCommandBuilder enableAssertions(final boolean assertionsEnabled) {
this.assertionsEnabled = assertionsEnabled;
return this;
}
/**
* Represents the JVM option as a option and value, combined by a separator.
* There are many different JVM option formats. This implementation only recognizes
* equals-separated and -Xm[nsx] memory options. All other option formats are
* represented with an option and empty value and separator.
*/
static final class JVMOption {
static final Pattern EQUALS = Pattern.compile("(.+)=(.+)");
static final Pattern MEMORY = Pattern.compile("(\\-Xm[nsx])(.+)");
final String option;
final String value;
final String separator;
private JVMOption(final String option, final String value,
final String separator) {
this.option = option;
this.value = value;
this.separator = separator;
}
static JVMOption parse(final String string) {
final String trimmed = string.trim();
final Matcher equalsMatcher = EQUALS.matcher(trimmed);
if (equalsMatcher.matches()) {
return new JVMOption(equalsMatcher.group(1), equalsMatcher.group(2), "=");
}
final Matcher memoryMatcher = MEMORY.matcher(trimmed);
if (memoryMatcher.matches()) {
return new JVMOption(memoryMatcher.group(1), memoryMatcher.group(2), "");
}
// Unknown options return the entire string as the option
return new JVMOption(trimmed, "", "");
}
public String toString() {
return option + separator + value;
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final JVMOption jvmOption = (JVMOption) o;
if (!option.equals(jvmOption.option)) {
return false;
}
if (!value.equals(jvmOption.value)) {
return false;
}
return separator.equals(jvmOption.separator);
}
@Override
public int hashCode() {
int result = option.hashCode();
result = 31 * result + value.hashCode();
result = 31 * result + separator.hashCode();
return result;
}
}
}