Chapter 9. Pageflows and business processes

JBoss jBPM is a business process management engine for any Java SE or EE environment. jBPM represents a business process or user interaction as a graph of nodes representing wait states, decisions, tasks, web pages, etc. The graph is defined with a simple, very readable XML dialect called jPDL, and can be edited and represented graphically using an Eclipse plug-in. jPDL is an extensible language, suitable for a range of problems, from defining web application pageflow, to managing traditional workflow, all the way up to orchestrating services in a SOA environment.
Seam applications use jBPM for two different problems: defining pageflow in complex user interactions, and defining the overarching business process.
For the former, a jPDL process definition defines the pageflow for a single conversation, whereas a Seam conversation is considered to be a relatively short-running interaction with a single user.
For the latter, the business process may span multiple conversations with multiple users. Its state is persistent in the jBPM database, so it is considered long-running. Coordinating the activities of multiple users is more complex than scripting interaction with a single user, so jBPM offers sophisticated facilities for managing tasks and multiple concurrent execution paths.

Note

Do not confuse pageflow with the overarching business process. They operate at different levels of granularity. Pageflows, conversations, and tasks are all single interactions with a single user. A business process spans many tasks. Furthermore, the two applications of jBPM are not dependent upon each other — you can use them together, independently, or not at all.

Note

It is not necessary to know jPDL to use Seam. If you prefer to define pageflow with JSF or Seam navigation rules, and your application is more data-driven than process-driven, jBPM is probably unnecessary. However, we find that thinking of user interaction in terms of a well-defined graphical representation helps us create more robust applications.

9.1. Pageflow in Seam

There are two ways to define pageflow in Seam:
  • Using JavaServer Faces (JSF) or Seam navigation rules — the stateless navigation model
  • Using jPDL — the stateful navigation model
Simple applications will only require the stateless navigation model. Complex applications will use a combination of the two. Each model has its strengths and weaknesses, and should be implemented accordingly.

9.1.1. The two navigation models

The stateless model defines a mapping from a set of named, logical event outcomes directly to the resulting view page. The navigation rules ignore any state held by the application, other than the page from which the event originates. Therefore, action listener methods must sometimes make decisions about the pageflow, since only they have access to the current state of the application.
Here is an example pageflow 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 pageflow 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>
If you find navigation rules too verbose, you can return view IDs directly from your action listener methods:
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 also specify parameters to be used in the redirect:
public String search() { 
  return "/searchResults.jsp?searchPattern=#{searchAction.searchPattern}"; 
}
The stateful model defines a set of transitions between a set of named, logical application states. With this model, you can 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.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>
Here, we notice two things immediately:
  • The JSF and 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 comprehensible, and removes the need to look at JSP or Java code.
In addition, the stateful model is more constrained. For each logical state (each step in the pageflow), there are a constrained set of possible transitions to other states. The stateless model is an ad hoc model, suitable to relatively unconstrained, freeform navigation where the user decides where he/she wants to go next, not the application.
The distinction between stateful and stateless navigation is similar to that between modal and modeless interaction. Seam applications are not usually modal in the simple sense of the word — we use conversations to avoid modal behavior. However, Seam applications can be, and often are, modal at the level of a particular conversation. Because user movements are not perfectly predictable, modal behavior is best avoided, but it has its place in the stateful model.
The biggest contrast between the two models is the back-button behavior.

9.1.2. Seam and the back button

With JSF or Seam navigation rules, the user can navigate freely with the back, forward and refresh buttons. The application is responsible for ensuring that conversational state remains internally consistent. Experience with web application frameworks and stateless component models has taught developers how difficult this is. It becomes far more straightforward in Seam, where it sits in a well-defined conversational model backed by stateful session beans. Usually, you need only combine a no-conversation-view-id with null checks at the beginning of action listener methods. Freeform navigation support is almost always desirable.
In this case, the no-conversation-view-id declaration goes in pages.xml. This tells Seam to redirect to a different page if a request originates from a page that was rendered during a conversation that no longer exists:
<page view-id="/checkout.xhtml" no-conversation-view-id="/main.xhtml"/>
On the other hand, in the stateful model, 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, by default, not permitted 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, redirecting the user to the "current" page (and displaying a Faces message). Although developers view this as a feature, it can be frustrating from the user's perspective. You can enable back button 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.

Note

If a page is set to redirect after a transition, the back button cannot return a user to that page even when back is enabled on a page later in the flow. This is because Seam stores information about the pageflow in the page scope and the back button must result in a POST for that information to be restored (for example, through a Faces request). A redirect severs this link.
We must still 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 will quickly learn to recognize where one model is better-suited to a task.