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
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}