7 min read

The JBoss Seam framework provides elegant solutions to a number of problems. One of these problems is the concept of conversation management. Traditional web applications have a limited number of scopes (or container-managed memory regions) in which they can store data needed by the application at runtime.

In a typical Java web application, these scopes are the application scope, the session scope, and the request scope. JSP-based Java web applications also have a page scope. Application scope is typically used to store stateless components or long-term read-only application data. Session scope provides a convenient, medium-term storage for per-user application state, such as user credentials, application preferences, and the contents of a shopping cart. Request scope is short-term storage for per-request information, such as search keywords, data table sort direction, and so on.

Seam introduces another scope for JSF applications: the conversation scope. The conversation scope can be as short-term as the request scope, or as long-term as the session scope. Seam conversations come in two types: temporary conversations and long-running conversations. A temporary Seam conversation typically lasts as  long as a single HTTP request. A long-running Seam conversation typically spans several screens and can be tied to more elaborate use cases and workflows within the application, for example, booking a hotel, renting a car, or placing an order for computer hardware.

There are some important implications for Seam’s conversation management when using Ajax capabilities of RichFaces and Ajax4jsf. As an Ajax-enabled JSF form may involve many Ajax requests before the form is “submitted” by the user at the end of a  use case, some subtle side effects can impact our application if we are not careful. Let’s look at an example of how to use Seam conversations effectively with Ajax.

Temporary conversations

When a Seam-enabled conversation-scoped JSF backing bean is accessed for the first time, through a value expression or method expression from the JSF page for instance, the Seam framework creates a temporary conversation if a conversation does not already exist and stores the component instance in that scope.

If a long-running conversation already exists, and the component invocation requires a long-running conversation, for example by associating the view with a long-running conversation in pages.xml, by annotating the bean class or method with Seam’s @Conversational annotation, by annotating a method with Seam’s @Begin annotation, or by using the conversationPropagation request parameter, then Seam stores the component instance in the existing long-running conversation.

ShippingCalculatorBean.java

The following source code demonstrates how to declare a conversation-scoped backing being using Seam annotations. In this example, we declare the ShippingCalculatorBean as a Seam-managed conversation-scoped component named shippingCalculatorBeanSeam.

@Scope(ScopeType.CONVERSATION)
public class ShippingCalculatorBean implements Serializable {
 /**
 * 
 */
 private static final long serialVersionUID = 1L;
 private Country country;
 private Product product;
 public Country getCountry() {
 return country;
 }
 public Product getProduct() {
 return product;
 }
 public Double getTotal() { 
 Double total = 0d;
 if (country != null && product != null) {
 total = product.getPrice();
 if (country.getName().equals("USA")) {
 total = +5d;
 } else {
 total = +10d;
 }
 }
 return total;
 }
 public void setCountry(Country country) {
 this.country = country;
 }
 public void setProduct(Product product) {
 this.product = product;
 }
}

faces-config.xml

We also declare the same ShippingCalculatorBean class as a request-scoped backing bean named shippingCalculaorBean in faces-config.xml. Keep in mind that the JSF framework manages this instance of the class, so none of the Seam annotations are effective for instances of this managed bean.

<managed-bean>
 <description>Shipping calculator bean.</description>
 <managed-bean-name>shippingCalculatorBean</managed-bean-name>
 <managed-bean-class>chapter5.bean.ShippingCalculatorBean
 </managed-bean-class>
 <managed-bean-scope>request</managed-bean-scope>
</managed-bean>

pages.xml

The pages.xml file is an important Seam configuration file. When a Seam-enabled web application is deployed, the Seam framework looks for and processes a file in the WEB-INF directory named pages.xml.

This file contains important information about the pages in the JSF application, and enables us to indicate if a long-running conversation should be started automatically when a view is first accessed.

In this example, we declare two pages in pages.xml, one that does not start a long-running conversation, and one that does.

<?xml version="1.0" encoding="utf-8"?>
<pages

xsi_schemaLocation=”http://jboss.com/products/seam/pages
http://jboss.com/products/seam/pages-2.1.xsd”>
<page view-id=”/conversation01.jsf” />
<page view-id=”/conversation02.jsf”>
<begin-conversation join=”true”/>
</page>

</pages>

conversation01.jsf

Let’s look at the source code for our first Seam conversation test page. In this page, we render two forms side-by-side in an HTML panel grid. The first form is bound to the JSF-managed request-scoped ShippingCalculatorBean, and the second form is bound to the Seam-managed conversation-scoped ShippingCalculatorBean. The form allows the user to select a product and a shipping destination, and then calculates the shipping cost when the command button is clicked.

When the user tabs through the fields in a form, an Ajax request is sent, submitting the form data and re-rendering the button. The button is in a disabled state until the user has selected a value in both the fields. The Ajax request creates a new HTTP request on the server, so for the first form JSF creates a new request-scoped instance of our ShippingCalculatorBean for every Ajax request.

As the view is not configured to use a long-running conversation, Seam creates a new temporary conversation and stores a new instance of our ShippingCalculatorBean class in that scope for each Ajax request.

Therefore, the behavior that can be observed when running this page in the browser is that the calculation simply does not work. The value is always zero. This is because the model state is being lost due to the incorrect scoping of our backing beans.

<h:panelGrid columns="2" cellpadding="10">
 <h:form>
 <rich:panel>
 <f:facet name="header">
 <h:outputText value="Shipping Calculator (No
 Conversation)" />
 </f:facet>
 <h:panelGrid columns="1" width="100%">
 <h:outputLabel value="Select Product: " for="product" />
 <h:selectOneMenu id="product"
 value="#{shippingCalculatorBean.product}">
 <s:selectItems var="product"
 value="#{productBean.products}"
 label="#{product.name}" noSelectionLabel="Select" />
 <a4j:support event="onchange" reRender="button" />
 <s:convertEntity />
 </h:selectOneMenu>
 <h:outputLabel value="Select Shipping Destination: "
 for="country" />
 <h:selectOneMenu id="country"
 value="#{shippingCalculatorBean.country}">
 <s:selectItems var="country"
 value="#{customerBean.countries}"
 label="#{country.name}" noSelectionLabel="Select" />
 <a4j:support event="onchange"
 reRender="button"/>
 <s:convertEntity />
 </h:selectOneMenu>
 <h:panelGrid columns="1" columnClasses="centered"
 width="100%">
 <a4j:commandButton id="button" value="Calculate" 
 disabled="#{shippingCalculatorBean.country eq null or
 shippingCalculatorBean.product eq null}"
 reRender="total" />
 <h:panelGroup>
 <h:outputText value="Total Shipping Cost: " />
 <h:outputText id="total"
 value="#{shippingCalculatorBean.total}">
 <f:convertNumber type="currency" currencySymbol="$"
 maxFractionDigits="0" />
 </h:outputText>
 </h:panelGroup>
 </h:panelGrid>
 </h:panelGrid>
 </rich:panel>
 </h:form>
 <h:form>
 <rich:panel> 
 <f:facet name="header">
 <h:outputText value="Shipping Calculator (with Temporary
 Conversation)" />
 </f:facet>
 <h:panelGrid columns="1">
 <h:outputLabel value="Select Product: " for="product" />
 <h:selectOneMenu id="product"
 value="#{shippingCalculatorBeanSeam.product}">
 <s:selectItems var="product"
 value="#{productBean.products}"
 label="#{product.name}" noSelectionLabel="Select" />
 <a4j:support event="onchange" 
 reRender="button" />
 <s:convertEntity />
 </h:selectOneMenu>
 <h:outputLabel value="Select Shipping Destination: "
 for="country" />
 <h:selectOneMenu id="country"
 value="#{shippingCalculatorBeanSeam.country}">
 <s:selectItems var="country"
 value="#{customerBean.countries}"
 label="#{country.name}" noSelectionLabel="Select" />
 <a4j:support event="onchange" 
 reRender="button" />
 <s:convertEntity />
 </h:selectOneMenu>
 <h:panelGrid columns="1" columnClasses="centered"
 width="100%">
 <a4j:commandButton id="button" value="Calculate"
 disabled="#{shippingCalculatorBeanSeam.country eq null
 or shippingCalculatorBeanSeam.product eq null}"
 reRender="total" />
 <h:panelGroup>
 <h:outputText value="Total Shipping Cost: " />
 <h:outputText id="total"
 value="#{shippingCalculatorBeanSeam.total}">
 <f:convertNumber type="currency" currencySymbol="$"
 maxFractionDigits="0" />
 </h:outputText>
 </h:panelGroup>
 </h:panelGrid>
 </h:panelGrid>
 </rich:panel>
 </h:form>
</h:panelGrid>

The following screenshot demonstrates the problem of using request-scoped or temporary conversation-scoped backing beans in an Ajax-enabled JSF application. As an Ajax request is simply an asynchronous HTTP request marshalled by client-side code executed by the browser’s JavaScript interpreter, the request-scoped backing beans are recreated with every Ajax request. The model state is lost and the behavior of the components in the view is incorrect.

LEAVE A REPLY

Please enter your comment!
Please enter your name here