Expor o progresso atual de uma function @Asynchronous para usar no View

No meu JEE6-App (rodando no Glassfish 3.0.1) eu tenho um EmailEJB que tem que enviar muitos e-mails. Os e-mails são enviados de forma assíncrona, portanto são anotados com o novo EJB3.1 @ Assíncrono, permitindo que ele seja executado em um Thread separado. Agora quero que o usuário seja informado sobre o status atual do método: quantos e-mails já foram enviados?

O envio de e-mails funciona de forma assíncrona, mas não consigo descobrir como deixar o progresso acessível de fora. Parece que minha abordagem para fazer isso é completamente errada, mas de alguma forma tem que ser possível (talvez outra abordagem). É assim que meu EmailEJB se parece atualmente (seu tipo de pseudo código, mas explica o que eu quero):

@Stateful public class EmailEJB { @Asynchronous public Future sendMails() { for (int i=0; i<mails.size; i++) { sendMail(mails[i]) // i want to return the progress without returning ;) return new AsyncResult(i) } } } //Just for the completeness... from outside, i'm accessing the progress like this: Future progress = emailEJB.sendEmails(); Integer currentvalue = progress.get(); 

Como posso retornar o progresso atual dentro da minha function assíncrona, sem cancelá-lo com um retorno? Como posso mostrar ao usuário o progresso de um loop dentro de uma function? Preciso de outro método asynchronous? Alguma dica?

Ninguém? Ok, então esta é a minha solução. Eu não tenho certeza se isso é uma grande solução alternativa ou apenas uma maneira de fazer isso.

Como um método @Asynchronous não pode acessar o contexto da Sessão e, portanto, também nenhum Beans de Sessão (pelo menos eu não sei como, sempre obtive ConcurrentModificationErrors ou similares) eu criei um Singleton ProgressEJB, que contém um HashMap:

 @Singleton @LocalBean @Startup public class ProgressEJB { private HashMap progressMap = new HashMap // getters and setters } 

Este hashmap deve mapear o SessionId (um String) para um valor Integer (o progresso 0-> 100). Portanto, uma session do usuário está associada a um progresso. No meu EmailEJB, estou injetando este ProgressEJB, e no meu método @Asynchronous, estou aumentando o valor toda vez que um email foi enviado:

 @Stateful @LocalBean public class EmailEJB { @Inject private ProgressEJB progress; // Mail-Settings ... @Asynchronous public void sendEmails(user:User, message:Message, sessionId:String) { progress.progressMap.put(sessionId, 0); for (int i=0; i 

O sessionId vem do meu Bean gerenciado (Weld), ao chamar a function:

 @SessionScoped @Named public class EmailManager { @Inject private ProgressEJB progress; @Inject private FacesContext facesContext; private String sessionId; @PostConstruct private void setSessionId() { this.sessionId = ((HttpSession)facesContext.getExternalContext().getSession(false)).getId(); } public Integer getProgress() { if (progress.getProgressMap().get(sessionId) == null) return 100; else return progress.getProgressMap().get(sessionId); } } 

Agora eu posso acessar o progresso do EmailManager da minha visualização JSF com o Ajax Polling, informando ao usuário quantos e-mails já foram enviados. Apenas testei com 2 usuários, parece funcionar.

Eu também vejo apenas uma solução @Singleton aqui. Mas isso implica a necessidade de Housekeeping in Progress EJB. Por exemplo, é necessário algum esforço para podar a session antiga do Hashmap.

Outra solução é descrita em Existe alguma maneira de saber o progresso de um processo asynchronous EJB?

Esta solução não precisa de um bean stateful.

 @Stateless public class EmailEJB { // Mail-Settings ... @Asynchronous public void sendEmails(User user, Message message, WorkContext context) { progress.progressMap.put(sessionId, 0); for (int i=0; i 

O Contexto-Objeto, que mantém o progresso.

 public class WorkContext { //volatile is important! private volatile Integer progress = 0; private volatile boolean running = false; // getters & setters } 

O uso é muito fácil.

 @SessionScoped @Named public class EmailManager { @Inject private EmailEJB emailEJB; private WorkContext workContext; public void doStuff() { workContext = new WorkContext(); emailEJB.sendEmails(user, message, workContext) } public Integer getProgress() { return workContext.getProgress(); } .... }