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