Seam FrameworkCommunity 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 jPDL 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:
Using JSF or Seam navigation rules - the stateless navigation model
Using jPDL - the stateful navigation model
Very simple applications will only need the stateless navigation model. Very complex applications will use both models in different places. Each model has its strengths and weaknesses!
The stateless model defines a mapping from a set of named, logical outcomes of an event directly to the resulting page of the view. The navigation rules are entirely oblivious to any state held by the application other than what page was the source of the event. This means that your action listener methods must sometimes make decisions about the page flow, since only they have access to the current state of the application.
Here is an example page flow definition using JSF navigation rules:
<navigation-rule>
<from-view-id>/numberGuess.xhtml</from-view-id>
<navigation-case>
<from-outcome>guess</from-outcome>
<to-view-id>/numberGuess.xhtml</to-view-id>
<redirect/>
</navigation-case>
<navigation-case>
<from-outcome>win</from-outcome>
<to-view-id>/win.xhtml</to-view-id>
<redirect/>
</navigation-case>
<navigation-case>
<from-outcome>lose</from-outcome>
<to-view-id>/lose.xhtml</to-view-id>
<redirect/>
</navigation-case>
</navigation-rule>
Here is the same example page flow definition using Seam navigation rules:
<page view-id="/numberGuess.xhtml">
<navigation>
<rule if-outcome="guess">
<redirect view-id="/numberGuess.xhtml"/>
</rule>
<rule if-outcome="win">
<redirect view-id="/win.xhtml"/>
</rule>
<rule if-outcome="lose">
<redirect view-id="/lose.xhtml"/>
</rule>
</navigation>
</page>
If you find navigation rules overly verbose, you can return view ids directly from your action listener methods:
public String guess() {
if (guess==randomNumber) return "/win.xhtml";
if (++guessCount==maxGuesses) return "/lose.xhtml";
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.xhtml?searchPattern=#{searchAction.searchPattern}";
}
The stateful model defines a set of transitions between a set of named, logical application states. In this model, it is possible to express the flow of any user interaction entirely in the jPDL pageflow definition, and write action listener methods that are completely unaware of the flow of the interaction.
Here is an example page flow definition using jPDL:
<pageflow-definition name="numberGuess">
<start-page name="displayGuess" view-id="/numberGuess.xhtml">
<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.xhtml">
<redirect/>
<end-conversation />
</page>
<page name="lose" view-id="/lose.xhtml">
<redirect/>
<end-conversation />
</page>
</pageflow-definition>
There are two things we notice immediately here:
The JSF/Seam navigation rules are much simpler. (However, this obscures the fact that the underlying Java code is more complex.)
The jPDL makes the user interaction immediately understandable, without us needing to even look at the facelets template or Java code.
In addition, the stateful model is more constrained. For each logical state (each step in the page flow), there are a constrained set of possible transitions to other states. The stateless model is an ad hoc model which is suitable to relatively unconstrained, freeform navigation where the user decides where he/she wants to go next, not the application.
The stateful/stateless navigation distinction is quite similar to the traditional view of modal/modeless interaction. Now, Seam applications are not usually modal in the simple sense of the word - indeed, avoiding application modal behavior is one of the main reasons for having conversations! However, Seam applications can be, and often are, modal at the level of a particular conversation. It is well-known that modal behavior is something to avoid as much as possible; it is very difficult to predict the order in which your users are going to want to do things! However, there is no doubt that the stateful model has its place.
The biggest contrast between the two models is the back-button behavior.
When JSF or Seam navigation rules are used, Seam lets the user freely
navigate via the back, forward and refresh buttons. It is the
responsibility of the application to ensure that conversational
state remains internally consistent when this occurs. Experience
with the combination of web application frameworks like Struts
or WebWork - that do not support a conversational model - and
stateless component models like EJB stateless session beans
or the Spring framework has taught many developers that this is
close to impossible to do! However, our experience is that in
the context of Seam, where there is a well-defined conversational
model, backed by stateful session beans, it is actually quite
straightforward. Usually it is as simple as combining the use
of no-conversation-view-id
with null
checks at the beginning of action listener methods. We consider
support for freeform navigation to be almost always desirable.
In this case, the no-conversation-view-id
declaration goes in pages.xml
. It tells
Seam to redirect to a different page if a request originates
from a page rendered during a conversation, and that conversation
no longer exists:
<page view-id="/checkout.xhtml"
no-conversation-view-id="/main.xhtml"/>
On the other hand, in the stateful model, using the back button is
interpreted as an undefined transition back to a previous state.
Since the stateful model enforces a defined set of transitions
from the current state, the back button is not permitted by default
in the stateful model! Seam transparently detects the use of the
back button, and blocks any attempt to perform an action from
a previous, "stale" page, and simply redirects the user to
the "current" page (and displays a faces message). Whether you
consider this a feature or a limitation of the stateful model
depends upon your point of view: as an application developer,
it is a feature; as a user, it might be frustrating! You can
enable backbutton navigation from a particular page node by
setting back="enabled"
.
<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!
Of course, we still need to define what happens if a request
originates from a page rendered during a pageflow, and the
conversation with the pageflow no longer exists. In this case,
the no-conversation-view-id
declaration
goes into the pageflow definition:
<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>
In practice, both navigation models have their place, and you'll quickly learn to recognize when to prefer one model over the other.
We need to install the Seam jBPM-related components, and place the
pageflow definitions (using the standard .jpdl.xml
extension) inside a Seam archive (an archive which
contains a seam.properties
file):
<bpm:jbpm />
We can also explicitly tell Seam where to find our pageflow
definition. We specify this in components.xml
:
<bpm:jbpm>
<bpm:pageflow-definitions>
<value>pageflow.jpdl.xml</value>
</bpm:pageflow-definitions>
</bpm:jbpm>
We "start" a jPDL-based pageflow by specifying the name of the
process definition using a @Begin
,
@BeginTask
or @StartTask
annotation:
@Begin(pageflow="numberguess")
public void begin() { ... }
Alternatively we can start a pageflow using pages.xml:
<page>
<begin-conversation pageflow="numberguess"/>
</page>
If we are beginning the pageflow during the RENDER_RESPONSE
phase — during a @Factory
or @Create
method, for example — we consider ourselves to be already at the page being
rendered, and use a <start-page>
node as the first node
in the pageflow, as in the example above.
But if the pageflow is begun as the result of an action listener invocation,
the outcome of the action listener determines which is the first page to be
rendered. In this case, we use a <start-state>
as
the first node in the pageflow, and declare a transition for each possible
outcome:
<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>
The view-id
is the JSF view id. The <redirect/>
element has the same effect as <redirect/>
in a
JSF navigation rule: namely, a post-then-redirect behavior, to overcome problems
with the browser's refresh button. (Note that Seam propagates conversation contexts
over these browser redirects. So there is no need for a Ruby on Rails style "flash"
construct in Seam!)
The transition name is the name of a JSF outcome triggered by clicking
a command button or command link in numberGuess.jsp
.
<h:commandButton type="submit" value="Guess" action="guess"/>
When the transition is triggered by clicking this button, jBPM will activate the
transition action by calling the guess()
method of the
numberGuess
component. Notice that the syntax used for
specifying actions in the jPDL is just a familiar JSF EL expression, and that
the transition action handler is just a method of a Seam component in the
current Seam contexts. So we have exactly the same event model for jBPM events
that we already have for JSF events! (The One Kind of Stuff
principle.)
In the case of a null outcome (for example, a command button with no
action
defined), Seam will signal the transition with no
name if one exists, or else simply redisplay the page if all transitions
have names. So we could slightly simplify our example pageflow and this button:
<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>
It is even possible to have the button call an action method, in which case the action outcome will determine the transition to be taken:
<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>
However, this is considered an inferior style, since it moves responsibility for controlling the flow out of the pageflow definition and back into the other components. It is much better to centralize this concern in the pageflow itself.
Usually, we don't need the more powerful features of jPDL when defining pageflows.
We do need the <decision>
node, however:
<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.
We end the conversation using <end-conversation>
or @End
. (In fact, for readability, use of
both is encouraged.)
<page name="win" view-id="/win.jsp">
<redirect/>
<end-conversation/>
</page>
Optionally, we can end a task, specify a jBPM transition
name. In this case, Seam will signal the end of the current task in the
overarching business process.
<page name="win" view-id="/win.jsp">
<redirect/>
<end-task transition="success"/>
</page>
It is possible to compose pageflows and have one pageflow pause
pause while another pageflow executes. The <process-state>
node pauses the outer pageflow, and begins execution of a named
pageflow:
<process-state name="cheat">
<sub-process name="cheat"/>
<transition to="displayGuess"/>
</process-state>
The inner flow begins executing at a <start-state>
node. When it reaches an <end-state>
node,
execution of the inner flow ends, and execution of the outer flow
resumes with the transition defined by the <process-state>
element.
A business process is a well-defined set of tasks that must
be performed by users or software systems according to
well-defined rules about who can perform
a task, and when it should be performed.
Seam's jBPM integration makes it easy to display lists of
tasks to users and let them manage their tasks. Seam also
lets the application store state associated with the business
process in the BUSINESS_PROCESS
context,
and have that state made persistent via jBPM variables.
A simple business process definition looks much the same as a
page flow definition (One Kind of Stuff),
except that instead of <page>
nodes,
we have <task-node>
nodes. In a
long-running business process, the wait states are where the
system is waiting for some user to log in and perform a task.
<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>
It is perfectly possible that we might have both jPDL business
process definitions and jPDL pageflow definitions in the
same project. If so, the relationship between the two is that
a single <task>
in a business process
corresponds to a whole pageflow
<pageflow-definition>
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>
As jBPM processes are persistent across application restarts,
when using Seam in a production environment you won't want to
install the process definition every time the application starts.
Therefore, in a production environment, you'll need to deploy
the process to jBPM outside of Seam. In other words, only install
process definitions from components.xml
when
developing your application.
We always need to know what user is currently logged in.
jBPM "knows" users by their actor id
and group actor ids. We specify the
current actor ids using the built in Seam component named
actor
:
@In Actor actor;
public String login() {
...
actor.setId( user.getUserName() );
actor.getGroupActorIds().addAll( user.getGroupNames() );
...
}
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>
When a process reaches a task node, task instances are created. These must be assigned to users or user groups. We can either hardcode our actor ids, or delegate to a Seam component:
<task name="todo" description="#{todoList.description}">
<assignment actor-id="#{actor.id}"/>
</task>
In this case, we have simply assigned the task to the current user. We can also assign tasks to a pool:
<task name="todo" description="#{todoList.description}">
<assignment pooled-actors="employees"/>
</task>
Several built-in Seam components make it easy to display task lists.
The pooledTaskInstanceList
is a list of pooled tasks
that users may assign to themselves:
<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.
The taskInstanceListForType
component includes tasks of
a particular type that are assigned 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>
These annotations begin a special kind of conversation that has significance in terms of the overarching business process. Work done by this conversation has access to state held in the business process context.
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>
You can also use EL to specify the transition in pages.xml.
At this point, jBPM takes over and continues executing the business process definition. (In more complex processes, several tasks might need to be completed before process execution can resume.)
Please refer to the jBPM documentation for a more thorough overview of the sophisticated features that jBPM provides for managing complex business processes.