package org.openslx.taskmanager.api; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Map; import org.apache.log4j.Logger; public abstract class SystemCommandTask extends AbstractTask { private static final Logger log = Logger.getLogger( SystemCommandTask.class ); private String[] command = null; private Process process = null; protected int timeoutSeconds = 0; @Override protected final boolean execute() { try { return execInternal(); } catch ( Exception e ) { log.warn( "Unexpected exception when executing " + getId() + ": " + e.toString() ); processStdErrInternal( e.toString() ); return processEnded( -3 ); } } private final boolean execInternal() { command = initCommandLine(); if ( command == null || command.length == 0 ) { return processEnded( -1 ); } for (String a : command) { if (a == null) { log.warn( "An argument from initCommandLine is null: " + Arrays.toString( command ) ); return processEnded( -5 ); } } ProcessBuilder pb = new ProcessBuilder( command ); pb.directory( new File( "/" ) ); initEnvironment( pb.environment() ); try { // Create process try { process = pb.start(); } catch ( Exception e ) { log.warn( "Process of task " + getId() + " died.", e ); processStdErrInternal( e.toString() ); return processEnded( -2 ); } final Process p = process; processStarted(); p.getOutputStream(); // Read its stdout Thread stdout = new Thread( new Runnable() { @Override public void run() { try ( BufferedReader reader = new BufferedReader( new InputStreamReader( p.getInputStream() ) ) ) { String line; while ( ( line = reader.readLine() ) != null ) { synchronized ( p ) { processStdOutInternal( line ); } } } catch ( Exception e ) { e.printStackTrace(); } } } ); // Read its stderr Thread stderr = new Thread( new Runnable() { @Override public void run() { try ( BufferedReader reader = new BufferedReader( new InputStreamReader( p.getErrorStream() ) ) ) { String line; while ( ( line = reader.readLine() ) != null ) { synchronized ( p ) { processStdErrInternal( line ); } } } catch ( Exception e ) { e.printStackTrace(); } } } ); stdout.start(); stderr.start(); // Wait for everything int retval = 124; // Default to 124, which is what the timeout util does if ( this.timeoutSeconds <= 0 ) { retval = process.waitFor(); } else { int togo = timeoutSeconds * 10; while ( togo-- > 0 ) { try { retval = process.exitValue(); break; } catch ( IllegalThreadStateException e1 ) { // Still running.... try { Thread.sleep( 100 ); } catch ( Exception e2 ) { // Bummer.... } } } } try { stdout.join( 500 ); stderr.join( 500 ); } catch ( Throwable t ) { } try { process.getErrorStream().close(); } catch ( Throwable t ) { } try { process.getOutputStream().close(); } catch ( Throwable t ) { } try { process.getInputStream().close(); } catch ( Throwable t ) { } synchronized ( p ) { return processEnded( retval ); } } catch ( InterruptedException e ) { processEnded( -4 ); Thread.currentThread().interrupt(); return false; } finally { if ( process != null ) process.destroy(); } } /** * Write data to the process's stdin. * * @param data stuff to write * @return success or failure mapped to a boolean in a really complicated way */ protected final boolean toStdIn( byte[] data ) { try { process.getOutputStream().write( data ); } catch ( IOException e ) { e.printStackTrace(); return false; } return true; } /** * Write text to the process's stdin. * * @param text stuff to write * @return success or failure mapped to a boolean in a really complicated way */ protected final boolean toStdIn( String text ) { return toStdIn( text.getBytes( StandardCharsets.UTF_8 ) ); } private final void processStdOutInternal( String line ) { try { processStdOut( line ); } catch ( Throwable t ) { log.warn( "processStdOut failed", t ); } } private final void processStdErrInternal( String line ) { try { processStdErr( line ); } catch ( Throwable t ) { log.warn( "processStdErr failed", t ); } } /** * Called to get the command line. Each argument should be a separate array * element. Returning null means the task should not run (as the arguments * were probably faulty). * * @return List of arguments. First element is the command itself. */ protected abstract String[] initCommandLine(); /** * Override this to modify the environment of the process to be started. * @param environment */ protected void initEnvironment( Map environment ) { } /** * Called when the process has been successfully started. */ protected void processStarted() { } /** * Called when the process has finished running * * @param exitCode the process' exit code * @return */ protected abstract boolean processEnded( int exitCode ); /** * Called when a line has been read from the process' stdout. * * @param line The line read from the process, without any newline characters */ protected abstract void processStdOut( String line ); /** * Called when a line has been read from the process' stderr. * Trailing newline is removed. * * @param line The line read from the process, without any newline characters */ protected abstract void processStdErr( String line ); }