2010-07-28 33 views
1

Dans mon JEE6-App (fonctionnant sur Glassfish 3.0.1) j'ai un EmailEJB qui doit envoyer beaucoup de mails. Les mails sont envoyés de façon asynchrone, donc ils sont annotés avec le nouveau @Asynchrone EJB3.1, en les laissant s'exécuter dans un thread séparé. Maintenant, je veux que l'utilisateur soit informé de l'état actuel de la méthode: Combien de mails ont déjà été envoyés?Expose la progression actuelle d'une fonction @Asynchronous à utiliser dans View

L'envoi asynchrone des mails fonctionne bien, mais je n'arrive pas à comprendre comment laisser la progression être accessible de l'extérieur. On dirait que mon approche pour faire cela est tout à fait fausse, mais d'une certaine manière, cela doit être possible (peut-être une autre approche). Voici comment mon EmailEJB ressemble actuellement (genre de pseudo-code, mais explique ce que je veux):

@Stateful 
public class EmailEJB { 

    @Asynchronous 
    public Future<Integer> sendMails() { 
    for (int i=0; i<mails.size; i++) { 
     sendMail(mails[i]) 
     // i want to return the progress without returning ;) 
     return new AsyncResult<Integer>(i) 
    } 
    } 
} 

//Just for the completeness... from outside, i'm accessing the progress like this: 
Future<Integer> progress = emailEJB.sendEmails(); 
Integer currentvalue = progress.get(); 

Comment puis-je retourner les progrès en cours dans ma fonction asynchrone, sans l'annuler avec un retour? Comment puis-je montrer à l'utilisateur la progression d'une boucle dans une fonction? Ai-je besoin d'une autre méthode asynchrone? Des indices?

Répondre

3

Personne? Ok, c'est ma solution. Je ne sais pas si c'est une grosse solution de contournement ou juste un moyen d'y arriver.

Comme une méthode @Asynchronous ne peut pas accéder au contexte de la session, et donc pas Beans session (au moins je ne sais pas comment, j'ai toujours eu ConcurrentModificationErrors ou similaires) i créé un Singleton ProgressEJB, qui contient une HashMap:

Cette hashmap doit mapper la SessionId (une chaîne) à une valeur entière (la progression 0 -> 100). Ainsi, une session utilisateur est associée à une progression. Dans mon EmailEJB, je suis l'injection de ce ProgressEJB, et dans ma méthode @Asynchronous, je suis de plus en plus la valeur à chaque fois un e-mail a été envoyé:

@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<mails.size; i++) { 
    sendMail(mails[i]) 
    progress.getProgressMap().put(sessionId, (i/mails.size) * 100) 
    } 
    progress.getProgressMap().remove(sessionId); 
} 

Le sessionId vient de mon Managed (Weld) Bean, lorsque vous appelez la fonction:

@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); 
    } 
} 

maintenant, je peux accéder à des progrès de EmailManager de mon point de vue JSF avec Ajax Polling, indiquant à l'utilisateur combien de mails ont été envoyés déjà. Juste testé avec 2 utilisateurs, semble fonctionner.

0

Je vois aussi seulement une solution @Singleton ici. Mais cela implique le besoin de ménager en progrèsEJB. Par exemple. certains efforts sont nécessaires pour élaguer l'ancienne session de Hashmap.

0

Une autre solution est décrite dans Is there any way to know the progress of a EJB Asynchronous process?

Cette solution n'a pas besoin d'un 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<mails.size; i++) { 
     sendMail(mails[i]) 
     context.setProgress((i/mails.size) * 100) 
     } 
     context.setRunning(false); 
    } 
} 

L'objet de contexte, qui contient la progression.

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

L'utilisation est très facile.

@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(); 
    } 
    .... 
}