Skip to main content

Seamless JSF, Part 2: Conversations with Seam

Use Seam to build a stateful CRUD application

Dan Allen (dan.allen@mojavelinux.com), Senior Java engineer, CodeRyte, Inc.
Dan Allen
Dan Allen is currently a senior Java engineer at CodeRyte, Inc. He is also a passionate open source advocate and gets a bit manic whenever he catches sight of a penguin. After graduating from Cornell University with a degree in Materials Science and Engineering, Dan became captivated by the world of Linux and open source software. He has been slaving over Web applications ever since, with the last several years focused exclusively on Java-related technologies, including Spring, Hibernate, Maven 2, and the abundant JSF stack. You can keep up with Dan's development experiences by subscribing to his blog at http://www.mojavelinux.com.

Summary:  Developing a stateful CRUD application is a breeze with Seam on the job. In this second article in his Seamless JSF series, Dan Allen shows you how to use Java™Server Faces (JSF) and Seam to develop the create, read, update, and delete use cases for a Web-based golf course directory. Along the way, he highlights a couple of Seam's enhancements to the JSF life cycle -- namely the conversation scope and configuration through custom Java 5 annotations -- and explains how they can reduce server load and trim your development time.

View more content in this series

Date:  01 May 2007
Level:  Intermediate
Activity:  14739 views

The first article in this three-part series introduced Seam, an application framework that significantly enhances the capabilities of JSF and substantiates the component-based architecture on which it is based. In that article, I explained what differentiates Seam from other Web frameworks typically paired with JSF, showed you how easy it is to add Seam to an existing JSF application, and concluded with an overview of Seam's enhancements to the JSF application life cycle, touching on stateful conversations, factory components, and no-fuss configuration using annotations.

While that article might have piqued your interest in Seam, you might not be convinced yet that it will improve your JSF development experience. Integrating a new set of tools is generally far more complex than reading about it, and sometimes not as rewarding. In this second article in the Seamless JSF series, you can find out for yourself whether Seam delivers on its promise to simplify JSF development. After using Seam to build a sample application that performs the standard CRUD operations, I'm positive that you will agree that Seam is an indispensable extension to the JSF framework. As it turns out, Seam can also help take a load off of the limited resources of the database tier.

About this series

Seamless JSF showcases Seam as the first application framework that truly fits JSF, compensating for its major weaknesses like no other extension framework does. Read the articles in this series and decide for yourself whether Seam is a worthy complement to JSF.

The Open 18 application

Open 18 is a Web-based application that allows users to manage a list of the golf courses where they have played and track scores for each round. For the purposes of this discussion, the scope of the application is limited to simply managing the directory of golf courses. The first screen presents a list of courses already entered, displaying a couple of relevant fields for each one, such as the course name, the location, and the phone number for the pro shop. From there, the user can view the full details of a course, add a new course, edit an existing course, and finally, delete a course.

As I show you how to use Seam to develop the use cases for the Open 18 application, I focus on how it simplifies the code, automatically manages state over a series of requests, and enforces data model validations on the input data.

One of my goals for this series is to demonstrate that Seam can be integrated into any existing JSF application without requiring a switch to Enterprise JavaBeans (EJB) 3. Accordingly, the Open 18 application does not rely on Seam's JPA EntityManager integration for transactional database access, or EBJ3 stateful session beans for state management. (Both of these technologies are used in many of the examples packaged with Seam.) Open 18 is designed using a stateless, layered architecture. The service and data access (DAO) layers are wired together using the Spring framework. I believe this design is an applicable choice given Spring's ubiquity in the Web application landscape. This application demonstrates how stateful behavior can be introduced to JSF managed beans through the use of the conversation scope. Keep in mind that these beans are simple POJOs.

You may download the Open 18 source file, as well as Maven 2, to compile and run the sample code. To get you started quickly, I have already configured the application to use Seam and the Spring-JSF integration. If you would like to set up Seam in your own project, you can find complete instructions for doing so in the first article in this series. See Resources to learn more about integrating JSF and Spring.

A tale of two containers

The first step to building a JSF application that leverages the Spring framework is to configure JSF so that it can access beans in the Spring container. The spring-web package, part of the Spring distribution, ships with a custom JSF variable resolver to establish this bridge. The Spring resolver first delegates to the native resolver supplied with the JSF implementation. The native resolver attempts to match a value binding reference, such as #{courseManager}, to one of the managed beans in the JSF container. The bean name consists of the characters between the #{} expression delimiters, in this case courseManager. If that lookup fails to find a match, the custom resolver then looks into Spring's WebApplicationContext to find a Spring bean with a matching id attribute. Keep in mind that Seam is an extension to the JSF framework, so any variable that is accessible to JSF is also accessible to Seam.

The Spring variable resolver is configured in the faces-config.xml file using the variable resolver node, as shown in Listing 1:


Listing 1. Configuring the spring variable resolver
                
<variable-resolver>
  org.springframework.web.jsf.DelegatingVariableResolver
</variable-resolver>


Seam's contextual components

For the purpose of this article, I'm assuming that the role of a Spring-based service layer is self-evident. Aside from the JSF-Spring integration layer, which is responsible for exposing the Spring beans to JSF (and therefore to Seam), the usage of Spring is peripheral. The service layer object will be treated as a stateless interface to which the CRUD operations can be delegated. With these application details out of the way, you are free to focus on how Seam transforms managed beans into stateful components that are cognizant of their role in facilitating the user's interaction with the application.

You begin developing the Open 18 application by creating a backing bean named courseAction to support the views that manage the directory of golf courses. This managed bean exposes a collection of golf course objects and then responds to actions for managing those instances. The persistence of that data is delegated to the Spring-based service layer.

In a typical JSF application, you use the managed bean facility to register the CourseAction bean and inject it with its delegate objects, or "dependencies." To do this, you must open the faces-config.xml file and add a new managed-bean node with the name and class of the bean, as shown in Listing 2. You specify the dependencies to be injected into the properties of the class by adding child managed-property nodes that reference other managed beans using value binding expressions. In this case, the only dependency is the stateless service object courseManager, implemented using the GenericManager class from the Appfuse project (see Resources).


Listing 2. CourseAction defined as a JSF managed bean
                
<managed-bean>
  <managed-bean-name>courseAction</managed-bean-name>
  <managed-bean-class>com.ibm.dw.open18.CourseAction</managed-bean-class>
  <managed-property>
    <property-name>courseManager</property-name>
    <value>#{courseManager}</value>
  </managed-property>
</managed-bean>

Cut the XML, annotate!

Now that you've been reminded of what a snarl it is to use the native JSF approach for defining managed beans, forget that you ever saw a managed-bean XML declaration -- because you no longer need one! In a Seam-built application, beans are declared simply by using Java 5 annotations. Seam refers to these beans as contextual components. Though this term may strike you as rather esoteric, it merely characterizes a component, or named instance, as being relevant with a given scope, otherwise known as a context.

Seam manages its contextual components for the lifetime of the scope in which they are assigned. Seam components are more like Spring beans than JSF managed beans in that they plug into a sophisticated, aspect-oriented framework. The Seam framework far outclasses JSF's basic inversion of control (IOC) container in terms of functionality. Take a look at the declaration of the courseAction in Listing 3. The CourseAction class has been refactored to take advantage of Seam's annotations.


Listing 3. CourseAction defined as a Seam component
                
@Name("courseAction")
public class CourseAction {
    @In("#{courseManager}")
    private GenericManager<Course, Long> courseManager;
}

Deeper integration with Spring

As of version 1.2, Seam includes a custom namespace handler for Spring, which allows you to expose your Spring beans as Seam components. Adding the <seam:component /> tag to your Spring bean definition would allow you to use the @In annotation (from Listing 3) in its basic form, without having to specify the value binding expression explicitly. In this case, Seam would match the name of the property to a Seam component, now including Spring beans exposed as Seam components in the search. See Resources to learn more about what's new with Seam 1.2.

Notice all the XML that was just eliminated! In a nutshell, that is the beauty of Seam's annotations. The @Name annotation on a class instructs Seam's variable resolver to handle requests for the variable whose name matches the value in the annotation. Seam then instantiates an instance of this class, injects any dependencies that have been designated by the @In annotation, and exposes the instance under the variable name. To use Listing 3 as an example, Seam creates an instance of the CourseAction class, injects the courseManager Spring bean into the courseManager property, and returns this instance when a request is made for the variable courseAction. As an added benefit, the configuration of this bean is closer to the code and thus more apparent to a new developer inheriting the code base (or perhaps even to you, six months down the road).

The @In annotation tells Seam to inject the value of the binding expression #{courseManager} into the property over which it is defined. With the JSF-Spring integration installed, this expression resolves to a bean named courseManager, defined in the Spring bean configuration.


Preparing the course list

Now that you're all wired up, you're ready to move on to your first use case. On the opening screen of the Open 18 application, you present the user with a list of all the courses that are currently stored in the database. Thanks to the h:dataTable component tag, the page definition in Listing 4 is fairly straightforward and doesn't warrant any Seam-specific elements:


Listing 4. The initial course listing view
                
<h2>Courses</h2>
<h:panelGroup rendered="#{courseAction.courses.rowCount eq 0}">
  No courses found.
</h:panelGroup>
<h:dataTable id="courses" var="_course" value="#{courseAction.courses}"
  rendered="#{courseAction.courses.rowCount gt 0}">
  <!-- column definitions go here -->
</h:dataTable>

Things get somewhat trickier in the Java code. Listing 5 demonstrates how you might prepare a collection of courses in your request-scoped backing bean using native JSF. I've excluded the injected Spring bean for brevity.


Listing 5. Exposing courses as a DataModel
                
public class CourseAction {
    // ...

    private DataModel coursesModel = new ListDataModel();

    public DataModel getCourses() {
        System.out.println("Retrieving courses...");
        coursesModel.setWrappedData(courseManager.getAll());
        return coursesModel;
    }
    
    public void setCourses(DataModel coursesModel) {
        this.coursesModel = coursesModel;
    }
}

The Java code in Listing 5 looks fairly straightforward, or does it? Let's explore the performance problems that are introduced when JSF uses this backing bean. When presenting a list of entities, you are likely to take one of two approaches. You will either apply conditional logic to render an h:dataTable backed by a collection of at least one item or display an informative message stating that no entities could be found. To make the determination, you will likely consult the #{courseAction.courses}, which in turn invokes the associated getter method on the backing bean.

If you load the page developed so far and then look at the resulting server log output, you will see:

Retrieving courses...
Retrieving courses...
Retrieving courses...

Oh brother! If you let this code go to production, you had better find a safe hiding spot where the DBA can't find you! This type of code execution is punishing to a database. Even worse, the situation continues to degrade upon postback, at which time additional redundant database calls may be made.

Give the database a break!

If you have experience developing applications with JSF, then you know that blindly fetching data in getter methods is strongly discouraged. Why? Because getter methods are invoked many, many times during a typical execution of the JSF life cycle. Workarounds attempt to segregate the process of retrieving data, via delegate objects, from the subsequent process of accessing that data. The goal is to avoid incurring the computative cost of running the query every time the backing bean accessor is consulted. Solutions include initializing the DataModel in the constructor, a static block, or an "init" managed property; caching the result in a private property of the bean; using the HttpSession or session-scoped backing beans; and relying on a second-level O/R caching mechanism.

Listing 6 demonstrates the second option: using a private property of a request-scoped bean to temporarily cache the lookup result. As you'll see, this at least eliminates the redundant fetches during the page-rendering phase, but the cache will still be dropped when the bean goes out of scope on the following page.


Listing 6. Exposing courses as a DataModel, fetching only once
                
public class CourseAction {
    // ...

    private DataModel coursesModel = null;

    public DataModel getCourses() {
        if (coursesModel == null) {
            System.out.println("Retrieving courses...");
            coursesModel = new ListDataModel(courseManager.getAll());
        }
        return coursesModel;
    }
    
    public void setCourses(DataModel coursesModel) {
        this.coursesModel = coursesModel;
    }
}

The approach in Listing 6 is just one of many that attempt to sever data retrieval from data access. Whatever solution you devise, having the data available until it is no longer needed is key to avoiding redundant data fetching. Fortunately, this sort of contextual state management is a Seam specialty!


Contextual state management

Seam uses the factory pattern to initialize non-component objects and collections. Once data is initialized, Seam can place the resulting object into one of the available scopes where it can be read over and over without further participation of the factory method. The context of particular interest is the conversation scope. The conversation scope provides a means of temporarily maintaining state over a well-defined series of requests.

Until recently, very few Web application architectures provided any kind of construct to represent conversations. None of the existing contexts offered the appropriate level of granularity to handle multirequest operations. As you'll see, conversations provide a means to prevent the short-term memory loss that is so common in Web applications and also a root cause of database abuse. Using conversations in combination with the factory component pattern makes it possible to consult the database when appropriate, rather than for the purpose of refetching data that the application has failed to track.

Bijection

Bijection is Seam's extension of the dependency injection concept. In addition to accepting context variables to set component property values, bijection allows component property values to be pushed out to the target context, an operation referred to as outjecting. Bijection differs from dependency injection in that it is dynamic, contextual, and bidirectional. That is to say, bijection is constantly in motion, importing and exporting values whenever a component is invoked. Thus, bijection is better suited for stateful components, such as those used in Web applications.

Using conversations to prevent memory loss

To complete a task, an application often must take the user through a progressive series of screens. This process typically requires several posts to the server, either by the user submitting forms directly or through Ajax requests. In either case, the application should be able to follow along by maintaining the state of server-side objects for the duration of the use case. A conversation is equivalent to a logical unit of work. It allows you to carve out an isolated context for a single user in a single browser window, with definitive beginning and end points. The state of the user's interaction with the application is maintained for the entirety of the conversation.

Seam offers two types of conversations: temporary and long running. A temporary conversation exists over the course of an entire request, including redirects. This feature solves a real sticking point in JSF development, where a redirect will unintentionally drop information stored in the FacesContext, such as FacesMessage instances. Temporary conversations are the standard mode of operation in Seam: you get them for free. That means that any outjected value will live past a redirect without additional work on your part. This feature is the safety net that allows Seam to take great liberty in using redirects whenever appropriate.

In comparison, a long-running conversation holds variables in scope over a well-defined series of requests. You can define conversation boundaries in a configuration file, declare them with annotations, or control them programmatically through the Seam API. A long-running conversation is somewhat like a mini-session, isolated in its own browser tab (or window) and automatically cleaned up when the conversation ends or times out. One of the highlights of the conversation scope over its session counterpart is that the conversation scope separates activities occurring on the same screen of the application loaded in more than one browser tab. Simply put, using conversations removes the danger of concurrency conflicts. (See Resources for a more detailed discussion of how Seam isolates concurrent conversations.)

Seam's conversations are a vast improvement over the ad-hoc approaches to session management that have been improvised out in the field or encouraged by other frameworks. The introduction of the conversation scope also addresses the concern, voiced by many developers, that JSF litters the HttpSession with objects, offering no mechanism for automatic garbage collection (GC). Conversations allow you to create stateful components without having to resort to using the HttpSession. With the conversation scope in the picture, the need for the session scope is a rarity, and you can use it more deliberately.


Creating objects with Seam

Returning to the course listing example, it's time to refactor the code to leverage the factory pattern. The goal is to allow Seam to manage the collection of courses so that it remains available for the duration of the request, including any redirects. If you want Seam to manage the collection, you must hand over the creation process to Seam, using the proper annotations to do so.

Seam uses builders to instantiate and assemble a component. These builders are declared via annotations in the bean classes. In fact, you have already seen one of them: the @Name annotation. The @Name annotation tells Seam to use the default constructor of a class to create a new instance. To build your list of courses, you don't want an instance of a component, but rather a collection of objects. For that purpose, you want to use the @Factory annotation. The @Factory annotation attaches a method to the creation process of an outjected variable, specified in the value of the annotation, when that variable has no value bound to it.

In Listing 7, the factory method findCourses() (on the CourseAction class) is expected to initialize the value of the courses property, which is being outjected to the view as a DataModel. This factory method instantiates the collection of course objects by delegating the work to the service layer.


Listing 7. Exposing courses using the DataModel annotation
                
@Name("courseAction")
public class CourseAction {
    // ...

    @DataModel
    private List<Course> courses;
    
    @Factory("courses")
    public void findCourses() {
        System.out.println("Retrieving courses...");
        courses = courseManager.getAll();
    }
}

Notice the absence of the getCourses() and setCourses() methods! With Seam in the picture, the data is outjected to the view using the name and value of the private property marked with the @DataModel annotation. The need for property accessor methods is thus eliminated. The @DataModel annotation performs two functions in this scenario. First, it outjects, or exposes, the property so that it is accessible to the JSF variable resolver through the value binding expression #{courses}. Second, it provides an alternative to manually wrapping the list of courses in the DataModel type, as you did in Listing 4. Instead, Seam automatically embeds the list of courses in a DataModel instance so that it can be used cleanly with UIData components such as the h:dataTable. As a result, your backing bean, CourseAction, becomes a simple POJO. The JSF-specific details are then left up to the framework.

Listing 8 shows the corresponding refactoring that occurs in the view. The only difference from Listing 5 is the value binding expression. When leveraging Seam's outjection mechanism, you use the abbreviated value binding expression #{courses} rather than consulting the accessor method on the backing bean via #{courseAction.courses}. Outjected variables are placed directly into the variable context, independent of their backing bean.


Listing 8. The course listing view using the outjected DataModel
                
<h2>Courses</h2>
<h:panelGroup rendered="#{courses.rowCount eq 0}">
  No courses found.
</h:panelGroup>
<h:dataTable id="courses" var="_course" value="#{courses}"
  rendered="#{courses.rowCount gt 0}">
  <!-- column definitions goes here -->
</h:dataTable>

When accessing the page this time around, the message appears only once in the console:

 Retrieving courses...

Using the factory builder along with the the temporary conversation scope preserves the data throughout the duration of the request and ensures that the variable courses is only instantiated once, regardless of how many times it is accessed in the view.

The creation scenario in sequence

You may be wondering when the @Factory annotation plays its part. To prevent annotations from becoming too mystical, let's step through the creation scenario just described. You can use the sequence diagram in Figure 1 to follow along:


Figure 1. Seam outjecting a DataModel initialized using a factory method
Seam factory sequence diagram

The view component, such as an h:dataTable, looks to the value binding expression #{courses} to provide a collection of courses. The native JSF variable resolver first looks for a JSF managed bean that matches the name courses. When no match is found, Seam receives the request to resolve the variable. Seam scours its components and finds a @DataModel annotation assigned to a property with an equivalent name, courses, in the CourseAction class. It then creates an instance of the CourseAction class if it does not already exist.

If the value of the courses property is null, Seam looks for a @Factory annotation, again using the name of the property as a key. Upon finding a match with the findCourses() method, Seam invokes it to initialize the variable. Finally, it outjects the value of the property as courses, wrapping it in a DataModel instance. The wrapped value is now available to the JSF variable resolver, and thus the view. Any subsequent request for this context variable returns the collection of courses already prepared.

Now that you have a clean way to retrieve the list of courses and maintain that data in a context variable managed by Seam, it is time to branch off from the course listing. You're ready to begin interacting with the course directory. In the next sections, you will expand the Open 18 application with functionality to show the details of a single course, as well as add, edit, and delete courses.


A slicker way to CRUD

Your first venture into CRUD operations is to show the details of a single course selected from the course listing. The JSF specification actually does some of the data selection legwork for you. When an action, such as an h:commandLink, is triggered from a row in a UIData component, such as h:dataTable, the component's current row is set to the row associated with that event before the event listeners are invoked. The current row can be thought of as a pointer, which in this case is fixed on the row that received the action. In effect, JSF understands that an action on a row is associated with the row's underlying data. JSF helps to put that data into context when the action is being processed.

By itself, JSF allows you to access the data backing the activated row in one of two ways. One way is to retrieve it using the DataModel#getRowData() method. The other way is to read it from the value binding that corresponds to the temporary loop variable, which is defined in the component tag's var attribute. In the second case, the temporary loop variable, _course, is once again exposed to the variable resolver during event processing. The downside of both forms of access is the required interaction with the JSF APIs.

If you choose the DataModel API as your entry point to the row data, then you must expose the DataModel wrapper object as a property of the backing bean, as shown in Listing 4. On the other hand, if you choose to access row data through the value binding, then you must consult the JSF variable resolver. The latter approach also ties you to the name of the temporary loop variable used in the view, _course.

Now consider Seam's more abstracted approach to obtaining the selected data. Seam lets you pair the @DataModel annotation defined on a Seam component with its complement, the @DataModelSelection annotation. During postback, Seam automatically detects the pairing. It then injects the data for the current row of a UIData component into the property that has been assigned the @DataModelSelection annotation. This approach untangles your backing beans from the JSF API and thus returns them to POJO status.

Component IDs

It is always a good idea to specify a value for the id attribute on JSF component tags, especially those that are naming containers. If you do not assign an ID to a component, the JSF implementation generates a rather cryptic one. Having meaningful IDs helps when debugging or writing JavaScript that accesses the DOM.

A long-running conversation

To ensure that the same list of courses is still available on postback and that you can render the next response without having to refetch the list from the database, you need to transition the current temporary conversation into a long-running one.

One way to convince Seam to promote a conversation from temporary to long-running is to place a method hosting the @Begin annotation in its execution path. You also need to place the component itself in the conversation scope. You do this by adding the @Scope(ScopeType.CONVERSATION) annotation at the top of the class definition for CourseAction. Using a long-running conversation allows variables to remain in scope until the end of the conversation, instead of merely for a single request. This stability across subsequent requests is especially important for UIData components. (See the discussion on stateful components in the first article in this series to learn about the problems that data instability can cause with regard to queued events on a UIData component.)

You want to allow the user to select a single course from the course directory. To enable this function, you wrap the name of each course in a h:commandLink that designates the method binding #{courseAction.selectCourse} as the action, as shown in Listing 9. When the user clicks on one of those links, it triggers the invocation of the selectCourse() method on your backing bean. Thanks to Seam taking control of injection, the course data associated with that row is automatically assigned to the property carrying the @DataModelSelection annotation. Hence, that property can be used without having to perform any lookups, as shown further in Listing 10.


Listing 9. Adding a command link to select a course
                
<h2>Courses</h2>
<h:panelGroup rendered="#{courses.rowCount eq 0}">
  No courses found.
</h:panelGroup>
<h:dataTable id="courses" var="_course" value="#{courses}"
  rendered="#{courses.rowCount gt 0}">
  <h:column>
    <f:facet name="header">Course Name</f:facet>
    <h:commandLink id="select"
        action="#{courseAction.selectCourse}" value="#{_course.name}" />
  </h:column>
  <!-- additional properties -->
</h:dataTable>

The additions to the backing bean to accommodate the data selection are primarily annotations; the class must be serializable when it is placed into the conversation scope.


Listing 10. DataModelSelection annotation to capture the selected course
                
@Name("courseAction")
@Scope(ScopeType.CONVERSATION)
public class CourseAction implements Serializable {
    // ...

    @DataModel
    private List<Course> courses;
  
    @DataModelSelection
    private Course selectedCourse;
    
    @Begin(join=true)
    @Factory("courses")
    public void findCourses() {
        System.out.println("Retrieving courses...");
        courses = courseManager.getAll();
    }
  
    public String selectCourse() {
        System.out.println("Selected course: " + selectedCourse.getName());
        System.out.println("Redirecting to /courses.jspx");
        return "/courses.jspx";
    }
}

The persistence context

One of the contexts that Seam can manage is the persistence context. The persistence context is the identity scope and in-memory cache for all objects loaded from the database with either Hibernate or JPA. In contrast to the stateless architecture advocated by Spring, Seam's creators recommend the use of conversation-scoped components that can extend the persistence context across multiple requests. The problem introduced by the stateless model is that when the persistence context is closed, all loaded objects enter a "detached" state and the identity of those objects is no longer guaranteed. The result is that both the database and the developer are taxed with having to reconcile the equality of objects across persistence-context sessions. The managed persistence context is not utilized in this article. See Resources to learn more about it.

The fine points of conversation

As you can see in Listing 10, all of the variable scoping is handled by Seam. When the factory method executes to initialize the collection of courses, Seam encounters the @Begin annotation and thus promotes the temporary conversation to a long-running one. The variable outjected by the @DataModel annotation assumes the scope of the owning component. Therefore, the collection of courses remains available for the duration of the conversation. When a method marked with an @End annotation is encountered, the conversation ends.

When you click on the name of a course in one of the rows, Seam populates the @DataModelSelection annotated property with the value of the course data backing that row. The action method, selectCourse(), is then triggered, causing the name of the selected course to be printed to the console. Finally, the list of courses is redisplayed. Following along in the console, you see:

Retrieving courses...
Selected course: Sample Course
Redirecting to /courses.jspx

Seam relieves you of having to define a navigation rule in faces-config.xml that maps to the return value of every action. Instead, Seam checks whether the return value of the action is a valid view template (technically a view id) and performs a dynamic navigation if it is. This feature keeps simple applications simple and leaves the option open for using declarative navigation for more advanced use cases. Keep in mind, though, that Seam issues a redirect when performing the navigation in this case.

If you needed to end the conversation declaratively, you would annotate the action method selectCourse() with @End(beforeRedirect=true), in which case the conversation would terminate after each invocation of the method. The beforeRedirect attribute ensures that the variables in the conversation context are cleared before the next page renders, short-circuiting the work of the temporary conversation, which would normally propagate the values over the redirect. In this scenario, the process of preparing the data starts over each time a course is selected. After the same sequence of events described above, the console would now read:

Retrieving courses...
Selected course: Sample Course
Redirecting to /courses.jspx
Retrieving courses...


Outjecting course details

You're not quite done with the use case of showing a course in detail. The @DataModelSelection annotation takes care of injecting the current row data into an instance variable of the backing bean, but it does not propagate the data beyond the execution of the action method, making it available to the ensuing view. To do that, you need to outject the value of the selection.

You've already seen one form of outjection in which the @DataModel annotation exposes a collection of objects to the rendering view. The @DataModel annotation's complement for single-object instances is the @Out annotation. The @Out annotation simply takes a property and exposes its value to the variable resolver under the property's own name. By default, the @Out annotation requires a non-null value every time it is activated. Because a course selection will not always be present, such as when the list of courses is first displayed, you need to set the required flag on the annotation to false, thereby indicating that the outjection is conditional.

Naming loop variables

When choosing a temporary loop variable name for an h:dataTable, you have to be careful not to conflict with the names of other outjected variables. In all of the examples, I've used an underscore prefix when specifying a loop variable name. Not only does the prefix prevent conflicts with the outjected course, but it also helps to clarify that the variable has limited scope. Context variables are stored in a shared map for a given scope, so be careful when selecting names for them!

By default, the @Out annotation reflects on the name of the property to determine the name of the context variable. You have the option to use a different name for the outjected variable if you feel that it is more appropriate. Because the course data is being outjected into the conversation scope and may be used over a number of subsequent requests, the "selected" aspect of the name begins to lose its meaning. In this case, the name of entity itself is preferred. Thus, the recommended annotation for the selectedCourse property is @Out(value="course", required=false).

You can either show the course details on a new page or on the same page just below the table. For the purpose of the demonstration, you show the details on the same page, limiting the number of views to construct. No additional work or fancy tricks would be required to access the outjected variables on a second page.

The revised backing bean

So little has changed from the last version of the backing bean that Listing 11 only highlights the differences. The selectedCourse property now has two annotations. The selectCourse() method has also been spruced up a bit. It now refetches the course object before continuing to the view. In a stateless design, you must ensure that objects are fully populated by the data layer and that any lazy associations that are needed to show its details are properly initialized.


Listing 11. Outjecting the selected course to the view
                
    // ...

    @DataModelSelection
    @Out(value="course", required=false)
    private Course selectedCourse;
    
    public String selectCourse() {
        System.out.println("Selected course: " + selectedCourse.getName());
        // refetch the course, loading all lazy associations
        selectedCourse = courseManager.get(selectedCourse.getId());
        System.out.println("Redirecting to /courses.jspx");
        return "/courses.jspx";
    }

    // ...

Most of the interesting changes occur in the view, but even those changes aren't that fancy. Listing 12 shows the detail pane that is rendered below the h:dataTable when a course has been selected:


Listing 12. Conditionally rendering the course detail for the selected course
                
<h:panelGroup rendered="#{course.id gt 0}">
  <h3>Course Detail</h3>
  <table class="detail">
    <tr>
      <th>Course Name</th>
      <td>#{course.name}</td>
    </tr>
    <!-- additional properties -->
</h:panelGroup>


Reinjecting the course

The trickiest use cases for the Open 18 application are the create and update operations. But with Seam helping out, they are still far from difficult. To fulfill these two requirements, you need to use one additional annotation: @In. After outjecting the course to the view that renders the course editor form, you need to capture the updated object on postback. Just as @Out is used to push variables out to the view, @In can be used to recapture them on a postback.

While the user works on the course information loaded in the form, the course entity waits patiently in the conversation scope. Because the application uses a stateless service interface, the course instance is at this point considered "detatched" from the persistence context. When the form is submitted, JSF's Update Model Values phase is eventually reached. At that time, properties of the course object associated with fields in the form receive the user's updates. When the action method is invoked, it is necessary to reattach the updated object to the persistence context, making the changes permanent. You do this by passing the object back through the service layer using the save() method.

But wait -- where are your validations? You certainly don't want to corrupt your database with invalid data! On the other hand, you probably don't want to clutter your view template with validation tags. You may even agree with the argument that validation code doesn't belong in the view layer. Luckily for you, Seam takes care of JSF's validation dirty work!

Validation with Seam and Hibernate

If you wrap your entire form in an s:validateAll component tag, Seam lets you enforce validations defined on your data model during JSF's Process Validations phase. This approach to validation is far more attractive than scattering JSF validator tags all over your views or maintaining a configuration file full of the validation definitions for a third-party validation framework. Instead, you can use the Hibernate Validator annotations to assign validation criteria to the properties of your entity class, as shown in Listing 13. Hibernate then double-checks the validations when it is persisting the object, giving you twice the protection. This double-fisted approach means that careless bugs in the view don't have a fighting chance of jeopardizing the quality of your data. (See Resources to learn more about the Hibernate Validator.)


Listing 13. The course entity, annotated with Hibernate validations
                
@Entity
@Table(name = "course")
public class Course implements Serializable {

    private long id;
    private String name;
    private CourseType type = CourseType.PUBLIC;
    private Address address;
    private String uri;
    private String phoneNumber;
    private String description;

    public Course() {}

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    @NotNull
    public long getId() {
        return this.id;
    }

    public void setId(long id) {
        this.id = id;
    }

    @Column(name = "name")
    @NotNull
    @Length(min = 1, max = 50)
    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Column(name = "type")
    @Enumerated(EnumType.STRING)
    @NotNull
    public CourseType getType() {
        return type;
    }

    public void setType(CourseType type) {
        this.type = type;
    }

    @Embedded
    public Address getAddress() {
        return address;
    }
    
    public void setAddress(Address address) {
        this.address = address;
    }
    
    @Column(name = "uri")
    @Length(max = 255)
    @Pattern(regex = "^https?://.+$", message = "validator.custom.url")
    public String getUri() {
        return this.uri;
    }

    public void setUri(String uri) {
        this.uri = uri;
    }

    @Column(name = "phone")
    @Length(min = 10, max = 10)
    @Pattern(regex = "^\\d*$", message = "validator.custom.digits")
    public String getPhoneNumber() {
        return this.phoneNumber;
    }

    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }

    @Column(name = "description")
    public String getDescription() {
        return this.description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    // equals and hashCode not shown
}


Just a few more steps ...

The course object is only injected on a postback, which is triggered by the user submitting the course editor form, not by every request that involves the courseAction component. To allow for conditional use of the @In annotation, you must define it with its required flag set to false. Doing so ensures Seam will not complain when it cannot find a course object to inject.

When the course editor form is submitted, the course object that was previously outjected becomes available for injection. To ensure that this instance is reinjected back into the same property, you need to give the @In annotation a name equivalent to the name used for the @Out annotation. As a result of all these additions, the selectedCourse property now has three annotations. (It sure is getting busy up there!)

You also need to give the backing bean three additional action methods to handle the new CRUD operations being addressed. The new annotation, as well as the addCourse(), editCourse(), and saveCourse() action methods, are shown in Listing 14:


Listing 14. Additional actions to create, edit, and save a course
                
    // ...

    @DataModelSelection
    @In(value="course", required=false)
    @Out(value="course", required=false)
    private Course selectedCourse;
    
    public String addCourse() {
        selectedCourse = new Course();
        selectedCourse.setAddress(new Address());
        return "/courseEditor.jspx";
    }
    
    public String editCourse() {
        selectedCourse = courseManager.get(selectedCourse.getId());
        return "/courseEditor.jspx";
    }
    
    public String saveCourse() {
        // remove course from cached collection
        // optionally, the collection could be nullified, forcing a refetch
        if (selectedCourse.getId() > 0) {
            courses.remove(selectedCourse);
        }
        courseManager.save(selectedCourse);
        // add course to the cached collection
        // optionally, the collection could be nullified, forcing a refetch
        courses.add(selectedCourse);
        FacesMessages.instance().add("#{course.name} has been saved.");
        return "/courses.jspx";
    }

    // ...

The course editor page takes care of both creates and updates. What makes Seam so cool is its ability to direct traffic behind the scenes, in this case by holding the selected course in context while you move from page to page. Nowhere are you using the HttpSession, request parameters, or any other trickery to store the selected course. You simply outject what you want to expose and inject what you expect to receive.


The editor template

Look at the form component from the editor page, which is shown in Listing 15. This page uses a couple of Seam component tags that make life easier when developing the view:

  • s:decorate combines with the afterInvalidField facet to insert an s:message component after every input component, which relieves you of having to repeat markup throughout a page.
  • s:validateAll instructs Seam to incorporate Hibernate Validator annotations into the JSF validation process to validate every field in the form on postback.

You won't find any native JSF validators on the course editor view page because Seam makes them completely unnecessary when leveraging the Hibernate Validator. The page also shows off the enum convertor component that ships with Seam, in case you happen to be using Java 5 enum types in your application.


Listing 15. The course editor view
                
<h2><h:outputText value="#{course.id gt 0 ? 'Edit' : 'Create'} Course" /></h2>
<h:form id="course">
  <s:validateAll>
    <f:facet name="afterInvalidField">
      <s:span styleClass="error">
        <s:message showDetail="true" showSummary="false"/>
      </s:span>
    </f:facet>
    <ul>
      <li>
        <h:outputLabel for="name" value="Course Name"/>
        <s:decorate>
          <h:inputText id="name" value="#{course.name}" required="true"/>
        </s:decorate>
      </li>
      <li>
        <h:outputLabel for="type" value="Type"/>
        <s:decorate>
           <h:selectOneMenu id="type" value="#{course.type}">
             <s:convertEnum />
             <s:enumItem enumValue="PUBLIC" label="Public" />
             <s:enumItem enumValue="PRIVATE" label="Private" />
             <s:enumItem enumValue="SEMI_PRIVATE" label="Semi-Private" />
             <s:enumItem enumValue="RESORT" label="Resort" />
             <s:enumItem enumValue="MILITARY" label="Military" />
           </h:selectOneMenu>
        </s:decorate>
      </li>
      <li>
        <h:outputLabel for="uri" value="Website" />
        <s:decorate>
          <h:inputText id="uri" value="#{course.uri}"/>
        </s:decorate>
      </li>
      <li>
        <h:outputLabel for="phone" value="Phone Number" />
        <s:decorate>
          <h:inputText id="phone" value="#{course.phoneNumber}"/>
        </s:decorate>
      </li>
      <li>
        <h:outputLabel for="city" value="City" />
        <s:decorate>
          <h:inputText id="city" value="#{course.address.city}"/>
        </s:decorate>
      </li>
      <li>
        <h:outputLabel for="state" value="State" />
        <s:decorate>
          <h:selectOneMenu id="state" value="#{course.address.state}" required="true">
            <s:selectItems var="state" value="#{states}" label="#{state}" />
          </h:selectOneMenu>
        </s:decorate>
      </li>
      <li>
        <h:outputLabel for="zip" value="ZIP Code" />
        <s:decorate>
          <h:inputText id="zip" value="#{course.address.city}"/>
        </s:decorate>
      </li>
      <li>
        <h:outputLabel for="description" value="Description" />
        <s:decorate>
          <h:inputTextarea id="description" value="#{course.description}"/>
        </s:decorate>
      </li>
    <ul>
  </s:validateAll>
  <p class="commands">
    <h:commandButton id="save" action="#{courseAction.saveCourse}" value="Save"/>
    <s:button id="cancel" view="/courses.jspx" value="Cancel"/>
  </p>
</h:form>


Adding delete functionality

Looking back on the code snippets, you can see that most of the focus so far has been on eliminating code, choosing instead to describe the functionality through annotations and letting the framework take care of the details. This simplicity lets you concentrate on the tougher problems and add all those fancy Ajaxian effects everyone loves. What you may not realize is that with just a little bit more work, you can have all the letters in CRUD handled -- you're practically on the home stretch!

Incorporating delete functionality into the application is a simple matter. All you need to do is add another h:commandLink to each row that activates the delete method on your backing bean, deleteCourse(). With the work already done to expose the selected course, the course object tied to the course property is simply passed on to the CourseManager for termination, as shown in Listing 16:


Listing 16. Add a command link to deleteCourse
                
<h:dataTable id="courses" var="_course" value="#{courses}"
  rendered="#{courses.rowCount gt 0}">
  <h:column>
    <f:facet name="header">Course Name</f:facet>
    <h:commandLink id="select"
        action="#{courseAction.selectCourse}" value="#{_course.name}" />
  </h:column>
  <h:column>
    <f:facet name="header">Actions</f:facet>
    <h:commandLink id="delete" action="#{courseAction.deleteCourse}" value="Delete" />
  </h:column>
  <!-- additional properties -->
</h:dataTable>

In the deleteCourse() method, shown in Listing 17, you leverage Seam's FacesMessages component to alert the user to what is going on. The messages are displayed in the typical way, using the h:messages JSF component in the view. But notice how much easier it is to create the message in the first place! You can throw out that JSF utility class you've been hanging onto with a death grip; Seam is steadily eliminating the ghosts of JSF's past.


Listing 17. Add an action method to deleteCourse
                
    // ...

    public String deleteCourse() {
        courseManager.remove(selectedCourse.getId());
        courses.remove(selectedCourse);
        FacesMessages.instance().add(selectedCourse.getName() + " has been removed.");
        // clear selection so that it won't be shown in the detail pane
        selectedCourse = null;
        return "/courses.jspx";
    }

    // ...


The complete course listing

With all the CRUD operations handled, you are so close to being done! The only step remaining is to the put the entire course listing view together, which is done in Listing 18:


Listing 18. The complete course listing view
                
<h2>Courses</h2>

<h:messages id="messages" globalOnly="true" />

<h:panelGroup rendered="#{courses.rowCount eq 0}">
  No courses found.
</h:panelGroup>

<h:dataTable id="courses" var="_course" value="#{courses}"
  rendered="#{courses.rowCount gt 0}">
  <h:column>
    <f:facet name="header">Course Name</f:facet>
    <h:commandLink id="select"
        action="#{courseAction.selectCourse}" value="#{_course.name}" />
  </h:column>
  <h:column>
    <f:facet name="header">Location</f:facet>
    <h:outputText value="#{course.address.city}, #{course.address.state}" />
  </h:column>
  <h:column>
    <f:facet name="header">Phone Number</f:facet>
    <h:outputText value="#{course.phoneNumber} />
  </h:column>
  <h:column>
    <f:facet name="header">Actions</f:facet>
    <h:panelGroup>
      <h:commandLink id="edit" action="#{courseAction.editCourse}" value="Edit" />
      <h:commandLink id="delete" action="#{courseAction.deleteCourse}" value="Delete" />
    </h:panelGroup>
  </h:column>
</h:dataTable>

<h:commandButton id="add" action="#{courseAction.addCourse}" value="Add Course" />

<h:panelGroup rendered="#{course.id gt 0}">
  <h3>Course Detail</h3>
  <table class="detail">
    <col width="20%" />
    <col width="80%" />
    <tr>
      <th>Course Name</th>
      <td>#{course.name} <span class="notation">(#{course.type})</span></td>
    </tr>
    <tr>
      <th>Website</th>
      <td><h:outputLink value="#{course.uri}"
        rendered="#{not empty course.uri}">#{course.uri}</h:outputLink></td>
    </tr>
    <tr>
      <th>Phone</th>
      <td>#{course.phoneNumber}</td>
    </tr>
    <tr>
      <th>State</th>
      <td>#{course.address.state}</td>
    </tr>
    <tr>
      <th>City</th>
      <td>#{course.address.city}</td>
    </tr>
    <tr>
      <th>ZIP Code</th>
      <td>#{course.address.postalCode}</td>
    </tr>
  </table>
  <h:panelGroup rendered="#{not empty course.description}">
  <p><q>...#{course.description}</q></p>
  </h:panelGroup>
</h:panelGroup>

Take a bow! You have completed your first Seam-based CRUD application.


In conclusion

In this second article in the Seamless JSF series, you've seen for yourself how Seam's Java 5 annotations can simplify your code, how the conversation scope automatically manages state over a series of requests, and how to use Seam and the Hibernate Validator together to enforce data model validations on input data.

It's actually possible to automate most CRUD work using seam-gen (see Resources), Seam's Ruby-on-Rails-style application generator. What I hope you've learned from the exercises in this article, however, is that Seam is not just another Web framework. Adopting Seam does not force you to abandon your JSF experience. Instead, Seam is a very powerful extension to JSF that naturally enhances the JSF life cycle. Together, Seam and JSF can fluently integrate with either a stateless service layer or the EJB3 model.

Now that you have seen some of the ways that Seam eases JSF development, you may be wondering how well it supports the more advanced Web 2.0 technologies discussed in Part 1. In the final installment in this series, I'll show you how to use Ajax remoting to further develop the Open 18 application by creating a mashup between the course directory and Google Maps. In the process, you will learn how Seam's Java 5 annotations and the bundled JavaScript library enable direct communication between the browser and server-side components.

See you then, and in the meantime, happy golfing!



Download

DescriptionNameSizeDownload method
Open 18 sample application - Phase 11j-seam2.zip248KB HTTP

Information about download methods

Note

  1. The sample application is organized as a Maven 2 project. All dependencies will be fetched on demand when the build is executed. The application uses several libraries from the Appfuse project to implement the service and DAO layers.

Resources

Learn

Get products and technologies

  • JBoss Seam: Download it to get the complete distribution, including the bundled example applications.

  • Hibernate: Get started with the Hibernate Validator.

  • Apache Geronimo: Download the Java EE 5 version and take advantage of Seam's EJB3 integration features.

  • Maven 2: A software project management and comprehension tool used in the source code sample. Maven automatically downloads dependencies during the build process.

  • Facelets: The preferred JSF view handler for Seam applications.

  • Appfuse: Practically hands you a remarkably comprehensive Maven 2-based project skeleton on which to build your application.

Discuss

About the author

Dan Allen

Dan Allen is currently a senior Java engineer at CodeRyte, Inc. He is also a passionate open source advocate and gets a bit manic whenever he catches sight of a penguin. After graduating from Cornell University with a degree in Materials Science and Engineering, Dan became captivated by the world of Linux and open source software. He has been slaving over Web applications ever since, with the last several years focused exclusively on Java-related technologies, including Spring, Hibernate, Maven 2, and the abundant JSF stack. You can keep up with Dan's development experiences by subscribing to his blog at http://www.mojavelinux.com.

Comments (Undergoing maintenance)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology, Web development
ArticleID=216538
ArticleTitle=Seamless JSF, Part 2: Conversations with Seam
publish-date=05012007
author1-email=dan.allen@mojavelinux.com
author1-email-cc=

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Rate a product. Write a review.

Special offers