SeamFramework.orgCommunity Documentation
JBoss jBPM is a business process management engine for any Java SE or EE environment. jBPM lets you represent a business process or user interaction as a graph of nodes representing wait states, decisions, tasks, web pages, etc. The graph is defined using a simple, very readable, XML dialect called jPDL, and may be edited and visualised graphically using an eclipse plugin. jPDL is an extensible language, and is suitable for a range of problems, from defining web application page flow, to traditional workflow management, all the way up to orchestration of services in a SOA environment.
Seam applications use jBPM for two different problems:
Defining the pageflow involved in complex user interactions. A jPDL process definition defines the page flow for a single conversation. A Seam conversation is considered to be a relatively short-running interaction with a single user.
Defining the overarching business process. The business process may span multiple conversations with multiple users. Its state is persistent in the jBPM database, so it is considered long-running. Coordination of the activities of multiple users is a much more complex problem than scripting an interaction with a single user, so jBPM offers sophisticated facilities for task management and dealing with multiple concurrent paths of execution.
Don't get these two things confused! They operate at very different levels or granularity. Pageflow, conversation and task all refer to a single interaction with a single user. A business process spans many tasks. Futhermore, the two applications of jBPM are totally orthogonal. You can use them together or independently or not at all.
You don't have to know jDPL to use Seam. If you're perfectly happy defining pageflow using JSF or Seam navigation rules, and if your application is more data-driven that process-driven, you probably don't need jBPM. But we're finding that thinking of user interaction in terms of a well-defined graphical representation is helping us build more robust applications.
There are two ways to define pageflow in Seam:
Here is an example page flow definition using JSF navigation rules:
<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>
Here is the same example page flow definition using Seam navigation rules:
<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>
public String guess() {
if (guess==randomNumber) return "/win.jsp";
if (++guessCount==maxGuesses) return "/lose.jsp";
return null;
}
Note that this results in a redirect. You can even specify parameters to be used in the redirect:
public String search() {
return "/searchResults.jsp?searchPattern=#{searchAction.searchPattern}";
}
Here is an example page flow definition using 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>
There are two things we notice immediately here:
The biggest contrast between the two models is the back-button behavior.
<page view-id="/checkout.xhtml"
no-conversation-view-id="/main.xhtml"/>
<page name="checkout"
view-id="/checkout.xhtml"
back="enabled">
<redirect/>
<transition to="checkout"/>
<transition name="complete" to="complete"/>
</page>
This allows navigation via the back button from
the checkout
state to any previous
state!
<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>
<bpm:jbpm />
<bpm:jbpm>
<bpm:pageflow-definitions>
<value>pageflow.jpdl.xml</value>
</bpm:pageflow-definitions>
</bpm:jbpm>
@Begin(pageflow="numberguess")
public void begin() { ... }
Alternatively we can start a pageflow using pages.xml:
<page>
<begin-conversation pageflow="numberguess"/>
</page>
<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>
Each <page>
node represents a state where
the system is waiting for user input:
<page name="displayGuess" view-id="/numberGuess.jsp">
<redirect/>
<transition name="guess" to="evaluateGuess">
<action expression="#{numberGuess.guess}" />
</transition>
</page>
<h:commandButton type="submit" value="Guess" action="guess"/>
<h:commandButton type="submit" value="Guess"/>
Would fire the following un-named transition:
<page name="displayGuess" view-id="/numberGuess.jsp">
<redirect/>
<transition to="evaluateGuess">
<action expression="#{numberGuess.guess}" />
</transition>
</page>
<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>
<decision name="evaluateGuess" expression="#{numberGuess.correctGuess}">
<transition name="true" to="win"/>
<transition name="false" to="evaluateRemainingGuesses"/>
</decision>
A decision is made by evaluating a JSF EL expression in the Seam contexts.
<page name="win" view-id="/win.jsp">
<redirect/>
<end-conversation/>
</page>
<page name="win" view-id="/win.jsp">
<redirect/>
<end-task transition="success"/>
</page>
We need to install jBPM, and tell it where to find the business process definitions:
<bpm:jbpm>
<bpm:process-definitions>
<value>todo.jpdl.xml</value>
</bpm:process-definitions>
</bpm:jbpm>
To initiate a business process instance, we use the
@CreateProcess
annotation:
@CreateProcess(definition="todo")
public void createTodo() { ... }
Alternatively we can initiate a business process using pages.xml:
<page>
<create-process definition="todo" />
</page>
<task name="todo" description="#{todoList.description}">
<assignment actor-id="#{actor.id}"/>
</task>
<task name="todo" description="#{todoList.description}">
<assignment pooled-actors="employees"/>
</task>
<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>
Note that instead of <s:link>
we could have used
a plain JSF <h:commandLink>
:
<h:commandLink action="#{pooledTask.assignToCurrentActor}">
<f:param name="taskId" value="#{task.id}"/>
</h:commandLink>
The pooledTask
component is a built-in component that
simply assigns the task to the current user.
<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>
To begin work on a task, we use either @StartTask
or @BeginTask
on the listener method:
@StartTask
public String start() { ... }
Alternatively we can begin work on a task using pages.xml:
<page>
<start-task />
</page>
If we end the conversation using @EndTask
, Seam
will signal the completion of the task:
@EndTask(transition="completed")
public String completed() { ... }
Alternatively we can use pages.xml:
<page>
<end-task transition="completed" />
</page>