martes, 10 de septiembre de 2019

Llamada a programas EJECUTABLES desde Java

1. Introducción


Uno de los principales problemas que tenía utilizando Java era el no saber como llamar a programas ejecutables.

Por tanto cuando se creaba un proyecto se tenía que ir agregando dependencias hasta que éste se hacía insostenible por su tamaño y problemas entre las versiones de dependencias.

He estado viendo algunos enlaces interesantes :

  • Mkyong : Explica muy bien las dos alternativas que ofrece Java ( ProcessBuilder y Runtime.getRuntime().exec)
  • Zetcode
  • Baeldung que incluye las mejoras de Java 9 para tratar las "pipes"


2. Apache commons exec

La mayor parte de las veces he tenido una buena experiencia con Apache, El problema es que esta libreria es ya algo antigua.

La dependencia maven es:


1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-exec -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-exec</artifactId>
    <version>1.3</version>
</dependency>

Veamos el código fuente de una clase de utilidad que he creado junto con un "main" de prueba.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
package openadmin.utils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

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

public class CmdUtils {
	/**
	 * 
	 * @param commandLine            Line to execute
	 * @param optSuccessValue        (Optional) If the success return is not equal
	 *                               to "0". For instance Acrobat returns 1
	 * @param optMaxDurationMiliSecs (Optional) Maximum wait until kill the program
	 *                               if blocked,
	 * @param isAsync                True if we want to execute the command
	 *                               asynchronously
	 * @param outputStream           ByteArrayOutputStream to collect the output
	 *                               messages of the program
	 * 
	 * @return
	 * @throws ExecuteException
	 * @throws IOException
	 */
	public static int execProgram(String commandLine, Integer optSuccessValue, Long optMaxDurationMiliSecs,
			Boolean isAsync, ByteArrayOutputStream outputStream) throws ExecuteException, IOException {

		int exitValue = 0;
		ExecuteWatchdog watchdog = null;

		CommandLine cmdLine = CommandLine.parse(commandLine);
		DefaultExecutor executor = new DefaultExecutor();

		if (outputStream != null) {
			PumpStreamHandler streamHandler = new PumpStreamHandler(outputStream);
			executor.setStreamHandler(streamHandler);
		}

		if (optSuccessValue != null)
			executor.setExitValue(optSuccessValue);
		if (optMaxDurationMiliSecs != null) {
			watchdog = new ExecuteWatchdog(optMaxDurationMiliSecs);
			executor.setWatchdog(watchdog);
		}
		if (isAsync != null && isAsync)
			executor.execute(cmdLine, new DefaultExecuteResultHandler());

		else
			exitValue = executor.execute(cmdLine);

		return exitValue;
	}

	public static void main(String[] args) {

		ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
		
		String[] commandLines = { "/usr/bin/java -version", "java -version", "ls /home/eduard/",
				"/usr/bin/AutoFirma  sign -i /home/ximo/kk/xmlprueba.xml -o /home/ximo/kk/xml_firma103.xsig -format xades -store mozilla -alias EPN1" };

		int i = 0;
		try {
			for (String s : commandLines) {
				System.out.println(i++ + "-->" + s);
				execProgram(s, null, null, null, outputStream);
				int j=0;
				for (String line : outputStream.toString().split("\\n")) 
					System.out.println("Action:" + i + "." + ++j + "-->" + line);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

}






Aquí se ejecutan 4 comandos (en azul, líneas 63-64) de forma secuencial (en un sistema Ubuntu) con ejecución satisfactoria. Observar que en última sentencia se llama AUTOFIRMA para firmar un documento "xml" utilizando un certificado del repositorio de Mozilla. si se quiere mas detalles, se puede ver un post anterior sobre Autofirma.

Observar que queremos interceptar la salida del comando mostrando las líneas (en el caso del "ls" parece que no va...)

Observar también que se puede ejecutar de forma asíncrona con el parámetro isAsync

Happy coding!