Prozesse ausführen – Apache Commons Exec ™

Externe Prozesse ausführen und umgebungsbezogene Eigenschaften abfragen ist in Java manchmal gar nicht so einfach. Der Entwickler muss ganz genau wissen auf welchem Betriebssystem seine Anwendung läuft. Um diese Aufrufe zu vereinfachen wurde von der Apache Software Foundation in dem Apache Commons Projekt die Bibliothek Apache Commons Exec ™ entwickelt.

Prozesse ausführen

Wir erstellen eine Klasse mit dem Namen ProcessExecutor. Die Methode executeCommand startet einen synchronen Kindprozess und bekommt als Übergabeparameter den Namen des Befehls.

package de.pawlidi.tutorials.exec;

import java.io.IOException;

import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.ExecuteException;
import org.apache.commons.exec.Executor;

public class ProcessExecutor {

  private Executor executor;

  public ProcessExecutor() {
    super();
    executor = new DefaultExecutor();
  }

  public int executeCommand(final String command) throws ExecuteException, IOException {
    int exitValue = -1;

    // create command line without any arguments
    final CommandLine commandLine = new CommandLine(command);

    // starting synchronous child process
    exitValue = executor.execute(commandLine);

    return exitValue;
  }

}

Der Prozess startet hierbei ohne Überwachung d.h. die Anwendung könnte einfrieren sobald der Unterprozess einfriert. Um dieses Problem zu umgehen kann ein Wächter definiert werden. Dieser wird in der Dokumentation auch als Watchdog bezeichnet.

Prozess überwachen

Nun erweitern wir die Methode executeCommand und definieren einen Wächter. Als Parameter wird hierbei die eine Timeout von 60000 Millisekunden angegeben. Damit wird sichergestellt, dass die Ausführungszeit nach einer Minute beendet wird.

package de.pawlidi.tutorials.exec;

import java.io.IOException;

import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.ExecuteException;
import org.apache.commons.exec.ExecuteWatchdog;
import org.apache.commons.exec.Executor;

public class ProcessExecutor {

  private Executor executor;

  public ProcessExecutor() {
    super();
    executor = new DefaultExecutor();
  }
  public int executeCommand(final String command) throws ExecuteException, IOException {
    int exitValue = -1;

    // create command line without any arguments
    final CommandLine commandLine = new CommandLine(command);
    // create process watchdog with timeout 60000 milliseconds
    ExecuteWatchdog watchdog = new ExecuteWatchdog(60000);

    // set watchdog and starting synchronous child process
    executor.setWatchdog(watchdog);
    exitValue = executor.execute(commandLine);

    return exitValue;
  }
}

Argumente übergeben

Oft erwarten externe Prozesse auch Argumente die für die Ausführung notwendig sind. Wir erweitern unsere Methode mit einer Liste von Argumenten, die an den Unterprozess übergeben werden.

package de.pawlidi.tutorials.exec;

import java.io.IOException;

import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.ExecuteException;
import org.apache.commons.exec.ExecuteWatchdog;
import org.apache.commons.exec.Executor;
import org.apache.commons.exec.PumpStreamHandler;

public class ProcessExecutor {

  private Executor executor;

  public ProcessExecutor() {
    super();
    executor = new DefaultExecutor();
  }

  public int executeCommand(final String command, String... args) throws ExecuteException, IOException {
    int exitValue = -1;

    // create command line without any arguments
    final CommandLine commandLine = new CommandLine(command);

    if (args != null && args.length != 0) {
      // add command arguments
      commandLine.addArguments(args);
    }

    // create process watchdog with timeout 60000 milliseconds
    ExecuteWatchdog watchdog = new ExecuteWatchdog(60000);

    // set watchdog and starting synchronous child process
    executor.setWatchdog(watchdog);

    exitValue = executor.execute(commandLine);
    return exitValue;
  }
}

Ergebnisse verarbeiten

Bisher liefern unsere Methoden Ganzzahlen als Rückgabewert des Kindprozesses. Es kommt jedoch auch vor, dass man von einem Prozess mehr als nur eine Zahl erwartet. Um die Ergebnisse des Unterprozesses weiterverarbeiten zu können muss der sogenannte StreamHandler angegeben werden. Dieser verarbeitet den Ein- und Ausgabestrom des Prozesses. Wir definieren eine Klasse ProcessStringOutput und leiten diese von der LogOutputStream Klasse ab. Die abstrakte Klasse LogOutputStream ist für das mitschreiben von Daten im Prozess zuständig.

package de.pawlidi.tutorials.exec;

import org.apache.commons.exec.LogOutputStream;

public class ProcessStringOutput extends LogOutputStream {

  private StringBuilder processOutput;

  public ProcessStringOutput(final int level) {
    super(level);
    this.processOutput = new StringBuilder();
  }

  @Override
  protected void processLine(String line, int logLevel) {
    processOutput.append(line);
    processOutput.append("\n");
  }

  public String getOutput() {
    return processOutput.toString();
  }
}

Als nächstes erweitern wir unsere Klasse ProcessExecutor um eine weitere Methode executeCommandWithOutput um Ergebnisse der Unterprozesse weiterverarbeiten zu könne.

package de.pawlidi.tutorials.exec;

import java.io.IOException;

import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.ExecuteException;
import org.apache.commons.exec.ExecuteWatchdog;
import org.apache.commons.exec.Executor;
import org.apache.commons.exec.PumpStreamHandler;

public class ProcessExecutor {

  private Executor executor;

  public ProcessExecutor() {
    super();
    executor = new DefaultExecutor();
  }

  public int executeCommand(final String command, String... args) throws ExecuteException, IOException {
    int exitValue = -1;

    // create command line without any arguments
    final CommandLine commandLine = new CommandLine(command);

    if (args != null && args.length != 0) {
      // add command arguments
      commandLine.addArguments(args);
    }

    // create process watchdog with timeout 60000 milliseconds
    ExecuteWatchdog watchdog = new ExecuteWatchdog(60000);

    // set watchdog and starting synchronous child process
    executor.setWatchdog(watchdog);

    exitValue = executor.execute(commandLine);
    return exitValue;
  }

  public String executeCommandWithOutput(final String command, String... args) throws ExecuteException, IOException {
    int exitValue = -1;

    // create command line without any arguments
    final CommandLine commandLine = new CommandLine(command);

    if (args != null && args.length != 0) {
      // add command arguments
      commandLine.addArguments(args);
    }

    // create process watchdog with timeout 60000 milliseconds
    ExecuteWatchdog watchdog = new ExecuteWatchdog(60000);

    // set watchdog and starting synchronous child process
    executor.setWatchdog(watchdog);

    // create string output for executor and add as pump handler
    ProcessStringOutput processOutput = new ProcessStringOutput();
    executor.setStreamHandler(new PumpStreamHandler(processOutput, processOutput));

    exitValue = executor.execute(commandLine);

    if (!executor.isFailure(exitValue)) {
      return processOutput.getOutput();
    }
    return null;
  }

}

Test

package de.pawlidi.tutorials.exec;

import java.io.IOException;

import org.apache.commons.exec.ExecuteException;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

public class ProcessExecutorTest {

  private ProcessExecutor executor;

  @Before
  public void setUp() throws Exception {
    executor = new ProcessExecutor();
  }

  @After
  public void tearDown() throws Exception {
    executor = null;
  }

  @Test
  public void testExecuteCommand() throws ExecuteException, IOException {
    int returnValue = executor.executeCommand("cmd");
    Assert.assertEquals(returnValue, 0);
  }

  @Test
  public void testExecuteCommandWithOutput() throws ExecuteException, IOException {
    String returnValue = executor.executeCommandWithOutput("cmd");
    Assert.assertNotNull(returnValue);
  }

}

Links

Verwendete Bibliothek:

Apache Commons Exec 1.3

Link zum source code:

https://github.com/pawlidim/

Link zum Apache Commons Exec ™ Projekt

https://commons.apache.org/proper/commons-exec/

Link zum Apache Commons Exec ™ Tutorials

https://commons.apache.org/proper/commons-exec/tutorial.html