Seam Conversation Management using JBoss Seam Components: Part 1

Exclusive offer: get 50% off this eBook here
JSF 1.2 Components

JSF 1.2 Components — Save 50%

Develop advanced Ajax-enabled JSF applications

$26.99    $13.50
by Ian Hlavats | December 2009 | JBoss

In this article by Ian Hlavats, we will look at an example of how to use Seam conversations effectively with Ajax.

We will discuss about:

  • Temporary conversations
  • Starting a long-running conversation
  • Concurrent conversations

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 xmlns="http://jboss.com/products/seam/pages"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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.

JSF 1.2 Components Develop advanced Ajax-enabled JSF applications
Published: November 2009
eBook Price: $26.99
Book Price: $44.99
See more
Select your format and quantity:

conversation02.jsf

Let's look at an example of how to implement this form correctly. First of all, we declare that the view should start a long-running conversation in pages.xml. Then, we bind the form to the conversation-scoped ShippingCalculatorBean.

Now every Ajax request obtains a reference to the same conversation-scoped backing bean. Seam serializes the Ajax requests within a long-running conversation on the server side to control concurrency and to ensure our backing bean is handling at most one request at a time.

<h:form>
<rich:panel style="width:50%">
<f:facet name="header">
<h:outputText value="Shipping Calculator (with Long-Running
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>

Starting a long-running conversation

There are several ways to start a long-running conversation in Seam. We can declare that a view should automatically start a long-running conversation in pages.xml; we can set the conversationPropagation request parameter to begin using the Seam tag, or we can annotate a backing bean method with the Seam framework's @Begin annotation. Let's look at an example of defining and executing a long-running conversation based on an online product ordering use case.

Declaring navigation rules in faces-config.xml

In this example, we design a JSF page flow in our faces-config.xml file that ties together all the necessary screens that make up this use case. The first screen in the page flow is the customer registration step. The second screen is the shipping and product information step. The third screen is the order details confirmation step. Our navigation flow branches after the third step. If we are out of stock, we redirect the user to an error page. Otherwise, we redirect the user to the order processed page. The following screenshot shows a visual representation of the navigation rules that make up our page flow.

The navigation rules are declared in faces-config.xml as follows:

<navigation-rule>
<display-name>order/step1.jsf</display-name>
<from-view-id>/order/step1.jsf</from-view-id>
<navigation-case>
<from-outcome>next</from-outcome>
<to-view-id>/order/step2.jsf</to-view-id>
<redirect />
</navigation-case>
</navigation-rule>
<navigation-rule>
<display-name>order/step2.jsf</display-name>
<from-view-id>/order/step2.jsf</from-view-id>
<navigation-case>
<from-outcome>next</from-outcome>
<to-view-id>/order/step3.jsf</to-view-id>
<redirect />
</navigation-case>
</navigation-rule>
<navigation-rule>
<display-name>order/step3.jsf</display-name>
<from-view-id>/order/step3.jsf</from-view-id>
<navigation-case>
<from-outcome>success</from-outcome>
<to-view-id>/order/success.jsf</to-view-id>
<redirect />
</navigation-case>
</navigation-rule>
<navigation-rule>
<display-name>order/step3.jsf</display-name>
<from-view-id>/order/step3.jsf</from-view-id>
<navigation-case>
<from-outcome>out-of-stock</from-outcome>
<to-view-id>/order/out-of-stock.jsf</to-view-id>
<redirect />
</navigation-case>
</navigation-rule>
<navigation-rule>
<display-name>order/step3.jsf</display-name>
<from-view-id>/order/step3.jsf</from-view-id>
<navigation-case>
<from-outcome>back</from-outcome>
<to-view-id>/order/step2.jsf</to-view-id>
<redirect />
</navigation-case>
</navigation-rule>
<navigation-rule>
<display-name>order/step2.jsf</display-name>
<from-view-id>/order/step2.jsf</from-view-id>
<navigation-case>
<from-outcome>back</from-outcome>
<to-view-id>/order/step1.jsf</to-view-id>
<redirect />
</navigation-case>
</navigation-rule>
<navigation-rule>
<display-name>order/out-of-stock.jsf</display-name>
<from-view-id>/order/out-of-stock.jsf</from-view-id>
<navigation-case>
<from-outcome>back</from-outcome>
<to-view-id>/order/step3.jsf</to-view-id>
<redirect />
</navigation-case>
</navigation-rule>

Defining a long-running conversation in pages.xml

We have grouped all the views for this use case in one directory named "order". Conveniently, we can declare in pages.xml that all the pages in this directory should be associated with a long-running conversation as follows:

<page view-id="/order/*">
<begin-conversation join="true"/>
</page>

Implementing OrderBeanImpl.java

Next, we implement a long-running conversation-scoped stateful session bean. We use the EJB3 @Stateful annotation to indicate to the EJB container that this component should be managed as a stateful session bean. This includes automatic, JTA-enabled transactions for all methods, activation/passivation for efficient memory management, declarative security, and more.

We use the Seam framework's @Conversational annotation to indicate that this component should only be invoked within the context of a long-running conversation. If a view attempts to access this bean outside a long-running conversation, Seam will throw an exception.

We use the @Name annotation to indicate that the bean should be available as a JSF managed bean with the name orderBean, and we use the @Scope annotation to indicate that the bean should be stored in conversation scope.

We also use several Seam annotations within the class to declare some dependencies which will be injected by the Seam framework. As we are using a Seam-managed persistence context, we can annotate our EntityManager instance variable with the Seam framework's @In annotation  and Seam will inject the appropriate object at runtime. Notice that we use the Seam @End annotation  on our submitOrder method to indicate that the long-running conversation should be concluded after the method is invoked.

OrderBeanImpl.java

@Stateful
@Conversational
@Name("orderBean")
@Scope(ScopeType.CONVERSATION)
public class OrderBeanImpl implements OrderBean {
private LineItem lineItem;
@In
private EntityManager em;
@In
private FacesMessages facesMessages;
@Logger
private Log logger;
private Order order;
private List<Order> orders;
public LineItem getLineItem() {
if (lineItem == null) {
lineItem = new LineItem();
lineItem.setQuantity(1);
Order order = getOrder();
lineItem.setOrder(order);
order.getLineItems().add(lineItem);
}
return lineItem;
}
public Order getOrder() {
if (order == null) {
order = new Order();
order.setCustomer(new Customer());
}
return order;
}
private Integer getUniqueOrderNumber() {
Integer value = null;
Query query = em.createNamedQuery(Queries.UNIQUE_ORDER_NUMBER);
value = (Integer) query.getSingleResult();
return value;
}
@SuppressWarnings("unchecked")
public List<Order> getOrders() {
if (orders == null) {
orders =
em.createNamedQuery(Queries.ALL_ORDERS).getResultList();
}
return orders;
}
public void setOrder(Order order) {
this.order = order;
}
@End

public String submitOrder() {
String outcome = null;
try {
if (lineItem.getProduct().getQuantityInStock() > 0) {
order.setOrderNumber(getUniqueOrderNumber());
em.persist(order);
facesMessages.add("Thank you. Your order has been
received.");
outcome = "success";
} else {
outcome = "out-of-stock";
}
} catch (Exception e) {
logger.error("Failed to submit order:", e);
facesMessages.add(Severity.ERROR, "Sorry, we were unable to
process your order.");
}
return outcome;
}
@Remove
@Destroy
public void remove() {
}
}

In the preceding example, we use the JPA EntityManager API to execute named queries. A named query is a Java Persistence Query Language (JPQL)  query named  and declared in an external configuration file or in an annotation. As we are using Hibernate as our JPA provider, we externalize our named queries in a file named Queries.hbm.xml. When our application is initialized, Hibernate will scan this file and validate our named queries, reporting issues such as invalid syntax, missing  columns, invalid object paths, and so on. Our JPA named queries are declared as follows:

Queries.hbm.xml

<?xml version="1.0" encoding='UTF-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
<hibernate-mapping>
<query name="Product.findAll">
<![CDATA[
select distinct product
from Product product
inner join fetch product.category
order by product.name
]]>
</query>
<query name="ProductCategory.findAll">
<![CDATA[
select category
from ProductCategory category
order by category.name
]]>
</query>
<query name="Customer.findAll">
<![CDATA[
select customer
from Customer customer
order by customer.lastName, customer.firstName
]]>
</query>
<query name="Country.findAll">
<![CDATA[
select country
from Country country
order by country.name
]]>
</query>
<query name="Order.findAll">
<![CDATA[
select o
from Order o
order by o.orderNumber
]]>
</query>
<query name="ProductCategory.findByName">
<![CDATA[
select category
from ProductCategory category
where category.name = ?
order by category.name
]]>
</query>
<query name="ProductCategory.findSubCategoryByName">
<![CDATA[
select category
from ProductCategory category
inner join fetch category.parentCategory parent
where parent = ?
and category.name = ?
order by category.name
]]>
</query>
<query name="Product.findByPrice">
<![CDATA[
select product
from Product product
where product.price <= ?
]]>
</query>
<query name="Order.findUniqueOrderNumber">
<![CDATA[
select (max(o.orderNumber) + 1)
from Order o
]]>
</query>
</hibernate-mapping>

Notice in the OrderBeanImpl source code listing that we called the EntityManager API by passing a String constant as follows:

em.createNamedQuery(Queries.UNIQUE_ORDER_NUMBER)

Instead of hardcoding the query name in our code, we also externalize this information in a constant interface. By using a constant we avoid the risk of typos in our code as the Java compiler will ensure that our constant reference is valid. Also, we can reuse the query more easily. This indirection has an additional benefit: if we decide to rename the query, we can do so in one place (the constant interface) and we do not have to hunt through code to find and replace string literals.

Queries.java

package chapter6.model;
public class Queries {
public static final String ALL_COUNTRIES = "Country.findAll";
public static final String ALL_CUSTOMERS = "Customer.findAll";
public static final String ALL_ORDERS = "Order.findAll";
public static final String ALL_PRODUCT_CATEGORIES =
"ProductCategory.findAll";
public static final String ALL_PRODUCTS = "Product.findAll";
public static final String PRODUCT_CATEGORY_BY_NAME =
"ProductCategory.findByName";
public static final String PRODUCT_SUBCATEGORY_BY_NAME =
"ProductCategory.findSubCategoryByName";
public static final String PRODUCTS_BY_PRICE =
"Product.findByPrice";
public static final String UNIQUE_ORDER_NUMBER =
"Order.findUniqueOrderNumber";

>> Continue Reading Seam conversation management using JBoss Seam Components: Part 2

 

[ 1 | 2 ]
JSF 1.2 Components Develop advanced Ajax-enabled JSF applications
Published: November 2009
eBook Price: $26.99
Book Price: $44.99
See more
Select your format and quantity:

About the Author :


Ian Hlavats

Ian Hlavats is an experienced Java developer, instructor, speaker, and author of the book JSF 1.2 Components (Packt). He has worked for clients in government, insurance, and entertainment industries, writing Java applications using Swing, Struts, JSF2, PrimeFaces, jQuery, and other UI technologies. He has delivered Java courses in college and corporate training environments including a one-year engagement with Cognos/IBM.

He is on the JSF 2.2 Expert Group and contributed to the next generation of the JSF specification. A regular speaker at Java EE conferences, he has given presentations on JSF and PrimeFaces technologies since 2008 at JSF Summit, NFJS, and JAXConf in San Francisco. He is the creator of JSFToolbox for Dreamweaver, a suite of design and coding extensions for JSF developers. He co-hosts a podcast on JSF and Java EE technologies with fellow authors Kito D. Mann and Daniel Hinojosa. He holds a Bachelor of Humanities degree from Carleton University and IT certificates from Algonquin College.

Books From Packt

GlassFish Administration
GlassFish Administration

JBoss AS 5 Development
JBoss AS 5 Development

Spring Persistence with Hibernate
Spring Persistence with Hibernate

Tomcat 6 Developer's Guide
Tomcat 6 Developer's Guide

RESTful Java Web Services
RESTful Java Web Services

ICEfaces 1.8: Next Generation Enterprise Web Development
ICEfaces 1.8: Next Generation Enterprise Web Development

JBoss RichFaces 3.3
JBoss RichFaces 3.3

Apache Maven 2 Effective Implementation
Apache Maven 2 Effective Implementation

No votes yet

Post new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
W
Z
G
h
G
p
Enter the code without spaces and pay attention to upper/lower case.
Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software