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.
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.
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>
|
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>
|
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;
}
|
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.
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.
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!
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.
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.
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
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.
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.
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 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... |
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.
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.
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>
|
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
}
|
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.
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:decoratecombines with theafterInvalidFieldfacet to insert ans:messagecomponent after every input component, which relieves you of having to repeat markup throughout a page. -
s:validateAllinstructs 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>
|
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";
}
// ... |
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 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!
| Description | Name | Size | Download method |
|---|---|---|---|
| Open 18 sample application - Phase 11 | j-seam2.zip | 248KB | HTTP |
Information about download methods
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.
Learn
- "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.
-
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
-
The Seam blog: Learn
what other users think about Seam and give your own feedback in the
Seam community forum.

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)





