Welcome!

Java Authors: Maureen O'Gara, Bruce Armstrong, Liz McMillan, Walter H. Pinson, III, Yakov Werde

Related Topics: Java

Java: Article

Seam: The Next Step in the Evolution of Web Applications

A powerful new application framework for managing contextual components

To get a better feel for it, let's add some additional functions to the search component and see exactly how the conversation works.

static final int PAGE_SIZE = 10;

int currentPage = 0;
boolean hasMore = false;

@Begin(join=true)
@Factory("products")
public void loadProducts()
{
    List<Product> items =
    em.createQuery("from Product p")
      .setMaxResults(PAGE_SIZE+1)
      .setFirstResult(PAGE_SIZE*currentPage)
      .getResultList();

    if (items.size() > PAGE_SIZE) {
      products = new ArrayList<Product>(items.subList(0,PAGE_SIZE));
      hasMore = true;
    } else {
      products = items;
      hasMore = false;
    }
}

public boolean isFirstPage() {
    return currentPage==0;
}
public boolean isLastPage() {
    return !hasMore;
}

public String nextPage() {
    if (!isLastPage()) {
      currentPage++;
      loadProducts();
    }
    return null;
}

public String prevPage() {
    if (!isFirstPage()) {
      currentPage--;
      loadProducts();
    }
    return null;
}

The changes add a little bit of extra state to the search component, namely the concept of a current page in the search results. The loadProducts() method now uses the current page number to load only a single page of results. An extra product is loaded as a test to see if there are are additional results available in the database. That provides enough information for the isFirstPage() and isLastPage() methods to determine whether or not the current page is either the first or last page of the results.

Finally, SearchAction now provides nextPage() and prevPage() action methods to alter the current state of the component. These methods work by changing the current page number and reloading the products list. The new products list is pushed out to the view to be displayed.

The listing below shows all the required changes to the view.

<h:form>
   <h:dataTable value="#{products}" var="prod">
     <!-- ... same as previous example -->
   </h:dataTable>

   <h:commandButton action="#{search.prevPage}"
value="previous" rendered="#{!search.firstPage}"/>
   <h:commandButton action="#{search.nextPage}"
value="next" rendered="#{!search.lastPage}"/>
</h:form>

The output is wrapped in a form that contains buttons linking to the prevPage() and nextPage() actions on the search component. Seam components are directly usable from a view by name. Seam will make sure that the appropriate instance for the current context (the current conversation, in this case) is available.

Let's look more closely at this. To display the products list on the page, Seam locates the search component, which the products factory, and loads the data. The loadProducts() method is annotated with @Begin, which hints to Seam that the current conversation (the one started when the page was first loaded) is a long-running conversation and should be kept around (without this hint, Seam would assume the conversation would expire at the end of the request and remove the conversation, along with any components in that scope).

With a conversation in place, the previous and next buttons automatically link to the action methods on the same instance we're talking to now. As long as the user continues to click through these actions, the conversation is maintained and the users sees the appropriate state.

However, if the user loads the page again or accesses the page in another tab or window, Seam will interpret this as a request for a new conversation and create a new stateful search component to service this conversation. Seam can maintain any number of concurrent conversations with a user, letting the user browse through multiple sets of search results without ever confusing the data between conversations as might happen if the search results were naively put in the HTTP session.

This is an amazingly powerful concept considering the component didn't have to be specially coded to be able to do it. The search component simply implements the logic required to maintain its own state. Seam takes care of controlling the visibility and lifecycle of the instances.

To complete the example and show a few extra features of Seam, we'll add the ability to select specific products and add them to a shopping cart. We'll start with a basic shopping cart component.

@Stateful
@Name("cart")
@Scope(ScopeType.SESSION)
@Interceptors(SeamInterceptor.class)
public class ShoppingCartBean
    implements ShoppingCart
{
    private List<Product> contents =
      new ArrayList<Product>();

    public List<Product> getContents() {
      return contents;
    }

    public void addToCart(Product product) {
      contents.add(product);
    }

    @Destroy @Remove
    public void destroy() {
    }
}

It's another stateful session bean. The state of the cart is the list of products in the cart, and the bean provides a method for adding a product to the cart. Just like the search component, the cart is a simple POJO concerned only about managing its state and providing useful functions to the application.

The shopping cart is a session-scoped bean. No matter how many simultaneous searches a user does, items should be added to the same cart instance. Session scope matches this perfectly, so we use the @Scope annotation to override Seam's default conversational scope for stateful components.

We're done with the shopping cart, so now we'll head back to the search component and its UI view. We need a simple way to operate on items in the products list. JSF provides a DataModel interface that can be used in conjunction with the dataTable tag to provide a concept of selecting an individual row of the table. Using it, however, would couple our application to the UI and move the search component more towards being a UI component than an application component.

Seam provides the solution here by providing UI wrapper annotations that let you construct UI components out of internal state. By replacing the @Out tag with the @DataModel tag on the products list, Seam will make sure that the UI sees a proper JSF DataModel instead of a simple list.

     @DataModel
     private List<Product> products;

A DataModel can have a selected item, and Seam can inject the selected item back into the search component using the @DataModelSelection annotation. Whenever an action is done on a row in the products dataTable, the selectedProduct value will be set accordingly.

     @DataModelSelection
     Product selectedProduct;

The dataTable can be easily updated with a button to add a specific item to the cart. The following code adds a "Buy it!" button on each row of the table.

<h:dataTable value="#{products}" var="prod">
   <!-- columns for title, description, and price -->
   <h:column>
     <f:facet name="header">Add to Cart</f:facet>
     <h:commandButton action="#{search.select}" value="Buy it!" />
   </h:column>
</h:dataTable>

More Stories By Norman Richards

Norman Richards is a JBoss developer living in Austin, Tx. He is co-author of JBoss: A Developer's Notebook and XDoclet in Action.

Comments (1) View Comments

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.


Most Recent Comments
SYS-CON Belgium News Desk 02/19/06 01:41:18 PM EST

Web sites were originally static. Later dynamic content came about through CGI scripts paving the way for the first true Web applications. Since HTTP was entirely stateless, it became necessary to invent ways for requests to be linked together in a sequence. At first state was added to the URLs, but later the cookie concept came into being. By giving each user a special token, the server could maintain a context for each user, the HTTP session where the application can store state. As simple as it is, the HTTP session defines the entire concept of what a Web application is today.