package org.rr.commons.utils; import java.io.IOException; import java.io.OutputStream; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.logging.Level; 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.LogOutputStream; import org.apache.commons.exec.PumpStreamHandler; import org.apache.commons.exec.ShutdownHookProcessDestroyer; import org.apache.commons.io.IOUtils; import org.rr.commons.log.LoggerFactory; import org.rr.commons.mufs.IResourceHandler; import org.rr.commons.mufs.ResourceHandlerFactory; /** * @author Nadav Azaria * @see http://www.javacodegeeks.com/2013/01/executing-a-command-line-executable-from-java.html?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A+JavaCodeGeeks+%28Java+Code+Geeks%29 */ public class ProcessExecutor { public static final Long WATCHDOG_EXIT_VALUE = -999L; public static void runProcessAsScript(final CommandLine commandline, final ProcessExecutorHandler handler, final long watchdogTimeout) throws IOException, InterruptedException, ExecutionException { ExecutorService executor = Executors.newSingleThreadExecutor(); ProcessCallable processCallable; CommandLine commandLineScript = null; try { if(ArrayUtils.isNotEmpty(commandline.getArguments())) { commandLineScript = createCommandLineScript(commandline); processCallable = new ProcessCallable(watchdogTimeout, handler, commandLineScript); } else { processCallable = new ProcessCallable(watchdogTimeout, handler, commandline); } Future<Long> submit = executor.submit(processCallable); submit.get(); } finally { if(commandLineScript != null) { IResourceHandler execResourceHandler = ResourceHandlerFactory.getResourceHandler(commandLineScript.getExecutable()); execResourceHandler.delete(); } } } public static Future<Long> runProcess(final CommandLine commandline, final ProcessExecutorHandler handler, final long watchdogTimeout) throws IOException { ExecutorService executor = Executors.newSingleThreadExecutor(); ProcessCallable processCallable; processCallable = new ProcessCallable(watchdogTimeout, handler, commandline); return executor.submit(processCallable); } private static CommandLine createCommandLineScript(CommandLine commandline) throws ExecutionException { if(ReflectionUtils.getOS() == ReflectionUtils.OS_LINUX) { return createLinuxCommandLineScript(commandline); } else if(ReflectionUtils.getOS() == ReflectionUtils.OS_WINDOWS) { return createWindowsCommandLineScript(commandline); } throw new ExecutionException("Failed to execute " + commandline, null); } private static CommandLine createWindowsCommandLineScript(CommandLine commandline) throws ExecutionException { IResourceHandler temporaryScriptResource = ResourceHandlerFactory.getTemporaryResource("bat"); try (OutputStream contentOutputStream = temporaryScriptResource.getContentOutputStream(false)) { IOUtils.write(commandline.toString(), contentOutputStream); contentOutputStream.flush(); //create command for exec script return new CommandLine(temporaryScriptResource.toFile().toString()); } catch(Exception e) { LoggerFactory.getLogger(ProcessExecutor.class).log(Level.SEVERE, "Could not create script.", e); } throw new ExecutionException("Failed to execute " + commandline, null); } private static CommandLine createLinuxCommandLineScript(CommandLine commandline) throws ExecutionException { StringBuilder script = new StringBuilder(); script.append("#!/bin/sh") .append(StringUtil.NEW_LINE) .append(getCommandLineString(commandline)); IResourceHandler temporaryScriptResource = ResourceHandlerFactory.getTemporaryResource("sh"); try (OutputStream contentOutputStream = temporaryScriptResource.getContentOutputStream(false)) { IOUtils.write(script, contentOutputStream); contentOutputStream.flush(); makeExecutable(temporaryScriptResource); //create command for exec script return new CommandLine(temporaryScriptResource.toFile().toString()); } catch(Exception e) { LoggerFactory.getLogger(ProcessExecutor.class).log(Level.SEVERE, "Could not create script.", e); } throw new ExecutionException("Failed to execute " + commandline, null); } private static void makeExecutable(IResourceHandler temporaryScriptResource) throws InterruptedException, ExecutionException, TimeoutException { ExecutorService executor = Executors.newSingleThreadExecutor(); ProcessCallable processCallable = new ProcessCallable(10000, new LogProcessExecutorHandler(), new CommandLine("chmod").addArgument("u+x").addArgument(temporaryScriptResource.toString(), true)); Future<Long> submit = executor.submit(processCallable); submit.get(10, TimeUnit.SECONDS); } private static String getCommandLineString(final CommandLine commandline) { return commandline.getExecutable() + " " + ArrayUtils.join(commandline.getArguments(), " "); } /** * Replaces the whitespace char with one which did not cause parsing problems. */ public static String saveWhitespaces(String s) { return StringUtil.replace(s, " ", "\u00A0"); } private static class ProcessCallable implements Callable<Long> { /** * the timeout for the process in milliseconds. It must be greater than 0 or {@link ExecuteWatchdog#INFINITE_TIMEOUT} */ private long watchdogTimeout; private ProcessExecutorHandler handler; private CommandLine commandline; private ProcessCallable(long watchdogTimeout, ProcessExecutorHandler handler, CommandLine commandline) { this.watchdogTimeout = watchdogTimeout; this.handler = handler; this.commandline = commandline; } @Override public Long call() throws Exception { Executor executor = new DefaultExecutor(); executor.setProcessDestroyer(new ShutdownHookProcessDestroyer()); ExecuteWatchdog watchDog = new ExecuteWatchdog(watchdogTimeout); executor.setWatchdog(watchDog); executor.setStreamHandler(new PumpStreamHandler(new StreamHandler(handler, StreamHandler.STD_OUT), new StreamHandler(handler, StreamHandler.STD_ERR))); Long exitValue; try { exitValue = new Long(executor.execute(commandline)); } catch (ExecuteException e) { exitValue = new Long(e.getExitValue()); } if (watchDog.killedProcess()) { exitValue = WATCHDOG_EXIT_VALUE; } return exitValue; } } private static class StreamHandler extends LogOutputStream { private static int STD_OUT = 0; private static int STD_ERR = 1; private ProcessExecutorHandler handler; private int type; private StreamHandler(ProcessExecutorHandler handler, int type) { this.handler = handler; this.type = type; } @Override protected void processLine(String line, int level) { if (type == STD_OUT) { handler.onStandardOutput(line); } else { handler.onStandardError(line); } } } public static class LogProcessExecutorHandler implements ProcessExecutorHandler { @Override public void onStandardOutput(String msg) { LoggerFactory.getLogger().log(Level.INFO, msg); } @Override public void onStandardError(String msg) { LoggerFactory.getLogger().log(Level.WARNING, msg); } } public static class EmptyProcessExecutorHandler implements ProcessExecutorHandler { @Override public void onStandardOutput(String msg) { } @Override public void onStandardError(String msg) { } } }