summaryrefslogblamecommitdiffstats
path: root/api/src/main/java/org/openslx/taskmanager/api/SystemCommandTask.java
blob: 18c8eccf53b3679c39006bc53b49edc98fa26e34 (plain) (tree)
1
2
3
4
5
6
7
8
9






                                         
                        
                     








                                                                                      
 
                                       
 

                                         


                                         










                                                                                                           



                                                               





                                                                                                                      


                                                                  
                                                    



                                         


                                                     
                                                                                       


                                                                      








                                                                    
                                                                                                                                           


                                                                                                
                                                                                              

                                                         
                                                                 
                                                                    







                                                                    
                                                                                                                                           


                                                                                                
                                                                                              

                                                         
                                                                 
                                                                    







                                              
                                                                                                






















                                                                                    







                                                                  



                                                                 
 


                                                              
 
                                                    
                                           

                                                           




                                                  
 





                                                                                     
                                                      















                                                                                     
                                                      



                                                                          

















                                                               









                                                                                   







                                                                                









                                                                 
                  


















                                                                                     
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<String, String> 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 );

}