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:
Link zum source code:
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