 | Level: Intermediate Dan Allen (dan.allen@mojavelinux.com), Senior Java engineer, CodeRyte, Inc.
01 May 2007 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.
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:
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
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 | Description | Name | Size | Download method |
|---|
| Open 18 sample application - Phase 11 | j-seam2.zip | 248KB | HTTP |
|---|
Note - 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
- "Seamless JSF, Part 1: An application framework tailor-made for JSF" (Dan Allen, developerWorks,
April 2007): An overview of Seam's enhancements to the JSF life cycle, including those utilized in Part 2.
- "Build Apache Geronimo applications using JavaServer Faces: Integrating your JSF application with Spring" (Chris Herborth, developerWorks, September 2006): A tutorial introduction to integrating Spring and JSF. Final installment of a five-part series on developing JSF applications.
- "Hibernate
can meet your validation needs" (Ted Bergeron, developerWorks, September
2006): Learn how to use Hibernate's annotations-driven validator to build and maintain validation logic.
- "A Conversation with Seam" (Brian Leonard's blog, November 2006): Demonstrates how Seam isolates concurrent conversations.
- "Getting started with Seam, using seam-gen" (The Seam Tutorial, JBoss.org): Find out how to use seam-gen to create a skeleton CRUD application. (But where's the fun in that?)
-
InfoQ: Find more interviews and articles about Seam.
-
JBoss Seam: Simplicity and Power Beyond Java EE
(Michael Juntao Yuan and Thomas Heute; Prentice Hall,
April 2007): Just out, this is the first book-length introduction to JSF development with Seam.
-
The Seam Reference Documentation: Definitely one of Seam's strong suits.
-
developerWorks Java technology zone: Hundreds of articles about every aspect of Java programming.
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 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. |
Rate this page
|  |