0

I have a command dialog on a web page that uses command buttons to confirm whether or not the user would like to run a back-end script. If the user confirms they'd like to continue, there should be an immediate growl message letting them know that the script has begun. The script then runs, blocking all other processes until its done. Upon completion, there should be another growl message letting the user know that the script has finished.

The xhtml code is set up as follows:

<p:commandButton value="Yes Sure" actionListener="#{listBean.invokeBackend}"
onclick="PF('backendRun').hide()" update="invokeBackendGrowl"/>

These attributes do not execute simultaneously The order of execution for these attributes is onclick, then actionlistener, then update. Due to the fact that my actionlistener item refers to a function that blocks other processes, I need the update attribute to be executed before the actionlistener item is complete.

onclick closes the command dialog

update works to display the most recent version of the growls

actionlistener executes the bean method, which uses process builder to ensure the script runs without interruption. It also contains the Java code for both growls (notification for beginning of script and notification for end of script). Since the script is running alone, these growls both display at the same time, after script completion, rather than the beginning growl being displayed right when the script begins.

The actionlistener item is as follows:

public void invokeBackend() throws IOException, InterruptedException {

     ProcessBuilder pb = new ProcessBuilder(<command for script to run>);

     pb.redirectErrorStream(true);
     File outputFile = new File(<location>);
     pb.redirectOutput(outputFile);

     Process p = pb.start();

     FacesContext context = FacesContext.getCurrentInstance();
     context.addMessage(null, new FacesMessage("Successful", "Script Called!"));

     p.waitFor();
     context.addMessage(null, new FacesMessage("Completed", "Script Complete!"));

}

Update

There is one thread in this code, and this question was looking for a way to re-organize when the thread began in relation to other actions.

Kukeltje
  • 12,223
  • 4
  • 24
  • 47
Manaar
  • 202
  • 4
  • 15
  • Possible duplicate of [Is it safe to start a new thread in a JSF managed bean?](https://stackoverflow.com/questions/6149919/is-it-safe-to-start-a-new-thread-in-a-jsf-managed-bean) – Kukeltje Jan 23 '19 at 18:09
  • Blocking threads between server and client is not the best design. You can't nor should you want to do it something like this. See the comment on your answer (which contains a not too good solution) – Kukeltje Jan 24 '19 at 08:29
  • 1
    Spawning a new thread is the way to go. Maybe https://stackoverflow.com/questions/3787514/how-can-server-push-asynchronous-changes-to-a-html-page-created-by-jsf is a better duplicate – Kukeltje Jan 24 '19 at 08:35

1 Answers1

-2

I solved this problem using the following workaround:

Since jsf will always call the actionlistener before the update attribute, I modified the actionlistener''s job to exclude the process blocking script. The actionlistener on the commandbutton now calls a reroute method, which simply executes the first growl, notifying the user that the script has begun. Then the update attribute runs, displaying the first growl.

I then introduces a remote command to call invokeBackend(), which is the same as before, minus the code for the first growl.

The xhtml code is now as follows:

<p:commandButton value="Yes Sure" actionListener="#{listBean.reroute()}"
onclick="PF('backendRun').hide();" update="invokeBackendGrowl" onsuccess="doAfter()" />
<p:remoteCommand name="doAfter" update="doneBackendGrowl" action="#{listBean.invokeBackend()}"/>  

The java code is now as follows:

public void invokeBackend() throws IOException, InterruptedException {

     ProcessBuilder pb = new ProcessBuilder(<command for script to run>);

     pb.redirectErrorStream(true);
     File outputFile = new File(<location>);
     pb.redirectOutput(outputFile);

     Process p = pb.start();

     FacesContext context = FacesContext.getCurrentInstance();

     p.waitFor();
     context.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, "Success!", "Backend complete! Please check email for the output."));            

}

public void reroute(){

     FacesContext context = FacesContext.getCurrentInstance();
     context.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_WARN, "Backend invoked! ", "Please check back here for updates."));

}

In short, the code is now split so that reroute() holds the code that needs to happen before the script blocks all other processes in invokeBackend().

Manaar
  • 202
  • 4
  • 15
  • 2
    Not the best modern design. Learn about push and async https://stackoverflow.com/questions/31872441/calling-a-long-running-process-asynchronously-on-a-button-click-in-jsf and https://stackoverflow.com/questions/6149919/is-it-safe-to-start-a-new-thread-in-a-jsf-managed-bean – Kukeltje Jan 23 '19 at 18:09
  • 1
    Please don't do it this way. This does not solve it - it hacks around it. See my answer instead. – Adam Waldenberg Jan 23 '19 at 23:44