001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.reef.runtime.common.launch; 020 021import org.apache.commons.lang.StringUtils; 022import org.apache.reef.runtime.common.REEFLauncher; 023import org.apache.reef.util.EnvironmentUtils; 024import org.apache.reef.util.Optional; 025 026import java.io.File; 027import java.util.ArrayList; 028import java.util.Collection; 029import java.util.HashMap; 030import java.util.List; 031import java.util.Map; 032import java.util.logging.Logger; 033import java.util.regex.Matcher; 034import java.util.regex.Pattern; 035 036/** 037 * Build the launch command for Java REEF processes. 038 */ 039public final class JavaLaunchCommandBuilder implements LaunchCommandBuilder { 040 private static final Logger LOG = Logger.getLogger(JavaLaunchCommandBuilder.class.getName()); 041 042 private static final String DEFAULT_JAVA_PATH = System.getenv("JAVA_HOME") + "/bin/" + "java"; 043 private static final String[] DEFAULT_OPTIONS = {"-XX:PermSize=128m", "-XX:MaxPermSize=128m"}; 044 private String stderrPath = null; 045 private String stdoutPath = null; 046 private Optional<List<String>> evaluatorConfigurationPaths = Optional.empty(); 047 private String javaPath = null; 048 private String classPath = null; 049 private Boolean assertionsEnabled = null; 050 private final Map<String, JVMOption> options = new HashMap<>(); 051 private final List<String> commandPrefixList; 052 private final Class launcherClass; 053 054 /** 055 * Constructor that populates default options, using the default Launcher 056 * class {@link REEFLauncher}. 057 */ 058 public JavaLaunchCommandBuilder() { 059 this(REEFLauncher.class, null); 060 } 061 062 /** 063 * Constructor that uses the default Launcher class, {@link REEFLauncher}. 064 * @param commandPrefixList 065 */ 066 public JavaLaunchCommandBuilder(final List<String> commandPrefixList) { 067 this(REEFLauncher.class, commandPrefixList); 068 } 069 070 /** 071 * Constructor that populates prefix and uses a custom Launcher class. 072 */ 073 public JavaLaunchCommandBuilder(final Class launcherClass, final List<String> commandPrefixList) { 074 for (final String defaultOption : DEFAULT_OPTIONS) { 075 addOption(defaultOption); 076 } 077 078 this.launcherClass = launcherClass; 079 this.commandPrefixList = commandPrefixList; 080 } 081 082 @Override 083 public List<String> build() { 084 return new ArrayList<String>() {{ 085 if (commandPrefixList != null) { 086 for (final String cmd : commandPrefixList) { 087 add(cmd); 088 } 089 } 090 091 if (javaPath == null || javaPath.isEmpty()) { 092 add(DEFAULT_JAVA_PATH); 093 } else { 094 add(javaPath); 095 } 096 097 if (assertionsEnabled != null && assertionsEnabled 098 || EnvironmentUtils.areAssertionsEnabled()) { 099 addOption("-ea"); 100 } 101 102 for (final JVMOption jvmOption : options.values()) { 103 add(jvmOption.toString()); 104 } 105 106 if (classPath != null && !classPath.isEmpty()) { 107 add("-classpath"); 108 add(classPath); 109 } 110 111 propagateProperties(this, true, "proc_reef"); 112 propagateProperties(this, false, 113 "java.util.logging.config.file", "java.util.logging.config.class"); 114 115 add(launcherClass.getName()); 116 if (evaluatorConfigurationPaths.isPresent()) { 117 for (final String configurationPath : evaluatorConfigurationPaths.get()) { 118 add(configurationPath); 119 } 120 } 121 122 if (stdoutPath != null && !stdoutPath.isEmpty()) { 123 add("1>"); 124 add(stdoutPath); 125 } 126 127 if (stderrPath != null && !stderrPath.isEmpty()) { 128 add("2>"); 129 add(stderrPath); 130 } 131 }}; 132 } 133 134 @Override 135 @SuppressWarnings("checkstyle:hiddenfield") 136 public JavaLaunchCommandBuilder setMemory(final int megaBytes) { 137 return addOption(JVMOption.parse("-Xmx" + megaBytes + "m")); 138 } 139 140 @Override 141 public JavaLaunchCommandBuilder setConfigurationFilePaths(final List<String> configurationPaths) { 142 this.evaluatorConfigurationPaths = Optional.of(configurationPaths); 143 return this; 144 } 145 146 @Override 147 public JavaLaunchCommandBuilder setStandardOut(final String standardOut) { 148 this.stdoutPath = standardOut; 149 return this; 150 } 151 152 @Override 153 public JavaLaunchCommandBuilder setStandardErr(final String standardErr) { 154 this.stderrPath = standardErr; 155 return this; 156 } 157 158 /** 159 * Set the path to the java executable. Will default to a heuristic search if not set. 160 * 161 * @param path Path to the java executable. 162 * @return this 163 */ 164 public JavaLaunchCommandBuilder setJavaPath(final String path) { 165 this.javaPath = path; 166 return this; 167 } 168 169 public JavaLaunchCommandBuilder setClassPath(final String classPath) { 170 this.classPath = classPath; 171 return this; 172 } 173 174 public JavaLaunchCommandBuilder setClassPath(final Collection<String> classPathElements) { 175 this.classPath = StringUtils.join(classPathElements, File.pathSeparatorChar); 176 return this; 177 } 178 179 /** 180 * Add a JVM option. 181 * @param option The full option, e.g. "-XX:+PrintGCDetails" 182 * @return this 183 */ 184 public JavaLaunchCommandBuilder addOption(final String option) { 185 return addOption(JVMOption.parse(option)); 186 } 187 188 /** 189 * Pass values of the properties specified in the propNames array as <code>-D...</code> 190 * command line parameters. Currently used only to pass logging configuration to child JVMs processes. 191 * 192 * @param vargs List of command line parameters to append to. 193 * @param copyNull create an empty parameter if the property is missing in current process. 194 * @param propNames property names. 195 */ 196 private static void propagateProperties( 197 final Collection<String> vargs, final boolean copyNull, final String... propNames) { 198 for (final String propName : propNames) { 199 final String propValue = System.getProperty(propName); 200 if (propValue == null || propValue.isEmpty()) { 201 if (copyNull) { 202 vargs.add("-D" + propName); 203 } 204 } else { 205 vargs.add(String.format("-D%s=%s", propName, propValue)); 206 } 207 } 208 } 209 210 private JavaLaunchCommandBuilder addOption(final JVMOption jvmOption) { 211 if (options.containsKey(jvmOption.option)) { 212 LOG.warning("Replaced option " + options.get(jvmOption.option) + " with " + jvmOption); 213 } 214 options.put(jvmOption.option, jvmOption); 215 return this; 216 } 217 218 /** 219 * Enable or disable assertions on the child process. 220 * If not set, the setting is taken from the JVM that executes the code. 221 * 222 * @param assertionsEnabled If true, enable assertions. 223 * @return this 224 */ 225 @SuppressWarnings("checkstyle:hiddenfield") 226 public JavaLaunchCommandBuilder enableAssertions(final boolean assertionsEnabled) { 227 this.assertionsEnabled = assertionsEnabled; 228 return this; 229 } 230 231 /** 232 * Represents the JVM option as a option and value, combined by a separator. 233 * There are many different JVM option formats. This implementation only recognizes 234 * equals-separated and -Xm[nsx] memory options. All other option formats are 235 * represented with an option and empty value and separator. 236 */ 237 static final class JVMOption { 238 static final Pattern EQUALS = Pattern.compile("(.+)=(.+)"); 239 static final Pattern MEMORY = Pattern.compile("(\\-Xm[nsx])(.+)"); 240 241 public final String option; 242 public final String value; 243 public final String separator; 244 245 private JVMOption(final String option, final String value, 246 final String separator) { 247 this.option = option; 248 this.value = value; 249 this.separator = separator; 250 } 251 252 static JVMOption parse(final String string) { 253 254 final String trimmed = string.trim(); 255 256 final Matcher equalsMatcher = EQUALS.matcher(trimmed); 257 if (equalsMatcher.matches()) { 258 return new JVMOption(equalsMatcher.group(1), equalsMatcher.group(2), "="); 259 } 260 261 final Matcher memoryMatcher = MEMORY.matcher(trimmed); 262 if (memoryMatcher.matches()) { 263 return new JVMOption(memoryMatcher.group(1), memoryMatcher.group(2), ""); 264 } 265 266 // Unknown options return the entire string as the option 267 return new JVMOption(trimmed, "", ""); 268 } 269 270 public String toString() { 271 return option + separator + value; 272 } 273 274 @Override 275 public boolean equals(final Object o) { 276 if (this == o) { 277 return true; 278 } 279 if (o == null || getClass() != o.getClass()) { 280 return false; 281 } 282 283 final JVMOption jvmOption = (JVMOption) o; 284 285 if (!option.equals(jvmOption.option)) { 286 return false; 287 } 288 if (!value.equals(jvmOption.value)) { 289 return false; 290 } 291 return separator.equals(jvmOption.separator); 292 293 } 294 295 @Override 296 public int hashCode() { 297 int result = option.hashCode(); 298 result = 31 * result + value.hashCode(); 299 result = 31 * result + separator.hashCode(); 300 return result; 301 } 302 } 303}