SeamFramework.orgCommunity Documentation
JBoss jBPM est un moteur de gestion du processus métier pour tout environement Java SE ou EE. jBPM vous permet de vous représenter un processus métier ou une intéraction utilisateur comme un graphe de noeud représentant les états d'attente, les décisions, les tâches, les pages web, etc. Le graphe est défini en utilisant un simple et facile à lire dialecte XML appelé jPDL, et peut être édité et visualisé graphiquement en utilisant un plugin d'Eclipse. jPDL est un language extensible et il est adapté pour une grande variété de problème, depuis la définition de l'enchainement de page d'une application web jusqu'à la gestion des traditionnels enchainement de tâches, tout cela dans un esprit d'orchestration des services dans un environnement SOA.
Les applications Seam utilise jBPM pour deux types de problèmes différents:
Définition d'un enchainement de page inclus dans de complexes intéractions de l'utilisateur. Une définition de processus jPDL définie l'enchainement de page pour une seule conversation. Une conversation Seam est considérée pour être une intérraction à relativement courte exécution avec un seul utilisateur.
Définition d'un processus métier hypercintré. Le processus métier peu s'étendre sur de multiples conversations avec de multiples utilisateurs. Son état est persistant dans la base de données jBPM, donc il est considéré comme à longue exécution. La coordination des activités de multiples utilisateurs est un problème beaucoup plus complexe que l'écriture d'une intéraction avec un seul utilisateur, donc jBPM offre des fonctionnalités sophistiquées pour la gestion des tâches et la considération de multiples chemins d'éxécutions concurents.
Ne vous laisser pas embrouillés par ces deux choses ! Elle fonctionne à des niveaux ou de granularité très différents. L'enchainement de page , de conversation et des tâches toutes ce réfèrent à une seule intéraction avec un seul utilisateur. Un processus métier s'étends sur plusieurs tâches. En outre, les deux applications de jBPM sont totalement ortogonales. Vous pouvez les utiliser ensemble ou indépendamment ou pas du tout.
Vous n'avez pas besoin de connaitre jDPL pour utiliser Seam. Si vous êtes parfaitement heureux de définir l'enchainement de page en utilisant JSF ou les règles de navigation de Seam, et si votre application est plus à connatation données qu'à connotation processus, vous n'avez probablement pas besoin de jBPM. Mais nous allons trouver que penser l'interaction utilisateur en terme de représentation graphique bien définie nous aide à construire des applications plus robustes.
Il y a deux façon de définir un enchainement de page dans Seam:
Utiliser les règles de navigation de JSF ou de Seam - le modèle de navigation sans état
Utiliser le jPDL - le modèle de navigation avec état
De très simple applications n'ont seulement besoin que du modèle de navigation sans état. Des applications très complexes auront besoins des deux modèles à des endroits différents. Chaque modèle a ses forces et ses faiblesses!
Le modèle sans état défini une relation depuis un groupe de résultat nomé et la logique d'un évènement directement dans la page résultat de la vue. Les règles de navigation sont entièrement inconnues des autres états inclus dans l'application à part de la page qui a été la source de l'évènement. Cela signifie que les méthodes d'écoute de l'action doivent parfois prendre la décicsion à propos de l'enchainement de page, alors qu'elles n'ont accès qu'à l'état courrant de l'application.
Voici un exemple de définition d'un enchainement de page en utilisant les règles de navigation de JSF:
<navigation-rule>
<from-view-id
>/numberGuess.jsp</from-view-id>
<navigation-case>
<from-outcome
>guess</from-outcome>
<to-view-id
>/numberGuess.jsp</to-view-id>
<redirect/>
</navigation-case>
<navigation-case>
<from-outcome
>win</from-outcome>
<to-view-id
>/win.jsp</to-view-id>
<redirect/>
</navigation-case>
<navigation-case>
<from-outcome
>lose</from-outcome>
<to-view-id
>/lose.jsp</to-view-id>
<redirect/>
</navigation-case>
</navigation-rule
>
Voici un exemple de définition d'un enchainement de page en utilisant les règles de navigation de Seam:
<page view-id="/numberGuess.jsp">
<navigation>
<rule if-outcome="guess">
<redirect view-id="/numberGuess.jsp"/>
</rule>
<rule if-outcome="win">
<redirect view-id="/win.jsp"/>
</rule>
<rule if-outcome="lose">
<redirect view-id="/lose.jsp"/>
</rule>
</navigation>
</page
>
Si vous trouvez que les règles de navigation beaucoup trop verbeuses, vous pouvez retourner l'identifiant de la vue directement depuis la méthode d'écouteur d'action:
public String guess() {
if (guess==randomNumber) return "/win.jsp";
if (++guessCount==maxGuesses) return "/lose.jsp";
return null;
}
Notez que cela résulte dans une redirection. Vous pouvez même spécifier les paramètres à utiliser pour la redirection:
public String search() {
return "/searchResults.jsp?searchPattern=#{searchAction.searchPattern}";
}
Le modèle avec état défini un groupe de transition entre un groupe d'état de l'application logique et nomé. Dans ce modèle, il est possible d'exprimer l'enchainement des interactions utilisateur entièrement dans une définition d'enchainement de page en jPDL et écrire les méthode d'écouteur d'action qui sont complètement ignare du flot d'interaction.
Voici un exemple de définition du flot de page en utilisant jPDL:
<pageflow-definition name="numberGuess">
<start-page name="displayGuess" view-id="/numberGuess.jsp">
<redirect/>
<transition name="guess" to="evaluateGuess">
<action expression="#{numberGuess.guess}" />
</transition>
</start-page>
<decision name="evaluateGuess" expression="#{numberGuess.correctGuess}">
<transition name="true" to="win"/>
<transition name="false" to="evaluateRemainingGuesses"/>
</decision>
<decision name="evaluateRemainingGuesses" expression="#{numberGuess.lastGuess}">
<transition name="true" to="lose"/>
<transition name="false" to="displayGuess"/>
</decision>
<page name="win" view-id="/win.jsp">
<redirect/>
<end-conversation />
</page>
<page name="lose" view-id="/lose.jsp">
<redirect/>
<end-conversation />
</page>
</pageflow-definition
>
Il y a deux choses que nous pouvons noter immédiatement ici:
Les règles de navigation JSF/Seam sont beaucoup plus simples. (Néanmoins, cela masque le fait que le code Java sous-entendu est plus complexe.)
Le jPDL rend les interactions utilisateurs immédiatement compréhensible, sans qu'il soit même nécéssaire de lire le code JSP ou Java.
En plus, le modèle à état est plus constraint. Pour chaque état logique (chaque étape dans l'enchainement de page), il y a un contrainte sur un groupe de transition possible vers les autres états. Le modèle sans état est un modèle ad hoc qui est efficace pour la navigation relativement sans contrainte, libre de formulaire quand l'utilisateur décide là ou il/elle veux aller ensuite, pas l'application.
La distinction entre navigation avec/sans état est assez similaire à la traditionnel vue de l'intération avec/sans modèle. Maintenant, les applications de Seam ne sont pas en général modale au sens strict de ce mot - en fait, éviter la fonctionnalité modal de l'application est une des raisons principale pour avoir les conversations! Malgré tout, les applications Seam peuvent être, et souvent le sont, modale au niveau de conversation particulière. Il est bien connu que la fonctionnalité modale est quelquechose à éviter autant que possible; il est très difficile de prédire dans quel ordre vos utilisateurs vont vouloir faire les choses! Ainsi, il n'y a pas de doutes que le modèle avec état a sa place.
Le plus grand contraste entre les deux modèles est la fonction du bouton-précédent.
Quand les règles de navigation JSF et Seam sont utilisées, Seam laisse l'utilisateur naviger librement avec les boutons précédent, suivant et rafraichir. Il est de la responsabilité de l'application de s'assurer que l'état conversationnel reste en interne consistant quand cela arrive. L'experience avec la combinaisond'application web comme Struts ou WebWork - qui ne supporte pas le modèle conversationnel - et les modèles de composants sans état comme les beans de session sans état EJB ou le serveur d'application Spring a appris à beaucoup de développers qu'il est presque impossible de le faire! Ainsi, notre expérience est que c'est dans le contexte de Seam qu'il y a un modèle conversaionnel bien définie, soutenu par les beans de de sessions avec état, il est actuellement presque prêt. Habituellement, il est presque aussi simple de combiner l'utilisation no-conversation-view-id
avec la vérification de null au démarrage des méthodes d'écouteur d'action. Nous considerons le support de la navigation libre de formulaire presque aussi souhaitable.
Dans ce cas, la déclaration de no-conversation-view-id
va dans pages.xml
. Il indique a Seam de rediriger vers une page différente si une requête originaire d'une page rendue pendant une conversation et que cette conversation n'existe plus:
<page view-id="/checkout.xhtml"
no-conversation-view-id="/main.xhtml"/>
D'un autre côté, dans le modèle avec état, le bouton "précédent" est interprété comme un retour de transition non-définie vers l'état précédent. Depuis que le modèle avec état se renforce comme un groupe définie de transition depuis l'état courrant, le bouton "précédent" est par défaut interdit dans le modèle avec état! Seam de manière transparent détecte l'utilisation du bouton précédent et bloque toute tentative de réaliser une action depuis une page précédente "périmée" et redirige simplement l'utilisateur vers la page "courante" (et affiche un message faces). Que vous considériez comme une fonctionnalité ou une limitation le modèle sans état cela dépend de votre point de vue: comme un développeur d'application, c'est une fonctionnalité, comme un utilisateur, cela peut être frustrant. Vous pouvez activer la navigation avec le bouton "précédent" depuis un noeud de page particulier en indiquant back="enabled"
.
<page name="checkout"
view-id="/checkout.xhtml"
back="enabled">
<redirect/>
<transition to="checkout"/>
<transition name="complete" to="complete"/>
</page
>
Cela autorise le bouton "précédent" depuis l'état checkout vers tout état précédent!
Bien sûr, nous avons toujours besoin de définir ce qu'il arrive si une requête originiaire d'une page rendue pendant l'enchainement de page, et la conversation ave l'enchainement de page n'existe plus. Dans ce cas, la déclaration no-conversation-view-id
va dans la définition d'enchainement de page:
<page name="checkout"
view-id="/checkout.xhtml"
back="enabled"
no-conversation-view-id="/main.xhtml">
<redirect/>
<transition to="checkout"/>
<transition name="complete" to="complete"/>
</page
>
En pratique, les deux modèles de navigation ont leur spécificité et vous allez rapidement apprendre à les reconnaitre quand vous préférerez un modèle plutôt que l'autre.
Nous avons besoin d'installer les composants jBPM-style de Seam et leurs dirent où ils vont trouver la définition de l'enchainement de page. Nous pouvons spécifier cette configuration de Seam dans components.xml.
<bpm:jbpm />
Vous pouvez aussi explicitement indiquer à Seam où trouver la définition d'enchainement de page. Nous spécifions ceci dans components.xml
:
<bpm:jbpm>
<bpm:pageflow-definitions>
<value
>pageflow.jpdl.xml</value>
</bpm:pageflow-definitions>
</bpm:jbpm
>
Nous "démarrons" un enchainement de pages jPDL-style en indiquant le nom de la définition du processus en utilisation une annotation @Begin
, @BeginTask
ou @StartTask
:
@Begin(pageflow="numberguess")
public void begin() { ... }
De manière alternative, nous pouvons démarrer un enchainement de page en utilisant pages.xml:
<page>
<begin-conversation pageflow="numberguess"/>
</page
>
Si nous commençons un enchainement de page pendant la phase RRENDER_RESPONSE
— pendant qu'une méthode @Factory
ou @Create
, par exemple — nous considerons nous même être à la page qui doit être rendue, et utilisons un noeud <start-page>
comme premier noeud dans l'enchainement de page, comme le montre l'exemple ci dessous.
Mais si l'enchainement de page a commencé comme le resulteat de l'invocation d'un écouteur d'action, le retour de lécouteur d'action détermine quel est la première page à être rendue. Dans ca cas, nous utilisons un <start-state>
comme premier noeud de l'enchainement de page et déclarons une transition pour chaque sortie possible:
<pageflow-definition name="viewEditDocument">
<start-state name="start">
<transition name="documentFound" to="displayDocument"/>
<transition name="documentNotFound" to="notFound"/>
</start-state>
<page name="displayDocument" view-id="/document.jsp">
<transition name="edit" to="editDocument"/>
<transition name="done" to="main"/>
</page>
...
<page name="notFound" view-id="/404.jsp">
<end-conversation/>
</page>
</pageflow-definition
>
Chaque noeud <page>
représente un état où le système est en train d'attendre une saisie de l'utilisateur:
<page name="displayGuess" view-id="/numberGuess.jsp">
<redirect/>
<transition name="guess" to="evaluateGuess">
<action expression="#{numberGuess.guess}" />
</transition>
</page
>
Le view-id
est l'identifiant de la vue JSF. L'élément <redirect/>
a le même effet que <redirect/>
dans une règle de navigation : à savoir, une fonctionnalité poster-puis-rediriger, pour contourner les problème avec le bouton rafraichir du navigateur. (Notez que Seam propage les contextes de conversation au travers de ces redirection de navigateur. Il n'y a donc pas besoin d'un fabrique de style Ruby on Rails "flash" dans Seam!)
Le nom de transition est le nom de la sortie JSF déclenché en cliquant sur le bouton de commande ou le lien de commande dans numberGuess.jsp
.
<h:commandButton type="submit" value="Guess" action="guess"/>
Quand la transition est déclenchée par le clic sur le bouton, jBPM ira activer l'action de transition en appelant la méthode guess() du composant numberGuess. Notez que la syntaxe utilisée pour spécifier les actions dans le jPDL est juste une expression familière JSF EL, et que l'action de transation est juste une méthode d'une composant Seam dans le contexte courrant de Seam. Donc, nous avons exactement le même modèle d'évènement pour jBPM que nous avons déjà pour les évènements JSF! (Le principe d'Un Seul Genre de Truc.)
Dans le cas d'un résultat null (par exemple, un bouton de command sans aucune action définie), Seam ira signaler la transition sans aucun nom si une existe, ou sinon simplement réaffiche la page si toutes les transition ont un nom. Donc nous pourrions légèrement simplifier notre exemple d'enchainement de page et ce bouton:
<h:commandButton type="submit" value="Guess"/>
Qui va déclencher la transition sans-nom suivante:
<page name="displayGuess" view-id="/numberGuess.jsp">
<redirect/>
<transition to="evaluateGuess">
<action expression="#{numberGuess.guess}" />
</transition>
</page
>
Il est même possible d'avoir un bouton qui appele une méthode d'action, dans ce cas le résultat de l'action va déterminer la tranistion qui doit être prise:
<h:commandButton type="submit" value="Guess" action="#{numberGuess.guess}"/>
<page name="displayGuess" view-id="/numberGuess.jsp">
<transition name="correctGuess" to="win"/>
<transition name="incorrectGuess" to="evaluateGuess"/>
</page
>
Malgré cela, il est est à considérer comme un style inférieur, car il déplace la responsabilité du contrôl du flot à l'extérieur de la définition de l'enchainement de page et le place dans un autre composants. Il est franchement meilleur de centraliser ce qui s'y rapporte dans l'enchainement de pages lui même.
Habituellement, nous n'avons pas besoin des fonctionnalités les plus puissantes de jPDL pendant la définition des enchainements de pages. Nous avons besoin du noeud <decision>
, malgré tout:
<decision name="evaluateGuess" expression="#{numberGuess.correctGuess}">
<transition name="true" to="win"/>
<transition name="false" to="evaluateRemainingGuesses"/>
</decision
>
Une décision est faite en évaluant une expression JSF EL dans les contextes de Seam.
Nous finissons la conversation en utilisant <end-conversation>
ou @End
. (Dans les faits, pour la lisibilité, l'utilisation des deux est encouragé.)
<page name="win" view-id="/win.jsp">
<redirect/>
<end-conversation/>
</page
>
De manière optionnel, nous pouvons finir une tâche, spécifiant un nom de transition
jBPM. Dans ce cas, Seam va signaler la fin de la tâche courrante dans le processus métier hypercintré.
<page name="win" view-id="/win.jsp">
<redirect/>
<end-task transition="success"/>
</page
>
Il ets possible de composer des enchainements de page et d'avoir une pause dans l'enchainement de page alors qu'un autre enchainement s'exécute. Le noeud <process-state>
met en pausse l'enchainement de page indiqué et comme l'exécution de l'enchainement de page appelé:
<process-state name="cheat">
<sub-process name="cheat"/>
<transition to="displayGuess"/>
</process-state
>
Le flot inclus commence en exécutant un noeud <start-state>
. Quand il atteind un noeud <end-state>
, l'exécution du flot inclus s'arrête et l'exécution du flot englobant recommence avec la transition définie dans l'élement <process-state>
.
Un processus métier est un groupe de tâche bien définis qui doivent être réaliser par des utilisateurs ou des systèmes logiciels en accord avec des règles bien définies à propos de qui peut réaliser une tâche, et quand elle devrait être réalisée. L'intégration de JBPM dans Seam rend facile d'afficher la liste des tâches des utilisateurs et les laisser gérer leurs tâches. Seam permet aussi à l'application stocker l'état associé avec le processus métier dans le contexte BUSINESS_PROCESS
et avoir cet état qui soit persistant via les variables du jBPM.
Une simple définition de processus métier ressemble assez à la définition de l'enchainement de page (Un Seul Genre de Truc), exception sauf qu'au lieu d'un noeud <page>
, nous avons des noeuds <task-node>
. Dans un procéssus métier à exécution longue, les état d'atente sont là où le système est en attente qu'un utilisateur se connecte pour réaliser une tâche.
<process-definition name="todo">
<start-state name="start">
<transition to="todo"/>
</start-state>
<task-node name="todo">
<task name="todo" description="#{todoList.description}">
<assignment actor-id="#{actor.id}"/>
</task>
<transition to="done"/>
</task-node>
<end-state name="done"/>
</process-definition
>
Il est parfaitement possible que nous ayons à la fois des définition de processus métier en jPDL et des définitions d'enchainements de pages en jPDL dans le même projet. Et donc, la relation entre les deux est qu'un seul <task>
dans un processus métier correspond à l'ensemble d'un enchainement de page <pageflow-definition>
Nous avons besoin d'installer un jBPM, et de lui dire où trouver les définition de processus métier :
<bpm:jbpm>
<bpm:process-definitions>
<value
>todo.jpdl.xml</value>
</bpm:process-definitions>
</bpm:jbpm
>
Les processus jBPM sont persistant au travers du redémarrage de l'application, avec l'utilisation de Seam dans un environement de production vous ne voudriez pas avoir à installer la définition de processus à chaque fois que l'application démarre. Cependant, dans un environement de production vous allez avoir besoin de détruire le processus jBPM à l'extérieur de Seam. En d'autres termes, installez seulement le processus de définitions dans components.xml
pendant le développement de votre application.
Nous avons toujours besoin de connaitre quel utilisateur est actuellement connecté. jBPM "connait" les utilisateur par leur identifiant d'acteur et par leurs identifiants de groupe d'acteur. Nous spécifions les identifiant d'acteur courrant en utilisant le composant livré dans Seam nommé actor
:
@In Actor actor;
public String login() {
...
actor.setId( user.getUserName() );
actor.getGroupActorIds().addAll( user.getGroupNames() );
...
}
Pour initialiser une instance d'un processus métier, nous utilisons l'annotation @CreateProcess
:
@CreateProcess(definition="todo")
public void createTodo() { ... }
Autre alternative, nous pouvons initialiser un processus métier en utilisant pages.xml:
<page>
<create-process definition="todo" />
</page
>
Quand un processus démarre, les instances de tâches sont créés. Ils doivent être assignés aux utilisateurs et aux groupes d'utilisateurs. Nous pouvons coder en durs les idenfiants d'acteur ou le déléguer à un composant de Seam:
<task name="todo" description="#{todoList.description}">
<assignment actor-id="#{actor.id}"/>
</task
>
Dans ce cas, nous avons simplement assigné la tâche à l'utilisateur courrant. Nous pouvons aussi assigné les tâches à un groupement:
<task name="todo" description="#{todoList.description}">
<assignment pooled-actors="employees"/>
</task
>
Plusieurs composant livrés dans Seam rendent facile l'affichage de listes de tâches. Le pooledTaskInstanceList
est une liste de tâches en groupement que les utilisateurs peuvent assigner à eux-même:
<h:dataTable value="#{pooledTaskInstanceList}" var="task">
<h:column>
<f:facet name="header"
>Description</f:facet>
<h:outputText value="#{task.description}"/>
</h:column>
<h:column>
<s:link action="#{pooledTask.assignToCurrentActor}" value="Assign" taskInstance="#{task}"/>
</h:column
>
</h:dataTable
>
Notez qu'au lieu de <s:link>
nous pouvons utiliser un <h:commandLink>
en pur JSF :
<h:commandLink action="#{pooledTask.assignToCurrentActor}"
>
<f:param name="taskId" value="#{task.id}"/>
</h:commandLink
>
Le composant pooledTask
est un composant livré qui assigne simplement la tâche à l'utilisateur courrant.
Le composant taskInstanceListForType
inclus les tâches d'un type particulier qui sont assigné à l'utilisateur courrant:
<h:dataTable value="#{taskInstanceListForType['todo']}" var="task">
<h:column>
<f:facet name="header"
>Description</f:facet>
<h:outputText value="#{task.description}"/>
</h:column>
<h:column>
<s:link action="#{todoList.start}" value="Start Work" taskInstance="#{task}"/>
</h:column
>
</h:dataTable
>
Pour commencer le travail sur une tâche, nous utilisons aussi bien @StartTask
ou @BeginTask
sur la méthode d'écoute:
@StartTask
public String start() { ... }
Autre alternative, nous pouvons commencer le travail sur une tâche en utilisant pages.xml:
<page>
<start-task />
</page
>
Ces annotations commencent une type spécial de conversation qui ont de l'importance en terme de processus métier hypercintré. Le travail fait par cette conversation à accès à l'état contenue dans le processus métier dans le contexte de processus métier.
Si nous finisons la conversation en utilisant @EndTask
, Seam va signaler la réalisation de la tâche:
@EndTask(transition="completed")
public String completed() { ... }
Autre alternative, nous pouvons utiliser pages.xml:
<page>
<end-task transition="completed" />
</page
>
Vous pouvez aussi utiliser une EL pour spécifier la transition dans pages.xml.
A ce moment, jBPM prends le contrôle et continue l'exécution de la définition du processus métier. (Dans des processus plus complexes, plusieurs tâches peuvent avoir besoin d'être réalisés avant que l'exécution du processus ne puisse reprendre.)
Merci de vous référer à la document de jBPM pour un apperçu plus détaillé des fonctionnalités sophistiquées que le jBPM fournir pour la gestion des processus métier complexes.