This project has retired. For details please refer to its Attic page.
Source code
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}