SeamFramework.orgCommunity Documentation

Chapitre 7. Les conversations et le gestionnaire de l'espace de travail

7.1. Le modèle conversationnel de Seam
7.2. Les conversations liées
7.3. Démarrage des conversations avec les requêtes GET
7.4. Demander une conversation à exécution longue
7.5. L'utilisation de <s:link> et de <s:button>
7.6. Message de succès
7.7. Utilisation d'un identifiant de conversation "explicite"
7.8. La création d'une conversation explicite
7.9. Redirection vers une conversation explicite
7.10. Le gestionnaire d'espace de travail
7.10.1. Le gestionaire d'espace de trvail et la navigation JSF
7.10.2. Le gestionnaire d'espace de travail et l'enchainement de page jPDL
7.10.3. Le commutateur de conversation
7.10.4. La liste de conversation
7.10.5. Le fil d'ariane
7.11. Les composants conversationnels et la liaison avec les composants JSF
7.12. Les appels concurentiels de composants conversationnels
7.12.1. Comment allons nous construire notre application AJAX conversationnelle?
7.12.2. La gestion des erreurs
7.12.3. RichFaces (Ajax4jsf)

Il est temps de comprendre le modèle conversationnel de manière plus détaillée.

Historiquement, la notion d'une "conversation" de Seam vient de la fusion de trois idées différentes:

En unifiant ces idées et en fournissant un profond support dans le squellete d'application, nous avons une construction percutante qui nous permets de construire de plus riche et de plus éfficace applications avec moins de code qu'auparavant.

Les exemples que nous avons vu jusque là utilisent un modèle conversationnel très simple qui suit ces règles:

Seam propage de manière transparente le contexte conversationnel au travers des postbacks JSF et des redirections. Si vous ne voulez pas faire quelquechose de spécial, une requête non-faces (une requête GET par exemple) ne va pas propager le contexte conversationnel et va être exécuté dans une nouvelle conversdation temporaire. C'est habituellement- mais pas toujours- le fonctionnement désiré.

Si vous voulez propager une conversation Seam au travers d'une requête non-faces, vous devez explicitement coder l'identifiant de conversation comme un paramètre de requête:


<a href="main.jsf?#{manager.conversationIdParameter}=#{conversation.id}"
>Continue</a
>

Ou beaucoup plus, JSF-ien:


<h:outputLink value="main.jsf">
    <f:param name="#{manager.conversationIdParameter}" value="#{conversation.id}"/>
    <h:outputText value="Continue"/>
</h:outputLink
>

Si vous utilisez la librairie de tag de Seam, ceci est équivalent:


<h:outputLink value="main.jsf">
    <s:conversationId/>
    <h:outputText value="Continue"/>
</h:outputLink
>

Si vous voulez désactiver la propagation du contexte conversationnel pour un postback, un truc similaire est utilisé:


<h:commandLink action="main" value="Exit">
    <f:param name="conversationPropagation" value="none"/>
</h:commandLink
>

Si vous utilisez la librairie de tag de Seam, ceci est équivalent:


<h:commandLink action="main" value="Exit">
    <s:conversationPropagation type="none"/>
</h:commandLink
>

Notez que la désactivation de la propgation du contexte conversationnel n'est absolument pas la même chose que finir la conversation.

Le paramètre de requête conversationPropagation ou le tag <s:conversationPropagation> peut même être utilisé pour commencer ou finir la conversation liée.


<h:commandLink action="main" value="Exit">
    <s:conversationPropagation type="end"/>
</h:commandLink
>

<h:commandLink action="main" value="Exit">
    <s:conversationPropagation type="endRoot"/>
</h:commandLink
>

<h:commandLink action="main" value="Select Child">
    <s:conversationPropagation type="nested"/>
</h:commandLink
>

<h:commandLink action="main" value="Select Hotel">
    <s:conversationPropagation type="begin"/>
</h:commandLink
>

<h:commandLink action="main" value="Select Hotel">
    <s:conversationPropagation type="join"/>
</h:commandLink
>

Ce modèle conversationnel rend facile de construire des applications qui traite correctement en respectant les opération multi-fenètres. Pour beaucoup d'application, c'est tout ce qu'il y a besoin. Quelques applications complexes ont besoin d'une ou des deux exigeances additionnelles suivantes:

Une conversation liée est créée en invoquant la métode marqué @Begin(nested=true) à l'intérieur d'une conversation existante. Une conversation liée a son propre contexte conversationnel et peut aussi lire les valeurs depuis le contexte conversationnel externe . Le contexte conversationnel externe est en lecture seul à l'intérieur d'une conversation liée mais parce que les objets sont obtenue par référence, les modifications des objets eux-même seront réfléchies dans le contexte externe.

Quand un @End est par la suite rencontré, la conversation liée sera détruite et la conversation externe sera terminée, en la "retirant du dessus" de la pile des conversations. Les conversations peuvent être liée à n'importe quelle profondeur arbitraire.

Certaines activités de l'utilisateur (gestionnaire de l'espace de travail ou bouton précédent) peuvent faire en sorte que la conversation externe soit reprise avant que la conversation interne ne soit finie. Dans ce cas, il est possible d'avoir de multiples conversations concurrantes liées appartenant à la même conversation externe. Si la conversation externe se fini avant que la conversation liée ne s'arrête, Seam détruit tous les contextes conversationnels liés en relation avec le contexte externe.

La conversation au bas de la pil de conversation est la conversation racine. La destruction de cette conversation va toujours détruire les conversations enfantées. Vous pouvez réaliser ceci en spécifiant @End(root=true).

Une conversation peut être imaginée comme un état continue. Les conversations liée permette à l'application de capture un état constistent et continue à plusieurs moments de l'interaction utilisateur, ainsi assurant de manière réellement correcte le focntionnement vis à vis du bouton précédent et de la gestion de l'espace de travail.

Comme indiqué précédemment, si un composant existe dans la conversation parente de la conversation liée courante, la conversation liée utilisera la même instance. Occasionnellement, il est utile d'avoir une instance différente pour chaque conversation liée, ainsi l'instance du composant qui existe dans la conversation parente est invisible à ses propres conversations filles. Vous pouvez avoir cette fonctionnalité en annotatant le composant @PerNestedConversation.

JSF ne définie aucun type d'écouteur d'action qui est déclencher quand une page est accédée via une requête non-faces (par exemple, une requête HTTP GET). Cela peut arriver si l'utilisateur fait une favori de la page, ou si nous navigons vers la page via un <h:outputLink>.

Parfois nous voulons commencer une conversation immédiatement quand la page est accédée. Depuis qu'il n'y a pas de méthode d'action JSF, nous ne pouvons résoudre le problème de la manière habituelle, en annotation l'action avec@Begin.

Un autre problème est levé si la page a besoin des états soient reliés dans une variable de contexte. Nous avons déjà vue deux façon de résoudre ce problème. Si l'état est retenue par un composant Seam, nous pouvons relié l'éata dans une méthode @Create . Sinon, nous pouvons définir une méthode @Factory pour la variable de contexte.

Si aucune de ces options ne fonctionne pour vous, Seam vous permet de définir une action de page dans le fichierpages.xml file.


<pages>
    <page view-id="/messageList.jsp" action="#{messageManager.list}"/>
    ...
</pages
>

Cette méthode d'action est appelé au début de la phase de rendue de la réponse, à chaque fois que la page est au moment d'être rendue. Si une actiond e page retourne une sortie non-nulle, Seam va réaliser chaque règle de navigation JSF et Seam appropriée, résultant possiblement dans une page complètement différente à être rendue.

Si tout ce que vous voulez faire avant de rendre la page est de commencer une conversation, vous pouvez utiliser une méthode d'action livrée qui fait juste cela:


<pages>
    <page view-id="/messageList.jsp" action="#{conversation.begin}"/>
    ...
</pages
>

Notez que vous pouvez aussi appeler cette action livrée depuis un control JSF, et , de manière simmilaire, vous pouvez utiliser #{conversation.end} pour finir les conversations.

Si vous voulez plus de contrôle, pour rejoindre les conversations existantes ou commencer une conversation liée, pour commencer une enchainement de page ou une conversation insécable, vous devriez utiliser l'élément <begin-conversation>.


<pages>
    <page view-id="/messageList.jsp">
       <begin-conversation nested="true" pageflow="AddItem"/>
    <page>
    ...
</pages
>

Il y aussi un élément <end-conversation>.


<pages>
    <page view-id="/home.jsp">
       <end-conversation/>
    <page>
    ...
</pages
>

Pour résoudre le premier problème, nous avons maintenant cinq options:

Certaines pages ne sont pertinente que dans le contexte d'une conversation à exécution longue. Une façon de "proteger" ce type de page est de demander une conversation à exécution longue comme un prérequis pour rendre la page. Heureusement, Seam a un mécanisme livré pour mettre en place ce prérequis.

Dans le descripteur de la page de Seam, vous pouvez indiquer que la conversation courante doit être à exécution longue (ou liée) pour que la page soit rendue en utilisant l'attribut conversation-required comme ci-dessous:


<page view-id="/book.xhtml" conversation-required="true"/>

Quand Seam détermine que cette page est requise à l'extérieure d'une conversation à exécution longue, les actions suivantes interviennent:

La page alternative est définie dans l'attribut no-conversation-view-id sur l'élément <pages> dans le descripteur de page de Seam comme ci-dessous:


<pages no-conversation-view-id="/main.xhtml"/>

A ce moment là, vous pouvez seulement définir un seul type de page pour toute l'application.

Les liens de commande JSF réalise toujours une soumission de formulaire via JavaScript, qui casse la fonctionnalité des navigateur web de "ouvrir une nouvelle fenêtre" ou "ouvrir un nouvel onglet". En pur JSF, vous avez besoin d'utiliser un <h:outputLink> si vous avez besoin de la fonctionnalité. Mais il y a deux limitations majeures à <h:outputLink>.

Seam fourni la notion d'action de page pour aider à résoudre le premier problème, mais cela ne fait rien pour le second problème. Nous pourrionscontourner cela en utilisant l'apporche RESTful en passant le paramètre de requête et en requérant l'objet sélectionné du côté serveur. Dans quelques cas — comme l'application blog en exemple de Seam — c'est en fait la meilleure approche. Le style RESTful supporte le marquepage alors qu'il ne requiere par un état du côté serveur. Dans les autres cas, quand nous ne nous inquiétons pas à propos des marques-pages, l'utilisation de @DataModel et @DataModelSelection est juste plus pratique et plus transparant!

Pour implémenter cette fonctionnalité manquante et pour permettre la propagation de la conversation aussi simple pour gérer, Seam fourni la tag JSF <s:link>.

Le lien peut juste spécifier l'iodentifiant de la vue JSF:


<s:link view="/login.xhtml" value="Login"/>

Or, it may specify an action method (in which case the action outcome determines the page that results):


<s:link action="#{login.logout}" value="Logout"/>

Si vous spécifier les deux : un identifiant de vue JSF et une méthode d'action, la 'vue' sera utilisée à moins que la méthode d'action ne retourne un résultat non-null:


<s:link view="/loggedOut.xhtml"  action="#{login.logout}" value="Logout"/>

Le lien propage automatiquement la ligne sélectionnée d'un DataModel en l'utilisant avec <h:dataTable>:


<s:link view="/hotel.xhtml" action="#{hotelSearch.selectHotel}" value="#{hotel.name}"/>

Vous pouvez quitter l'étendue d'une conversation existante:


<s:link view="/main.xhtml" propagation="none"/>

Vous pouvez commencer, finir ou grouper les conversations:


<s:link action="#{issueEditor.viewComment}" propagation="nest"/>

Si le lien commence une conversation, vous pouvez même spécifier l'enchainement de page à utiliser:


<s:link action="#{documentEditor.getDocument}" propagation="begin"
        pageflow="EditDocument"/>

L'attribut taskInstance à utiliser dans une liste de tâches jBPM:


<s:link action="#{documentApproval.approveOrReject}" taskInstance="#{task}"/>

(Voir l'application de démonstration du magasin de DVD pour des exemples de cela.)

Enfin, si vous avez besoin d'un "lien" qui soit visualisé comme un bouton, utilisez <s:button>:


<s:button action="#{login.logout}" value="Logout"/>

Il est assez commun d'afficher un message pour l'utilisateur indiquant le succés ou l'echec d'une action. Il est pratique d'utiliser un FacesMessage de JSF pour cela. Malheureusement, une action réussie a besoin souvant d'une redirection du navigateur, et JSF ne peut pas propager un messages faces au travers de redirections. Cela rends les choses un peu plus difficile pour afficher un message de succés en pur JSF.

Le composant de Seam d'étendue conversation livré dénomé facesMessages résoud ce problème. (Vous devez avoir un filtre de redirection de Seam installé.)

@Name("editDocumentAction")

@Stateless
public class EditDocumentBean implements EditDocument {
    @In EntityManager em;
    @In Document document;
    @In FacesMessages facesMessages;
    
    public String update() {
        em.merge(document);
        facesMessages.add("Document updated");
    }
}

Chaque message ajouté à facesMessages est utilisé dans la phase de réponse immédiatement suivante pour la conversation courante. Cela fonctionne même quand il y une conversaiton à exécution longue depuis que Seam conserve les contexte de conversation temporaire au travers de redirections.

Vous pouvez même inclure les expressions EL de JSF dans le label du messages faces:

facesMessages.add("Document #{document.title} was updated");

Vous pouvez afficher le message de manière usuelle, par exemple:


<h:messages globalOnly="true"/>

Ordinairement avec les conversation qui travaille avec des objets persistants, il peut être utile d'utiliser une clef métier explicite au lieu de la version standard, identifiant de conversation de "substitution":

Redirection simple vers une conversation existante

Il peut être utile de rediriger vers une conversation existante si l'utilisateur demande la même opération deux fois. Prenons cet exemple: « Vous êtes sur ebay, au milieu de la phase de paiement pour un cadeau de noël pour vos parents que vous avez gagné. Inutile de dire que vous voulez le leur envoyé immédiatement - vous entrez les défailts sur le paiement mais sans pouvoir vous souvenir de leur adresse. Vous réutilisez accidentellement la même fenètre du navigateur pour trouver leur adresse. Maintenant, vous avez besoin de retourner vers le paiement pour ce truc.  »

Avec une conversation explicite, il est vraiment très facile d'avoir un utilisateur qui rejoind une conversation existante, et la reprendra là où il la laissé - tout simplement lui permettre de rejoindre la conversation payForItem avec l'itemId comme identifiant de conversation.

Utilisation des URLs sympatiques

Pour moi cela consiste dans une hierachie navigable (Je peux naviguer en éditant l'URL) et une URL pertinente (comme font les Wiki - donc sans identifier les choses avec des identifiants kabbalistiques). Pour des applications l'utilisation des URLs sympatiques ne sont pas , bien sûr, du tout utile.

Avec une conversation explicite, quand vous allez construire votre système de réservation d'hotel (ou, bien sûr, quelque soit votre application) vous pouvez générer une URL comme http://seam-hotels/book.seam?hotel=BestWesternAntwerpen (bien sûr, quelque soit le paramètre hotel correspondant au modèle de votre domaine doit être unique) et avec une ré-écriture des URLs facilement transformable en http://seam-hotels/book/BestWesternAntwerpen.

Encore mieux!

Les conversations explicites sont définies dans description


  <conversation name="PlaceBid"
                  parameter-name="auctionId"
                  parameter-value="#{auction.auctionId}"/>

La première chose à noter de la définition ci-dessus est qua la conversation a un nom, dans ce cas PlaceBid. Ce nom identifiant de manière unique cette conversation particulière, et est utilisé par la définition de page pour identifier une conversation sus-nommé pour y participer.

L'attribut suivant, parameter-name définit le paramète de la requête qui va contenir l'identifiant explicite de conversation, en lieu et place du paramètre d'identifiant de conversation par défaut. Dans cet exemple, le parameter-name est auctionId. Ce qui signifie qu'au lieu d'avoir un paramètre de conversation comme cid=123 qui apparait dans l'URL de votre page, il y aura auctionId=765432 à la place.

Le dernier attribut dans la configuration ci-dessus, parameter-value, défini une expression EL utilisée pour évaluer la valeur de la clef métier explicite à utiliser comme identifiant de conversation. Dans cet exemple, l'identifiant de conversation sera une valeur de clef primaire de l'instance auction courante dans l'étendue.

Ensuite, nous définissons quelle page va être dans la convesation dénommée. Ceci est fait en spécifiant que l'attribut conversation pour une définition de page:


  <page view-id="/bid.xhtml" conversation="PlaceBid" login-required="true">
      <navigation from-action="#{bidAction.confirmBid}"
>        
          <rule if-outcome="success">
              <redirect view-id="/auction.xhtml">
                  <param name="id" value="#{bidAction.bid.auction.auctionId}"/>
              </redirect>
          </rule
>        
      </navigation>
  </page
>

Au moment de démarrer, ou de rediriger vers, une conversation explicite, il y a plusieurs options pour spécifier le nom de la conversation explicite. Commençons pas regarder la définition de page suivante:


  <page view-id="/auction.xhtml">
    <param name="id" value="#{auctionDetail.selectedAuctionId}"/>
       
    <navigation from-action="#{bidAction.placeBid}">
      <redirect view-id="/bid.xhtml"/>
    </navigation>
  </page
>

A partir de là, nous pouvons voir que l'invocation de l'action #{bidAction.placeBid} pour notre vue auction (à partir de là, tous ces exemples sont prit de l'exemple seamBat dans Seam), qui va être redirigier vers /bid.xhtml, qui , comme nous l'avons vu précédemment, est configuré avec la conversation explicite PlaceBid. La déclaration de notre méthode action devrait ressemble à ceci:

   @Begin(join = true)

   public void placeBid()

Quand les conversations nommées sont spécifiées dans l'élément <page/>, la redirection vers la conversion nommé intervient comme une des règles de navigations, après que la méthode d'action ai été invoquée. Ceci est un problème quand la redirection vers une conversation existante, quand la redirection a besoin d'intervenir après que la méthode d'action ai été invoquée. C'est pourquoi il est nécéssaire de spécifier le nom de la conversation quand l'action est invoquée. Une façon de faire cela est en utilisant la balise s:conversationName :


  <h:commandButton id="placeBidWithAmount" styleClass="placeBid" action="#{bidAction.placeBid}">
    <s:conversationName value="PlaceBid"/>
  </h:commandButton
>

Une autre manière de spécifier l'attribut conversationName est en utilisant aussi bien s:link que s:button:


  <s:link value="Place Bid" action="#{bidAction.placeBid}" conversationName="PlaceBid"/>

Le gestionnaire de l'espace de travail est capable de "basculer" les conversations dans une seule fenètre. Seam rend la gestion des espaces de travail complètement transparente au niveau du code Java. Pour activer le gestionnaire de l'espace de travail, tout ce que vous avez à faire est :

La liste des conversations est vraiment similaire au commutateur de conversation, exception faite qu'elle est affiché comme un tableau:


<h:dataTable value="#{conversationList}" var="entry"
        rendered="#{not empty conversationList}">
    <h:column>
        <f:facet name="header"
>Workspace</f:facet>
        <h:commandLink action="#{entry.select}" value="#{entry.description}"/>
        <h:outputText value="[current]" rendered="#{entry.current}"/>
    </h:column>
    <h:column>
        <f:facet name="header"
>Activity</f:facet>
        <h:outputText value="#{entry.startDatetime}">
            <f:convertDateTime type="time" pattern="hh:mm a"/>
        </h:outputText>
        <h:outputText value=" - "/>
        <h:outputText value="#{entry.lastDatetime}">
            <f:convertDateTime type="time" pattern="hh:mm a"/>
        </h:outputText>
    </h:column>
    <h:column>
        <f:facet name="header"
>Action</f:facet>
        <h:commandButton action="#{entry.select}" value="#{msg.Switch}"/>
        <h:commandButton action="#{entry.destroy}" value="#{msg.Destroy}"/>
    </h:column>
</h:dataTable
>

Nous imaginons que nous allons vouloir la personnaliser pour notre application.

Seule les conversations avec une description seront incluse dans la liste.

Il faut noter que la liste de conversation permet à l'utilisateur de détruire les espaces de travail.

Les composants conversationnels ont une limitation mineure; ils ne peuvent être utilisé pour conserver une liaison vers des composants JSF. (Nous préférons générallement ne pas utiliser cette fonctionnalité avec JSF à moins que cela ne soit absolument nécssaire, car cela créer une dépendance forte de la logique applicative vers la vue). Avec une requête de retour,la liaison de composant est mise à jours pendant la phase de restoration de la vue, avant que le contexte conversationnel de Sean ne soit restoré.

Pour contourner ceci, utilisez un composant d'étendue évènement pour conserver la liaison du composant et l'injecter dans le composant d'étendue conversationnel qui en a besoin.

@Name("grid")

@Scope(ScopeType.EVENT)
public class Grid
{
    private HtmlPanelGrid htmlPanelGrid;
    // getters and setters
    ...
}
@Name("gridEditor")

@Scope(ScopeType.CONVERSATION)
public class GridEditor
{
    @In(required=false)
    private Grid grid;
    
    ...
}

De même, vous ne pouvez pas injecter un composant d'étendue conversationnel dans un composant d'étendue d'évènement que vous liez avec un contrôle JSF. Ceci inclu les composants livrés de Seam comme facesMessages.

L'alternative est que vous pouvez accéder à l'arbre de composant JSF au traver d'une référence uiComponent implicite. L'exemple suivant accède getRowIndex() du composant UIData qui conserve le tableau de données pendant l'itération, il affiche le numéro de ligne courrant:



<h:dataTable id="lineItemTable" var="lineItem" value="#{orderHome.lineItems}">
   <h:column>
      Row: #{uiComponent['lineItemTable'].rowIndex}
   </h:column>
   ...
</h:dataTable
>

Les composant UI de JSF sont disponibles avec leurs identifiants de client dans cette table de hachage.

Une discussion général d'appel concurentiel des composants de Seam peut être trouvée dans Section 4.1.10, « Modèle de concurrence ». Ici, nous allons discuter de la situation la plus courante où vous allez rencontrer de la concurence — l'accès aux composants conversationnel depuis les requêtes AJAX. Nous aloons discuter des options que les bibliothèque AJAX cliente devraient fournir pour controler les évènements provenant du client — et nous allons voir l'option que RichFaces vous offre.

Les composants conversationnels ne permettent pas un accès réellement concurentiel à moins que Seam ne mette en file d'attente chaque requête pour les réaliser séquenciellement. Ceci permet à chaque requête d'être exécutée de façon déterministe. Cependant une simple file d'attente n'est pas aussi géniale — en premier lieu, si uen méthode prend, pour une raison quelconque, un très long moment pour se terminer, exécuter encore une par dessus , et encore une autre, tant et plus que le client génère une requête est une mauvaise idée (attaque potentielle par Dénie de Service) et en second, AJAX est souvent utilisé pour fournir une mise à jours rapide d'un status à l'utilisateur, donc continuer à exécuter l'action après un bout de temps n'est pas très utile.

Ainsi, quand vous travaillez à l'intérieur d'une conversation à exécution longue, Seam met en file d'attente les évènements d'action pour une période de temps (le temps de péremption de la requête concurentielle); si il peut exécuter l'évènement dans les temps, il créé une conversation temporaire et affiche le message pour que l'utilisateur sache ce qu'il se passe. Il est donc très important de ne pas saturer le serveur avec des évènements AJAX!

Nous pouvons définir par défaut et précisément le temps de péremption de la requête concurentielle (en ms) dans components.xml:


<core:manager concurrent-request-timeout="500" />

Nous pouvons aussi configurer finement le temps de péremption de la requête concurente sur une base de page-par-page:


<page view-id="/book.xhtml" 
         conversation-required="true" 
         login-required="true"
         concurrent-request-timeout="2000" />

D'aussi loin que nous discutons des requêtes AJAX qui apparaisent sérialisée à l'utilisateur - le client indique au serveur qu'un évènement intervient, et ensuite re-rends de la partie de la page basé sur le résultat. Cette approche est géniale quand la requête AJAX est légère (la méthode appelé est simple, par exemple, le calcul de la somme d'une colonne de nombre). Mais si nous avons besoin de faire un calcul complexe qui va prendre une minute?

Pour les calculs lourds, vous devrions utiliser une approche basé sur un groupement — le client envoie une requête AJAX vers le serveur, ce qui fait que l'action soit exécuté assynchronement sur le serveur (la response vers le client est immédiate) et le client ensuite regoupe les mises à jours vers le serveur. Ceci est une bonne approche si vous avez une action à exécution longue pour laquelle il est important que chaque action soit exécuté (vous ne voulez pas de péremption).

En premier lieu, vous avez besoin de choisir si vous voulez utiliser la requête la plus simple en "série" ou vous voulez utiliser une approche par regroupement.

SI vous vous décidez pour des requêtes en "séries", alors vous avez besoin d'estimer combien de temps va durer votre requête pour se terminer - est ce plus court que le temps de péremption de la requête concurentielle? Ci ce n'est pas le cas, vous voulez probablement modifier le temps de péremption de la requête concurentielle (comme vu précédemment). Vous voulez probablement mettre en fil d'attente côté client pour prévenir la saturation du serveur avec les requêtes. Si l'évènement intervient souvent (par exemple, pression sur une touche, au survol de champs de saisie) et la mise à jours immédiate du client n'est pas une priorité, vous devriez définir un delais de la requête côté client. Quand le delais de la requête sera passé, tenir compte que l'évènement sera aussi mis en file d'attente du côté serveur.

Au final, la bibliothèque cliente peut fournir une option pour intérrompre une requête de duplication en cours au profit d'une plus récente.

L'utilisation d'un design de style regroupement nécéssite un réglage moins précis. Vous avez juste à indiquer votre méthode d'action avec @Asynchronous et décider d'un intervale de regroupement:

int total;


// This method is called when an event occurs on the client
// It takes a really long time to execute
@Asynchronous      
public void calculateTotal() {
   total = someReallyComplicatedCalculation();
}
// This method is called as the result of the poll
// It's very quick to execute
public int getTotal() {
   return total;
}

Quelquesoit le cas, définissez votre application avec attention pour mettre les requêtes concurentes en file d'attente vers votre composant conversationnel, il ya une risque que le serveur devienne trop surchargé pour être capable d'exécuter toutes les requêtes avant que la requête n'ai à attendre plus longtemps que concurrent-request-timeout. Dans ce cas, Seam déclenchera un ConcurrentRequestTimeoutException qui peut être gérer dans pages.xml. Nous recommendons d'envoyer une erreur HTTP 503:


   <exception class="org.jboss.seam.ConcurrentRequestTimeoutException" log-level="trace">
      <http-error error-code="503" />
   </exception
>

Autrement vous pouvez rediriger vers une page d'erreur:


<exception class="org.jboss.seam.ConcurrentRequestTimeoutException" log-level="trace">
   <end-conversation/>
   <redirect view-id="/error.xhtml">
      <message
>The server is too busy to process your request, please try again later</message>
   </redirect>
</exception
>

ICEfaces, RichFaces Ajax et Seam Remoting peuvent gérer les codes d'erreurs HTTP. Seam Remoting va faire surgir une boite de dialogue montrant l'erreur HTTP. ICEfaces va indiquer l'erreur dans son composant de status de connection. RichFaces fourni le support le plus complet pour la gestion des erreurs HTTP en fournissant une fonction de rappel définissable pour l'utilisateur. Par exemple, pour afficher le message d'erreur pour l'utilisateur:

<script type="text/javascript">
   A4J.AJAX.onError = function(req,status,message) { 
      alert("An error occurred");
   };
</script
>

Si au lieu d'un code d'erreur, le serveur rapporte que la vue a expirée, peut être à cause du temps de péremption de la session, vous pouvez utiliser une fonction de rappel séparée dans RichFaces pour gérer ce scénario.

<script type="text/javascript">
   A4J.AJAX.onExpired = function(loc,message) { 
      alert("View expired");
   };
</script
>

Autre alternative, vous pouvez permettre à RichFaces de gérer l'erreur, dans ce cas l'utilisateur aura un message interogatif qui indiquera "View state could't be restored - reload page?" Vous pouvez personnaliser ce message globallement en définissant la clef du message suivant dans le fichier de ressource livré dans l'application.

AJAX_VIEW_EXPIRED=View expired. Please reload the page.

RichFaces (Ajax4jsf) est une bibliothèque Ajax la plus communément utilisée dans Seam, et fourni tous les controls discutés ci-dessous: