5

I want to be able to perform an asynchronous task in java and be able to keep a completion (and if possible progress) monitor associated to the user's session. Is this possible, and if yes what is the way to do it?

Currently the task is implemented synchronously as a stateless session bean method, which is called from a jax-rs endpoint.

I looked at https://docs.oracle.com/javaee/7/tutorial/ejb-async001.htm but AsyncResult is not serializable so I guess I cannot add it to session.

Marinos An
  • 9,481
  • 6
  • 63
  • 96
  • Your goal is unclear for me. You plan to stay with jax-rs or you cannot do other way? I can share a working example for wildfly + JSF which does the following: client uploads a file, this file is processed in background and client can see the number of processed lines in outputText – Oleg Gritsak Apr 17 '18 at 03:50
  • The solution does not need to be bound to `jax-rs`. I guess if I can save the progress monitor to the session, it should not matter what do I use as an endpoint. I want the user to be able to visit the page at anytime and see the progress of his task (or at least whether it is completed), but the solution should not involve adding a database state (only session). – Marinos An Apr 17 '18 at 10:16
  • So please share the code, if possible. – Marinos An Apr 17 '18 at 10:23

2 Answers2

3

Using the Spring annotation @Async, you can make any bean/method asynchronous. The container will create a new thread and method will be executed asynchronously. You can as well pass a session object into this method and upon completion, you can mark an attribute in the session object. Example:- https://spring.io/guides/gs/async-method/

Srinivas Lakshman
  • 469
  • 2
  • 4
  • 21
  • I wonder why this answer is upwoted and rewarded. Spring is a technology absolutely unrelated to Wildfly. JavaEE and Wildfly has @Asynchronous annotation. So what? – Oleg Gritsak Apr 20 '18 at 04:18
0

JSF example, works in Wildfly:

1 inside in view (xhtml) we have an upload form and progress meter

<h:form>
    <div align="justify">
    <p:fileUpload style="width: auto" fileUploadListener="#{fileUploadCtrl.handleFileUpload}" mode="advanced" label="Please pick XLS file" update="messages" auto="true" sizeLimit="1000000" allowTypes="/(\.|\/)(xls|xlsx)$/" />
    <p:growl id="messages" showDetail="false" life="4000"/>
    </div>
</h:form>

 <h:form id="wholeform">
    <h:outputText id="statusot" value="#{fileUploadCtrl.message}" />
    <p:spacer width="10" height="10"/>
    <p:poll interval="1" listener="#{fileUploadCtrl.updateStatus}" update="wholeform" />
</h:form>

2 in controller, which is a managed bean, we process file and once a second update status

@ManagedBean
@ViewScoped

public class FileUploadCtrl {

@EJB
private SomeBusinessLogicClass model;

@EJB
private ProgressTracker progress;

private Future<List<String>> asyncResult;
private int progressId = 0;
private String message;
private boolean busy = false;

public void handleFileUpload(FileUploadEvent event) {

    Set<String> ids = model.populate(event.getFile().getContents());

    progressId = progress.newIndex();
    asyncResult = model.process(ids);
    busy = true;

    FacesMessage message = new FacesMessage("Loaded " + ids.size() + " objects", "");
    FacesContext.getCurrentInstance().addMessage(null, message);
}

public void updateStatus() {

    if (!busy)
        return;

    try {

        if (asyncResult.isDone()) {
            List<String> r = asyncResult.get();
            message = "Job done";
            busy = false;
            progress.delIndex(progressId);
        } else {
            message = progress.getIndex(progressId)+"-th element in work";
        }

    } catch (Exception e) {
        System.out.println("updateStatus " + e.toString());
    }
}

3 All business logic is in EJBs like SomeBusinessLogicClass or many others. Also we need a simple progress-manager EJB, I post it completely

@Singleton
public class ProgressTracker {

private Map<Integer,Integer> indexes = new HashMap<>();

public Map<Integer, Integer> getIndexes() {
    return indexes;
}

public void setIndexes(Map<Integer, Integer> indexes) {
    this.indexes = indexes;
}

public Integer newIndex() {
    Integer size = indexes.size();
    indexes.put(size,0);
    return size;
}

public void incIndex(final Integer index) {

    int old = indexes.get(index);
    old++;
    indexes.put(index,old);
}

public Integer getIndex(final Integer index) {
    return indexes.get(index);
}

public void delIndex(Integer index) {
    indexes.remove(index);
}

}

Maybe this example is not elegant, I'm almost newbie with frontends, but it is working and better, than nothing.

Oleg Gritsak
  • 548
  • 7
  • 26
  • If it is not obvious, calling business method in model (like model.process(ids); Should be done with @Asynchronous annotation – Oleg Gritsak Apr 20 '18 at 04:21