Seam FrameworkCommunity Documentation

Chapter 9. Pageflows and business processes

9.1. Pageflow in Seam
9.1.1. The two navigation models
9.1.2. Seam and the back button
9.2. Using jPDL pageflows
9.2.1. Installing pageflows
9.2.2. Starting pageflows
9.2.3. Page nodes and transitions
9.2.4. Controlling the flow
9.2.5. Ending the flow
9.2.6. Pageflow composition
9.3. Business process management in Seam
9.4. Using jPDL business process definitions
9.4.1. Installing process definitions
9.4.2. Initializing actor ids
9.4.3. Initiating a business process
9.4.4. Task assignment
9.4.5. Task lists
9.4.6. Performing a task

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:

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:

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:

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 "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.

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>