1

With my Controller class I have to execute several IO commands (ex: SSH, RCP commands with some parameter values) sequential manner. Each of this command will get some amount of time to execute.

I have to update UI controller when each command is start to execute. Then depending on that execution result (whether success or failed) I have to update UI again. Then have to execute the next command with same steps.

Execution of each command is depending on the result of previous command. As a example,

for (IOCommand command : commandsList) {

    // Update the UI before start the command execution  
    messageTextArea.append("Command " + command.getType() + " Stated");

    boolean result = commandExecutor(command);  

    if(result) {

      // Update the UI after successful execution  
      messageTextArea.append("Command " + command.getType() + " Successfully Executed");

      // Then go to next command execution 

    } else {

      // Update the UI after failure execution  
      messageTextArea.append("Command " + command.getType() + " Failed");

      // Fix the issue and do re execution
      commandReExecutor(command);       
    }
} 

For accomplish this gradual UI update I have to use some JavaFX related Task or Service related features (otherwise it will hang the application until finish all commands were executed and also it will update the UI all at once). But due to nature or concurrency I can not execute these commands with help of Task or Service, in sequential manner (not all at once, one after another). How can I address this problem. Thanks in advance.

Channa
  • 4,963
  • 14
  • 65
  • 97
  • 3
    Take a look at the sequential execution sample in: [How to reset progress indicator between tasks in JavaFX2?](http://stackoverflow.com/questions/16368793/how-to-reset-progress-indicator-between-tasks-in-javafx2) – jewelsea Jul 04 '14 at 08:57

1 Answers1

1

I'd the exact requirement in a project and it can be done with Task and Service. You just need a correct implementation.
Few notes:
1. Always start a background task using service or Platform.runLater.
2. If you want to update UI, it must be done from either Task or Service.
3. Bind progress property of task to that of progress bar for smooth updation.
4. Similarly bind text property of a Label to message property of a task for smooth updation of status or something else.

To execute external commands like shell, etc. I've written following class:

package utils;

import controller.ProgressController;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.concurrent.Task;
import main.Installer;

public class ProcessExecutor extends Task<Integer>
{
    Logger logger =Logger.getLogger("ProcessExecutor");
    File dir;
   String []cmd;
    String cmds;
    int exitCode=-1;
    boolean NextStepExists=false;
    Task nextStep;
    public ProcessExecutor(String...cmd )
{
    this.cmd=cmd;
    this.dir=new File(System.getProperty("user.dir"));
    this.nextStep=null;
    NextStepExists=false;
}
public ProcessExecutor(Task nextStep,String...cmd )
{
    this.cmd=cmd;
    this.dir=new File(System.getProperty("user.dir"));
    this.nextStep=nextStep;
    NextStepExists=true;
}
public ProcessExecutor(Task nextStep,File dir,String...cmd)
{
    this.cmd=cmd;
    this.dir=dir;
    this.nextStep=nextStep;
    NextStepExists=true;
}


@Override
protected final Integer call()
{
    cmds=new String();
        for(String i:cmd)
            cmds+=i+" "; // just to log cmd array

    try
    {

        logger.info("Starting new process with cmd > "+cmds);

        ProcessBuilder processBuilder=new ProcessBuilder(cmd);
        processBuilder.directory(dir);
        processBuilder.redirectErrorStream(true);
        Map<String, String> env = processBuilder.environment();
        // create custom environment 
        env.put("JAVA_HOME", "/opt/jdk1.7.0_45/"); 

        Process pr=processBuilder.start();
        BufferedReader in = new BufferedReader(new InputStreamReader(pr.getInputStream()));
           String line = in.readLine();
            while (line != null) {
                logger.log(Level.FINE,line);
                ProgressController.instance.printToConsole(line);
                line = in.readLine();
            }
            BufferedReader er = new BufferedReader(new InputStreamReader(pr.getErrorStream()));
           String erLine = in.readLine();
            while (erLine != null) {
                logger.log(Level.FINE,erLine);
                ProgressController.instance.printToConsole(erLine);
                erLine = in.readLine();
            }


        exitCode=pr.waitFor();
        exitCode=pr.exitValue();
        logger.info("Exit Value="+exitCode);
        updateMessage("Completed Process");
        if(exitCode!=0 && exitCode!=1)
        {
            logger.info("Failed to execute process commands >"+cmds+" with exit code="+exitCode);
            failed();
        }

        else
        {
            logger.info("PE succeeded()");
            if(NextStepExists)
                Installer.pool.submit(nextStep);
            succeeded();
        }

    }
    catch(Exception e)
    {
        logger.log(Level.SEVERE,"Exception: Failed to execute process commands >"+cmds,e);
        updateMessage(e.getMessage());
    }

    return new Integer(exitCode);



}
@Override
public void failed()
{
    super.failed();
    logger.log(Level.SEVERE,"Failed to execute process commands >"+cmds+"; ExitCode="+exitCode);

}
}



This class uses ProcessBuilder to create required environment for new process,
It waits to finish execution of process using process.waitFor(),
the directory of process can be set using processBuilder.directory(dir).

In order to execute a single Task<> at any time, use java.util.concurrent.ExecutorService

public ExecutorService pool=Executors.newSingleThreadExecutor();
pool.submit(new ProcessExecutor("installTomcat.bat","tomcat7"));
pool.submit(new ProcessExecutor("installPostgres.bat","postgresql","5432"));


In this way you can execute batch files one after another.
Executors.newSingleThreadExecutor() takes care of executing a single task at any time and queing the newly submitted tasks.

I've written a generalized working example of sequential execution here:
github
This is a NetBeans JavaFX project and its a generalized & stripped down version of a project.
Hope this helps

Ruturaj Patil
  • 608
  • 1
  • 10
  • 25