Seam in Action
Table of Contents
Part 1 - Teeing off with Seam
1 Seam unifies Java EE
2 Putting seam-gen to work
Part 2 – Seam fundamentals
3 The Seam life cycle
4 Components and contexts
5 The Seam component descriptor
6 Absolute inversion of control
Part 3 – How Seam manages state
7 The conversation: Seam's unit of work
8 Understanding Java persistence
9 Seam-managed transactions and persistence
10 Rapid Seam development
Part 4 – Sinking the business requirements
11 Securing Seam applications
12 Ajax and JavaScript remoting
13 File uploads, rich rendering, and email support
14 Managing the business process
15 Spring integration
Appendix A. Seam starter set
Appendix B. Seam annotations quick reference
Appendix C. JSF component libraries
Licensed to Jaroslaw Gilewski <[email protected]>
MEAP Edition
Manning Early Access Program
Copyright 2008 Manning Publications
For more information on this and other Manning titles go to
www.manning.com
Licensed to Jaroslaw Gilewski <[email protected]>
1
Seam unifies Java EE
Is JSF worth a second look? Is EJB really fixed? Is it worth sticking with Java rather than jumping ship for
Ruby on Rails?
With the release of JBoss Seam 2.0, you can now confidently answer yes to all of these questions. Seam is
a progressive application framework for Java EE that makes writing web-based applications easier by finally
delivering on the promise of a unified component architecture. Seam builds on the innovative changes in Java
EE brought about by the Enterprise JavaBeans (EJB) 3 specification. These changes include favoring
annotations over container interfaces and relying on configuration by exception rather than verbose and
laborious XML descriptors. Seam tares down Java EE's remaining heavyweight legacy by spreading EJB 3's
pivotal changes across the platform, leveraging more annotations, more configuration by exception and
extending the platform as designed, weaving functionality into the JavaServer Faces (JSF) life cycle, and using
the unified EL to allow these technologies to communicate. With Seam, the pain typically associated with
using Java EE has vanished and JSF, in particular, appears completely revamped and worthy of attention.
In this chapter, you discover why Seam is the most exciting technology that has landed in Java's turf since
its inception and the reasons why you should make Seam your framework of choice. I demonstrate how Seam
solves your current problems with the Java EE platform by blending innovative concepts with existing
standards. In a world inundated with frameworks, JBoss Seam is the unframework. It does not force a new
programming model on you. Instead, Seam pulls together the standard Java EE APIs, makes them more
accessible, functional, and attractive, and then finishes them off with modern upgrades such as page flows,
JavaScript remoting, PDF rendering, email composition, charting, file upload management, business processes,
and Groovy integration. Like a classic car, underneath the hood Seam has all the muscle of Java EE, but on the
surface it appears stunning and elegant.
Putting Seam's strengths aside, the fact remains that there are many qualified frameworks that you have to
choose from. In the next section, I provide you with advice that can hopefully put an end to your search and
move you towards developing your application. Despite the fact that no one can tell you what framework is
right for you, you are probably going to ask anyway, right? Don't worry, I came prepared.
1.1 Which framework should I use?
In a world full of framework options, how do you choose one? There are so many frameworks available for the
Java platform, some proven, some promising, that the decision is downright agonizing! Does figure 1.1 speak
to you?
Licensed to Jaroslaw Gilewski <[email protected]>
Figure 1.1 The great framework decision.
The choice is so bewildering that the framework inquiry is now the dominant greeting exchanged between
developers at conferences. While the question "What do you do?" may have traditionally served in the role of
sizing up a person's abilities, these days you are judged based on the merit of what framework you use for
software development (or the advice that you can give pertaining to that choice). Just when you've made a
decision, a new framework arrives on the scene promising to bury its predecessors.
These choices can be harmful, especially to productivity. Barry Schwartz argues in The Paradox of Choice
that having a bewildering array of options floods our already exhausted brains. The result is that your ability to
write a quality application stalls. You keep believing that the best framework is the one you haven't tried yet.
As a consequence, you spend more time researching frameworks than you do designing functional
applications. The search consumes you. You develop a false sense of how busy you are. While you may appear
busy, the fact is, you aren't accomplishing much.
If any of these choices were truly satisfying, than you probably would not be reading this book. You
would already have a set of tools that you know, beyond all doubt, allows you to be highly productive. But,
you don't, do you? You are still searching for a framework that is new, yet familiar. Lightweight, yet powerful.
You are in need of a platform that integrates the vast landscape of Java technologies into a unified stack. Seam
might be just the framework you are looking for.
1.2 Choosing Seam
You might be tempted to think that Seam is just another web framework, competing in an already flooded
market. In truth, to tag Seam as a web framework is quite unfitting. Seam is far more broad than a traditional
web framework, such as Struts, and is better described as an application stack.
1.2.1 A complete application stack
Let's consider the distinction between an application stack and a web framework. Web frameworks are
analogous to the guests that show up just in time for dinner and then leave immediately after eating. They
entertain and soak up the limelight, but they are mostly unhelpful. They go out the same way they arrived, with
lots of commotion. An application stack, on the other hand, is like the person who helps to plan the dinner
party, shops for the groceries, cooks, sets up, serves, makes the coffee, and then ultimately cleans up when it is
all over. They are steadfast and resourceful. Sadly, their work goes mostly unrecognized.
In a world where everyone wants to be a rock star (i.e. web framework), Seam is your practical sidekick,
your sous chef. The Seam stack includes the framework, the libraries, the build script and project generator, the
Licensed to Jaroslaw Gilewski <[email protected]>
IDE integration, a base test class, an embedded JBoss container, and integrations with other technologies.
Seam is a certainly hard worker. Figure 1.2 gives a sample cross-section of the technologies that Seam is
capable of pulling together in a typical application.
Figure 1.2 A cross-section of the technologies incorporated in the Seam stack.
While this stack gives you an idea of the technologies used in a Seam application, it does not give you a
clear picture of Seam's purpose and why it exists. To understand why Seam was created, you have to recognize
the challenge that it faced. Although the Java EE 5 release took a gigantic step towards establishing an agile
platform for enterprise Java development, it left behind a rather significant gap between the component-based
web tier managed by JSF and the component-based business-tier managed by EJB 3. A bridge was needed.
1.2.2 Why Seam was created
The Java EE 5 specification incorporates two key component architectures (specifications for creating reusable
objects) for creating web-based business applications: JavaServer Faces (JSF) 1.2 and Enterprise JavaBeans
(EJB) 3. JSF is the standard presentation framework for the web-tier that provides both a user-interface
component model and a server-side event model. EJB 3 is the standard programming model for creating secure
and scalable business components that access transactional resources. EJB 3 also encompasses the Java
Persistence API (JPA), which defines a standard persistence model for translating data between a relational
database and Java entity classes.
Aside from their residence in the Java EE 5 specification, the two architectures just mentioned share little
resemblance, their backs facing each other like two sides of a coin. This communication barrier casts a shadow
on the tremendous potential each of these technologies has. While it's true that developers were able to get
these two Java EE tiers to work together, it required a lot of "glue" code. Seam absorbs that responsibility and
fits JSF and EJB 3 together, thus ironing out one of the roughest spots in the Java EE 5 specification and
completing the missing link in the evolution of the Java EE platform. As such, Seam has positioned itself as
the prototype for future Java EE specifications. Three such specifications are JSR 299 (Web Beans), JSR 314
(JavaServer Faces 2.0), and JSR 303 (Bean Validation). Seam isn't just about JSF and EJB 3, though. You can
Licensed to Jaroslaw Gilewski <[email protected]>
swap in alternative view technologies such as Wicket, Tapestry, GWT and Flex in place of JSF. Seam also
boasts integration with the Spring container.
With that said, becoming the future of Java EE and an integration point for many open source technologies
is not why Seam was created. That just happens to be a celebrated outcome. As with most software projects,
Seam came about because of a single developer's itch.
As the story goes, Gavin King was fed up (and that doesn't take much) with the way developers were
incorrectly using Hibernate, trapping it in a stateless design as popularized by the Spring Framework.
Recognizing that the missing integration between JSF and EJB 3 would only lead to further abuse of Hibernate
as a JPA provider, he decided to step in and build a foundation that would allow the persistence context
(Hibernate Session or JPA EntityManager) to transcend layers and permit stateful session beans to
respond directly to JSF UI components. The name "Seam" was chosen because this foundation brings JSF and
EJB 3 together and teaches them to play nicely together in the same sandbox. In addition, Seam encourages the
adoption of a stateful, yet efficient, architecture. As a result, applications built on Seam have effortless
continuity from one user interaction (or event) to the next, a feature which is labeled a web conversation. The
keen focus on variable scoping is what makes Seam contextual.
In the process of solving the mismatch between JSF and EJB 3, the Seam architects broadened the solution
and created a universal component model, bringing the implicit services provided by the EJB 3 programming
model, such as transactions, interceptors and security, to non-EJB components such as JavaBeans and Spring
beans. For non-EJB components, Seam takes on the role of processing the Java EE annotations—or synonyms
of these annotations from the Seam API—and weaving in the enterprise services. What that means is that you
do not have to rely on an EJB 3 container to leverage the benefits the EJB 3 provides. In fact, you don't even
need the JBoss Application Server to use Seam, despite what you may have heard.
1.2.3 Debunking the "vendor lock-in" myth
I don't want to be shy about addressing the myth that Seam is JBoss-focused technology or that by using Seam,
you get locked into JBoss. Seam is no more a JBoss technology than Struts is an Apache technology or Spring
is a SpringSource technology. An examination of the most successful complex projects in enterprise Java, such
as Spring, Hibernate, Eclipse, JBoss AS, and even the Java EE platform, reveals that these projects are
supported by organizations with paid developers. The projects in the JBoss Labs, such as Seam, are open
source and can be whatever you, the community 1, drives them to be. While the projects may be operated under
JBoss/RedHat's roof, the source code is yours to copy, share and modify. Specifically, JBoss Seam is licensed
under the Lesser GNU Public License (LGPL), which is considered one of the more flexible options.
If you use Seam, you aren't stuck having to deploy to the JBoss Application Server either. A lot of effort
has gone into ensuring that Seam is compatible and will run on all major application servers, including BEA
Weblogic, IBM Websphere, Oracle OC4J, Apache Tomcat, and Glassfish. And it's more than about deploying
to major application servers. The improvements that Seam introduces are being contributed back into the
platform as a standard using the Java Community Process (JCP) as a vehicle and summarized in the Java
Specification Request (JSR) 299: Web Beans. The purpose of this JSR is to unify the JSF managed bean
component model with the EJB component model, resulting in a significantly simplified programming model
for web-based applications.
Seam is about standards blended with innovation. By signing your name on the dotted line and choosing
Seam as your framework, you are not locking yourself in to a JBoss technology. Once the Web Beans JSR is
accepted, as well as any others originating from the Seam project, any vendor can provide their own
implementation. It is not a closed platform.
1
http://www.seamframework.org is the main communite site for Seam
Licensed to Jaroslaw Gilewski <[email protected]>
With an understanding of why Seam exists, and faith that you are not getting locked into JBoss by
choosing this technology, you now need to consider if Seam is the right framework for you based on technical
merit. After all, Seam may have saved Java EE, but can it fit the bill as your development framework of
choice?
1.2.4 Making the case for Seam
Is there really a need for another application framework? Wasn't Spring supposed to be the one framework to
rule them all? I will let the success of Ruby on Rails, and the wave of Java developers flocking to it, reveal that
the need remains for a suitable Java application framework—or, in some developers' minds, an entire
programming environment. So, should you follow the crowd? My advice is to look before you leap.
Promising that a framework will make the job of developing applications simpler is just lip service. Just
because you are able to create a throwaway blog application with a framework doesn't make it viable. To earn
the right to be called enterprise software, the framework has to stand up to the challenges of the real world,
warts and all, and help the developer create well-designed, robust, and readable code. That is Seam's goal.
Seam eliminates complexity and makes proven libraries more accessible. Seam doesn't turn its back on the
pervasive Java EE platform, but rather serves as the glue that makes it truly integrated. Rather than encourage
you to forget everything you know, Seam finds a way to allow you to use the Java EE services in a more agile
way, while also providing enough new toys, in the form of extensions and third-party integrations, to make
using it fun and interesting.
Below is a small sampling of the many improvements that Seam brings to the Java EE platform, all of
which succeed in making the platform simpler:
z
Eliminates the shortcomings in JSF, such as the lack of pre-render page actions, that have been the
subject of countless rants
z
Mends the communication between JSF and EJB 3 session beans / Spring beans
z
Collapses unnecessary layers and cuts out passive middle-man components
z
Offers a solution for contextual state management, discouraging the use of the stateless architecture
(i.e. procedural business logic)
z
Manages the persistence context (Hibernate Session or JPA EntityManager) to avoid lazy
initialization exceptions in the view and in subsequent requests
z
Provides a means for extending the persistence context throughout a use case
z
Connects views together with stateful page flows
z
Brings business processes to the web application world
z
Plugs in a POJO-based authentication and authorization mechanism that is enforced at the JSF view
ID level, accessible via the EL, and can be extended using declarative rules
z
Provides an embedded container for testing in non-Java EE environments
z
Delivers more than 30 reference examples with the distribution
As you can see, Seam is not shy about addressing problems in the platform, particularly those with JSF.
For many, bullet point one is enough to justify the need for this framework if you are already committed to
JSF. JSF can be quite painful without Seam's aid. The second point justifies Seam's usefulness in standardsbased environments. But, Seam doesn't stop there. It advocates simpler architectures by encouraging
developers to collapse unneeded layers and feel comfortable using long-running state. Seam does more than
just improve the programming model. It provides a tool to build out the scaffolding of an application and
generate CRUD functionality from an existing database schema, makes integration testing easy, and serves up
Ajax in a variety of ways.
Licensed to Jaroslaw Gilewski <[email protected]>
1.3 Seam's approach to unification
Seam revitalizes the standard Java EE platform by putting an end to its divergence and unifying its
components, filling in the voids for which it is often criticized, making it more accessible, extending its reach
to third-party frameworks and libraries, and form fitting them all together as a well integrated and consistent
stack. While the features of Seam are vast, Seam's core mission is getting JSF, JPA, and POJO components to
work together so that the developer's focus can be placed on building the application, not on integrating
unallied technologies.
1.3.1 Seam integrates JSF, JPA and POJO components
Getting technologies to work with one another is more than just having them pass messages. It's about creating
an interaction that the blurs the boundary between them, making them act as a single, unified technology.
Seam achieves this integration by fitting EJB 3 up against the web-tier, finding a place for JPA, and scrapping
the ineffectual JSF managed bean container. After reviewing how Seam tackles these challenges, you get a
chance to determine which Seam stack is right for you.
Helping out a web-challenged EJB 3
By design, EJB components are not intended to be called on directly from JSF. It's great that EJB components
are scalable, transactional, and secure, but it doesn't do much good if they are completely isolated from the
web-tier, and in turn from JSF. This isolation makes them of limited use in web-applications because of the
complexity involved to integrate them. They are not able to access data stored in any of the web-tier scopes
(request, session, and application) or the JSF component tree, thus impairing their insight into essential parts of
the application. (In practice, you really just need the EJB 3 components to have access to the conversation
scope). Also, it's easy to get into trouble with concurrency when using EJB components from the web-tier. For
instance, the Java EE container is not required to serialize access to the same stateful session bean, leaving it
up to the developer to take care of this task or catch the exception that can result. There are also complexities
that arise when dealing with non-thread-safe resources such as the JPA EntityManager. The only way the
developer can safely use EJB components in the web-tier is by interfacing with an adapter layer.
Seam gives EJB 3 components access to web-tier scopes, offers a way to manage the state of these
components so that they can be used safely in the web-tier, and even serializes access to stateful components to
make concurrency issues a responsibility of the infrastructure and not the developer. Also, there is never a
question about thread-safety accessing non-thread-safe resources since Seam handles the scoping properly.
Turning the tables, JSF faces equivalent challenges accessing business-tier components.
Hooking JSF to a better backend
JSF has its own "managed" bean container that is configured using a verbose XML descriptor rather than
annotations and has a very limited dependency injection facility. While JSF managed beans can be stored in
the web-tier contexts, they are barren objects, lacking scalability, transaction atomicity, and security (probably
why they are termed beans and not components). They must reach out to an EJB 3 component to attain these
business services. What you find is that you are stuck creating this facade layer to bridge EJB 3 components to
the user interface that acts on them.
To correct this mismatch, Seam allows JSF UI components to tap right into the EJB layer by allowing EJB
3 components to stand in as JSF "backing" beans and action listeners. There is no longer a need for the
managed bean facade layer and its verbose XML descriptor. By eliminating the complexity caused by the
mismatch, it encourages developers to relax stringent mandates on overarchitected designs.
Licensed to Jaroslaw Gilewski <[email protected]>
Which Seam are you?
Seam is not just a collection of classes and artifacts that get dropped on your desk with the disclaimer "some
assembly required." The key to Seam's success is that it offers a handful of well-tested bundles that work
fluently. You can liken it to the simplicity of buying a Mac when compared to buying a Dell. When you buy a
Dell, you can customize the assembly down to the last stick of RAM. You get a product customized exactly to
your needs, but getting there requires a lot of thought and effort on your part. Buying a Mac is much simpler in
comparison. You choose between a laptop and a notebook, and then you select a screen size. Everything else is
just details that Apple works out for you. Seam has a comparable set of options. You chose a state provider and
a persistence provider (and, down the road, a web framework). Everything else is just details that the Seam
developers work out for you. By removing the burden of too many choices, Seam can make life for the
developer simpler.
The two main technology choices in a Seam application, summarized in figure 1.3, are the state provider
and the persistence provider. The state provider is the technology that handles the application logic and
responds to events in the UI. The persistence provider transports data to and from persistence storage. Seam
manages the persistence provider to allow for the persistence context to be extended across a series of pages
and shared amongst multiple components.
Figure 1.3 Seam's stack matrix, with options for state and persistence provider.
In looking at these options, you may be surprised to discover that EJB 3 is not required to use Seam. You
can use Seam with basic JavaBeans and Hibernate and still not lose out on functionality. Note that JavaBean
really means non-EJB component, so this option encapsulates Spring beans. Another popular bundle is to
adopt JPA without using EJB 3 session beans, which is the bundle used in the example application in this
book.
In order to make any of these technologies work together, there needed to be a way to integrate the various
containers. EJB 3 has its container. JSF has one two. Spring is yet another. Once again, the task of integrating
them falls on the shoulders of the developer. The need for a central integration point gave rise to Seam's
contextual component model.
Licensed to Jaroslaw Gilewski <[email protected]>
1.3.2 The contextual component model
At the heart of Seam is the contextual component model. Before your eyes gloss over, give me three short
sentences to make this term meaningful to you. (1) Seam is a factory that constructs objects according to
component definitions. (2) After creation, each object is stored in the container under one of several contexts
(i.e. variable scopes) with varying lifetimes, making the objects contextual and capable of holding state (i.e.
stateful). (3) Seam promotes the interaction of these stateful objects across contexts, assembling them together
according to metadata associated with their respective classes. Chapter 4 explores components and contexts in
depth and gives you an opportunity to learn how they are used in an application.
In this section, you learn how this model provides the basis for the unification of the technologies
previously discussed. The unification is facilitated by a combination of the component registry, annotations,
configuration by exception, method interceptors, and the unified expression language (EL).
A central component registry
Seam rakes in all of the Java EE components into a central registry, whether they are EJB session beans,
JavaBeans, Spring beans or JPA entities. Any technology incorporated into the Seam stack can look to the
Seam container to retrieve instances of the components by name and collaborate with the container to
exchange state. Technologies which have access to the container include Seam components, JSF view
templates, jBPM process definitions, jPDL page flow definitions, JBoss Rules, Spring beans, JavaScript, and
more. Seam's container also unifies the variable scopes of the servlet API and introduces two additional
stateful scopes, conversation and business process, that are better aligned with supporting user interactions.
Of course, components aren't just going to fall into this registry, they have to be recruited. Seam scours the
classpath and enlists any class that contains a marker annotation, discussed next, that identifies it as a Seam
component.
Annotations over XML
One way that Seam cuts down on the configuration overhead of Java EE is by eliminating needless XML.
Although once thought to be desirable because of is flexibility, XML is an external configuration and quickly
becomes out of sync (and out of touch) with the application logic. Seam brings configuration back in line with
the code where it is easier to locate and can be refactored.
When the temptation arises to define JSF managed beans in XML, Seam just says "No", following the
advice of figure 1.4. Seam reduces the declaration of a component to a single annotation, @Name, placed above
the class definition. Seam components can take the place of JSF managed beans.
Figure 1.4 Seam cuts down on superfluous XML configuration that is difficult to keep in sync with the source code.
Licensed to Jaroslaw Gilewski <[email protected]>
If you are dedicated enough, you can avoid the use of XML in Seam altogether, which is quite surprising
given the number of places it could be warranted. Seam only resorts to XML when annotations do not suffice
or to isolate deployment overrides. Moving to annotations is more than just improving the efficiency of
keystrokes. Annotations are the central piece of Seam's configuration by exception strategy, conserving
keystrokes until they are really necessary.
Configuration by exception
A good way to describe configuration by exception is by saying that the software is "opinionated." The general
idea is that the framework happily prefers to operate as designed. The more you embrace the defaults, the less
work you have to do. You are only required to step in and play a part when the software needs to do something
different than the typical behavior.
In Seam, configuration by exception goes hand-in-hand with annotations. The annotations give Seam a
hint to apply behavior and Seam tries to assume as much as possible about the declaration by relying on
sensible defaults and standard naming conventions to keep your load light. In this way, Seam offers a nice
balance between explicit declarations and assumed functionality.
While annotations cut down on keystrokes, there is more to annotations than just the elimination of XML.
Annotations supply extra metadata to the class definition, where it is easier to find when compared with
external descriptors and stays with the component throughout its lifetime.
Decorating components with services
Since components are requested through the Seam container, Seam has an opportunity to manage the instances
throughout their life cycle. Seam wires the object with interceptors, wrapping it in a shell known as an object
proxy, before handing down the newly created instance. This allows Seam to act as the object's puppeteer,
pulling on its strings during each method call to modify its behavior, as depicted in figure 1.5. Interceptors
account for much of the implicit logic in Seam that makes it "just work". Examples include beginning and
committing transactions, enforcing security, and getting objects to socialize with one another. Annotations on
the class definition give the interceptors a hint of how to apply the extra functionality, if for some reason it
cannot be implied or needs to be different from the default behavior.
Figure 1.5 Interceptors trap method calls and perform cross-cutting logic around a method invocation.
The final piece to the unification puzzle is giving the application a way to access components in the
container using a universal syntax. That is the role of the unified EL.
Extending the reach of the unified EL
The unified EL is a expressive syntax used to resolve variables and bind components to properties and
methods on JavaBeans. It was introduced to better integrate JSF with JavaServer Pages (JSP). It is also used by
Licensed to Jaroslaw Gilewski <[email protected]>
JSF to lookup managed beans and other objects stored in web-tier scopes and is the basis for the JSF binding
mechanism. It's impact, however, is far more widespread thanks to its pluggable design.
The EL is an open API that allows custom resolvers to be registered, thus turning the EL into a variable
hub. Conversely, any layer of the code that wants to tap into the EL unified variable context can do so using its
API. Thus, the EL frees you from having to develop a custom bridge between the variable contexts of the
different technologies in your application. Although you are used to seeing the EL only in the view, there
really isn't anything web-specific about it.
Seam takes advantage of the EL in two ways. First, Seam registers a custom EL resolver that is aware of
the Seam container. This allows Seam components to be accessed using EL notation from anywhere in the
application where the EL is available (which is pretty much everywhere). Secondly, Seam makes heavy use of
the EL under the covers, allowing EL notation to be used in annotations, configuration descriptors, message
strings, EJBQL queries, page flow definitions, and even business processes. With Seam, the EL truly is
unified.
Despite all that has been said about Seam, nothing speaks to a programmer like lines of code. To help
demonstrate why Seam is a sound choice and how it saves you valuable development time, I am going to whet
your appetite with a brief example. I don't want to go into too much detail and spoil your appetite before
chapter 2, when you get a chance to sink your teeth into Seam by building out an entire application with just a
couple of commands.
1.4 Your first swings with Seam
To demonstrate some of the core principles of Seam, I am going to step you through a basic application that
manages a collection of golf tips. Don't worry about trying to understand everything that you see here. Instead,
focus on how Seam relies on annotations to define components, how it pulls the layers of the application
together with its unified component model, and how it maintains a high signal-to-noise ratio in the business
logic. I demonstrate a densely packed set of features in this example, so don't think that you have to use all of
these techniques in order to use Seam.
We all want to be better golfers (at least, those of us who torture ourselves with the sport). Focusing on a
simple golf tip can help to shave a couple of strokes off your round. To keep track of the tips that you collect
from the pros, buddies, and articles, you are going to slap together a Seam application that reads and writes
these tips to a database. Aside from the deployment artifacts, which aren't going to be considered in this
example, there are only a handful of files that you need to create to get this application functioning.
1.4.1 Entity classes a backing beans
I will start by discussing the GolfTip JPA entity class, shown in listing 1.1. In a Seam application, entity
classes serve two purposes. Their primary role is to carry data to and from the database. The object-relational
mapping mechanism, as this is called, is not part of Seam, per se. That work is handled either by JPA (the
standard Java persistence framework) or Hibernate, though you will discover later that Seam helps manage the
interaction with these two persistence frameworks.
In a Seam application, entity classes serve as form "backing" beans (akin to a Struts ActionForm) to
capture input from the user, thus replacing the need for developing a dedicated "backing" bean class. An entity
class becomes a candidate for use in a JSF view if it has a @Name annotation on its class definition, a condition
which is satisfied by the GolfTip class in listing 1.1. You then bind the form inputs to properties on the entity
class and JSF handles the necessary conversions.
Listing 1.1 The JPA entity class that represents a golf tip.
Licensed to Jaroslaw Gilewski <[email protected]>
@Entity
@Name("tip")
public class GolfTip implements Serializable {
@Id @GeneratedValue
protected Long id;
#2
#1
#3
protected String author;
protected String category;
protected String content;
// getters/setters for author, category and content not shown
}
Cueballs in code and text
The keywords prefixed with the @ symbol are Java 5 annotations. The @Name annotation [#1], shown in
bold, is a Seam annotation that registers the GolfTip class as Seam component named tip. Whenever the
context variable tip is requested from the Seam container, Seam creates a new instance of the GolfTip
class, binds the instance to the tip context variable in the conversation context (the default scope for entity
classes), and returns the instance to the requester.
The remaining annotations in this class pertain to JPA. The @Entity annotation [#2] associates the
GolfTip class with a database table by the same name. The @Id annotation [#3] indicates to JPA which
property is to be used as the primary key. The @GeneratedValue annotation [#3] enables automatic
surrogate key generation in the database. All of the other properties on the class (author, category, and
content) are automatically mapped to columns with the same name as the respective property in the
GolfTip table, following configuration by exception semantics.
As you can see, using the @Name annotation gives you one less file to worry about (that of the JSF
managed bean facility and its verbose XML dialect). Staying away from the managed bean configuration is
one of the early benefits of moving to Seam components. Another compelling advantage of moving to Seam is
being able to bind the action of UI command component to an action listener method on transactional business
object.
1.4.2 POJO components as action listeners
As with entity classes, there is no need to create a dedicated managed bean to act as a mediator between the
JSF page and the service object in Seam. Instead, the service object can respond directly to an action invoked
in the user interface. At first, that might sound like a really bad idea because it causes tight coupling between
the user interface and the application logic. Seam prevents this coupling by acting as the mediator itself. The
action listener component does not have to contain a single reference to a JSF resource. In fact, in chapter 3,
you discover that the method's return value does not have to correlate with a navigation rule, which is a typical
requirement of JSF managed beans. This example relaxes the separation from JSF to keep the number of
classes to a minimum.
In the golf tips application, the tip TipAction class, shown in listing 1.2, is declared as a Seam
component using the @Name annotation and is thus capable of having its methods bound to user interface
controls. It handles the add and delete operations in the golf tips interface.
Listing 1.2 The action listener for the JSF view.
@Name("tipAction")
#1
Licensed to Jaroslaw Gilewski <[email protected]>
public class TipAction {
@In
EntityManager entityManager;
#2
@In
FacesMessages facesMessages;
#3
@DataModel(scope = ScopeType.PAGE)
private List<GolfTip> tips;
#4
@DataModelSelection
@Out(required = false)
private GolfTip activeTip;
@Factory("tips")
public void retrieveAllTips() {
tips = entityManager.createQuery("from GolfTip")
.getResultList();
}
public void add(GolfTip tip) {
entityManager.persist(tip);
activeTip = tip;
facesMessages.add(
"Thanks for the tip, #{activeTip.author}!");
retrieveAllTips();
}
public void delete() {
activeTip = entityManager.find(
GolfTip.class, activeTip.getId());
entityManager.remove(activeTip);
facesMessages.add("The tip contributed by " +
"#{activeTip.author} has been deleted.");
retrieveAllTips();
}
#5
#4
#7
#6
#6
#6
}
Cueballs in code and text
Like the GolfTip entity class, the @Name annotation [#1] designates the TipAction class as a Seam
component, this time scoped to the event context (the default scope for JavaBean components). What really
sets this component apart from the GolfTip entity class is that it is capable of receiving references to
dependent components dynamically on account of the @In annotation being placed above certain fields of the
class, a mechanism know as bijection. The two dependent components are the JPA EntityManager [#2] and
the built-in JSF messages manager [#3]. This component also prepares and exposes the collection of tips to the
JSF view [#4], captures the record selected in the UI [#5], and can reference that selected record in JSF status
messages to be displayed to the user [#6]. All of these features are available without having to write a single
line of custom code.
The TipAction component packs a lot of functionality in a very limited amount of space. What I want
you to recognize is that, aside from the annotations, there is very little evidence of framework code in this
class. The only code that you are required to write is that which reads, persists, and removes tips from the
database by interacting with the JPA EntityManager. Even that code is probably better off in a data access
Licensed to Jaroslaw Gilewski <[email protected]>
object, which itself can be a Seam component. The focus of this example is on frugality. There is no
infrastructure logic that reads request parameter values or sets request or session attributes. Instead, it only
contains pure business logic.
1.4.3 Binding components to the view
Seam bridges the layers in the golf tips application by binding both the properties of the entity class and the
action handler method to elements in the JSF view. Figure 1.6 shows the rendered golftips.xhtml template
along with the value- and method-binding expressions that are associated with the page elements of interest.
NOTE
The file extension .xhtml indicates that this file is a Facelets template. Facelets is an alternative view
handler for JSF that was created to alleviate the mismatch between the JSF and JSP life cycles. Facelets is
the preferred view technology for Seam applications and is used throughout the book.
Use this figure to follow along with the discussion of how the JSF view interacts with the Seam
components in the server.
Figure 1.6 The golf tips page, which renders the collection of tips at the top and a form for contributing a new tip at the bottom.
Start by focusing your attention on the form that is used to submit a new tip at the bottom of the page.
Each of the input elements are bound to properties on the GolfTip entity class using expression language
(EL) notation (e.g. #{tip.author}). When used in the value attribute of an input element, the EL notation
Licensed to Jaroslaw Gilewski <[email protected]>
acts as a value-binding expression. It captures the form value and transfers it to an instance of the GolfTip
entity class as part of the JSF life cycle. Here is the (slightly trimmed down) fragment of the JSF template that
renders the form:
<h:form>
<h3>Do you have golf wisdom to add?</h3>
<div class="field">
<h:outputLabel for="author">Author:</h:outputLabel>
<h:inputText value="#{tip.author}"/>
</div>
<div class="field">
<h:outputLabel for="category">Category:</h:outputLabel>
<h:selectOneMenu value="#{tip.category}">
<f:selectItem itemValue="The Swing"/>
<f:selectItem itemValue="Putting"/>
<f:selectItem itemValue="Attitude"/>
</h:selectOneMenu>
</div>
<div class="field">
<h:outputLabel for="content">Advice:</h:outputLabel>
<h:inputTextarea value="#{tip.content}"/>
</div>
<div class="actions">
<h:commandButton action="#{tipAction.add(tip)}"
value="Submit Tip"/>
</div>
</h:form>
Seam makes the association between the value-binding expressions in the input fields and the GolfTip
entity class through the context variable tip. The @Name annotation on the GolfTip class binds the class to
the tip context variable. When the tip context variable is referenced by a value expression in the JSF
template (#{tip.*}), Seam instantiates the GolfTip class and stores the instance in the Seam container
under the variable name tip. All the value expressions that reference the tip context variable are bound to
that same instance of the GolfTip class. When the form is submitted, the input values are transferred to the
properties of the unsaved entity instance.
Let's consider what happens when the form is submitted. The method binding expression specified in the
action attribute of the submit button, #{tipAction.add(tip)}, indicates that the TipAction
component serves as the action handler for this form. Notice that this method expression actually passes the
GolfTip instance associated with the tip context variable directly into the action method as its sole
argument. Parameterized method-binding expressions are provided as an enhancement to JSF as part of Seam.
You aren't required to use this feature, but it helps with readability.
1.4.4 Retrieving data on demand
What makes Seam so powerful is that it includes a mechanism for initializing a variable on demand. The top
half of the screen in figure 1.6 renders the collection of tips in the database using the following markup:
<rich:dataGrid var="_tip" value="#{tips}" columns="1">
<rich:panel>
<f:facet name="header">
<h:outputText value="#{_tip.author} on #{_tip.category}"/>
</f:facet>
Licensed to Jaroslaw Gilewski <[email protected]>
<h:outputText value="#{_tip.content}"/>
<h:commandLink action="#{tipAction.delete}">
<h:graphicImage value="/images/delete.png" style="border: 0;"/>
</h:commandLink>
</rich:panel>
</rich:dataGrid>
The focal point of this markup is the #{tips} value expression. Notice that tips is not the name of one
of the Seam components in the golf tips application. However, it is referenced in the value attribute of the
@Factory annotation above the retrieveAllTips() method of the TipAction class from listing 1.2.
The purpose of this method is to initialize the value of the tips context variable when it is requested.
Subsequent requests for the same variable return the calculated value rather than causing the method to be
invoked again.
But, hold on a minute. The retrieveAllTips() method doesn't return a value. How is the value
passed back to the view renderer? That's where things get a little tricky. After executing this method, Seam
exports properties of the component that are annotated with either @Out or @DataModel to the view. Seam
notices that the @DataModel annotation is assigned to the tips property on the TipAction component.
That tells Seam not only to export its value to the tips context variable, but to first wrap the value in a JSF
DataModel instance. The view iterates over this wrapped collection to render the data grid. The reason the
collection is wrapped in a DataModel is to enable clickable lists to support the delete functionality.
1.4.5 Clickable lists
The scope specified on the annotation is ScopeType.PAGE, which instructs Seam to store the collection of
tips in the JSF component tree. Since the data model is being stored in the JSF component tree, it is made
available to any JSF action that is invoked from that page (resulting in a "postback").
The command link bound to the #{tipAction.delete} method expression is an example of an action
that benefits from the propagation of this data model through the component tree. When the user clicks on one
of the delete buttons, the data model is passed along with the event and JSF ensures that the activated row
correlates with the appropriate item in that data model. This is where the complement to the @DataModel
annotation, the @DataModelSelection annotation, is used. This annotation captures the selected row in the
tips data model and injects it into the property over which it resides. With the work of assigning the tip from
the activated row to a property of the component already taken care of, all the action listener method has to do
is pass that reference to the JPA EntityManager to have it removed from the underlying database. Once
again, no custom coding is required other than the business logic.
All that is left is to write a quick end-to-end test to ensure that we can save a new tip and that it can be
subsequently retrieved.
1.4.6 Integration tests designed for JSF
The area of development that has routinely slowed down Java EE developers most often is testing. Even if you
have never written a test, you are still testing. You test your code every time you redeploy your application or
restart the application server to view the result of your latest modifications. It's just really slow and boring to
do it that way. These days, testing is an integral part of any application development, and no framework is
complete without an environment that allows you to test "outside of the container." Seam once again
demonstrates its simplicity by exposing a single test class that can handle all of the integration testing needs in
a Seam-powered application.
To make integration testing of JSF actions a breeze, Seam provides a base test class that sets up a
standalone Java EE environment and executes the JSF life cycle within the test cases. The test infrastructure is
Licensed to Jaroslaw Gilewski <[email protected]>
driven by TestNG 2, a modern unit testing framework that can be configured using annotations. Although
TestNG does not require you to inherit from a base test class, Seam's testing framework uses this approach to
setup the fixture needed to bootstrap the embedded Java EE environment and the JSF context.
The test class GolfTipsTest in listing 1.4 simulates the initial request for the golf tips page and the
subsequent form submission to add a new tip. The code in the test is invoked nearly identically to when it is
used in the deployed application.
Listing 1.4 An end to end test of the golf tips application using the Seam test framework.
public class GolfTipsTest extends SeamTest {
@Test
public void testAddTip() throws Exception {
#1
new NonFacesRequest("/golftips.xhtml") {
protected void renderResponse() throws Exception {
Object value = getValue("#{tips}");
#2
assert value != null && value instanceof DataModel;
DataModel tips = (DataModel) value;
assert tips.getRowCount() == 0;
}
}.run();
new FacesRequest("/golftips.xhtml") {
protected void updateModelValues() throws Exception {
setValue("#{tip.author}", "Ben Hogan");
setValue("#{tip.category}", "The Swing");
setValue("#{tip.content}",
"Good golf begins with a good grip.");
}
protected void invokeApplication() throws Exception {
invokeMethod("#{tipAction.add(tip)}");
}
#3
#4
protected void renderResponse() throws Exception {
Object value = getValue("#{tips}");
#5
assert value != null && value instanceof DataModel;
DataModel tips = (DataModel) value;
assert tips.getRowCount() == 1;
List<FacesMessage> messages =
FacesMessages.instance().getCurrentMessages();
assert messages.size() == 1;
assert messages.get(0).getSummary()
#6
.equals( "Thanks for the tip, Ben Hogan!" );
}
}.run();
}
}
<Annotation #1> Designates a TestNG test method
<Annotation #2> Initial retrieval of tips, expecting 0
<Annotation #3> User filling out form
<Annotation #4> User clicking submit button
2
http://www.testng.org
Licensed to Jaroslaw Gilewski <[email protected]>
<Annotation #5> Subsequent retrieval of tips, expecting 1
<Annotation #6> Verify message with interpolated value
Cueballs in code
This tests both the initial rendering of the JSF view and the subsequent JSF action triggered from the
rendered page. The first request is a HTTP GET request, which occurs when the user first requests the golf tips
page. This part of the test verifies that when the tips are retrieved in the Render Response phase, Seam properly
resolves a DataModel, but the collection underlying that model is empty. The second part of the test
simulates the user submitting the form to create a new tip. The Update Model Values phase emulates the work
JSF does to bind the input values to the value expressions. The method expression that is bound to the submit
button is then forcefully invoked. Because Seam automatically wraps the Invoke Application phase in a
transaction, there is no need to worry about beginning and committing the transaction. Finally, in the Render
Response phase, the test verifies that when the tips are retrieved once again, that exactly one tip is found and
that the message to display to the user is present and the author's name has been interpolated properly. This test
is intentionally terse. Of course, there are many other cases that could be verified. Focus instead on how easy it
is to exercise a Seam application using this simple test framework.
Hopefully the golf tips application has given you an idea of what Seam is all about. You should now have
a general understanding of how Seam simplifies your application and saves you time by relying on a
centralized container, annotations, configuration by exception and the unified EL. That is the essence of Seam.
I now want to give you an idea of what else Seam offers you before you begin your journey down the road to
becoming a Seam master.
1.5 Seam's core competencies
Throughout this chapter, there has been a lot of discussion about how Seam resolves issues in Java EE. I want
to leave you with an understanding of how Seam is going to help your development process. Given how much
Seam has to offer, this was a challenging exercise, but I have been able to summarize its benefits into three
core competencies. Seam offers a better JSF, allows you to get rich quick, and fosters and agile environment.
1.5.1 Offers a better JSF
Seam does a lot to improve on the design of JSF. Although JSF isn't without flaws, it was selected as the main
presentation framework in Seam because of its extensible request life cycle and strong UI component model.
Seam taps into this potential and succeeds in making JSF a very compelling technology choice for creating
web-based interfaces. While it's true that Seam supports alternative view technologies, this book primarily
focuses on using Seam with JSF. Much of this coverage comes in chapter 3, which explains how Seam
decorates and extends the JSF life cycle.
Seam's JSF enhancements
Seam's most recognizable improvement to JSF is eliminating the requirement to declare managed beans in the
JSF descriptor. In additional, Seam adds a rich set of page-oriented functionality, covered in chapter 3, that
makes the navigation rules in the JSF descriptor obsolete as well. These features include:
z
pre-render page actions
z managed request parameters (for a given page)
z intelligent stateless and stateful navigation
z transparent JSF data model and data model selection handling
z fine-grained exception handling
Licensed to Jaroslaw Gilewski <[email protected]>
z view-level security (per view ID)
z annotation-based form validation
z bookmarkable command links (solving the "everything is a POST" problem)
z entity converter for pick lists
z conversation controls
z prevents lazy initialization exceptions and non-transactional data access in the view
Part of the cleaning out process of JSF involved purging passive connector beans that did nothing more
than adapt the UI with the backend action handlers.
Eliminating connector beans
Any Seam component can serve as a backing bean. Figure 1.7 shows the design of an interaction between a UI
form and an EJB 3.0 session bean (or regular JavaBean) that completely eliminates the need for the connector
bean. The form controls are bound directly to the entity class and the session bean handles the action to persist
the data.
Figure 1.7 Seam cuts out the middle-man by eliminating the need for a JSF backing bean. Instead, the entity class and the EJB 3.0
session bean work together to capture data from the UI and handle the event to persist the data.
By cutting out the middleman, not only does Seam allow you to eliminate a class that you have to write
and maintain, but it allows you to cut back on the number of layers, allowing your applications to become
more light weight.
Aside from providing universal access to components, the Seam container augments the coarsely-grained
scopes in the Java servlet specification—request, session and application—to include scopes that make more
sense from the perspective of the application user. Seam offers two "stateful" contexts that are used to support
single and multi-user pages flows in an application.
Stateful variable scopes
One of the main challenges with developing applications that are delivered over the web is learning how to
efficiently propagate data from one page to the next—so called state management. The two goto options are
Licensed to Jaroslaw Gilewski <[email protected]>
hidden form fields or the HTTP session. The first is cumbersome for the developer, the second eventually eats
through precious server resources and hurts an application's ability to scale.
Seam addresses need for stateful variable scopes whose lifetime aligns with user interactions by adding the
conversation context and business process context to the standard web scopes. The conversation scope,
covered in chapter 7, maintains data for a single user across a well-defined series of pages while the business
process scope, covered in chapter 14, is used to manage data that supports multi-user flows complete with wait
states. The relationship between the lifetime of the scopes managed by the Seam container is illustrated in
figure 1.8.
Figure 1.8 The lifetimes of the six scopes in a Seam application. The standard scopes are represented by dashed lines, while the
scopes that Seam contributes are shown as solid lines. The business process scope is persisted to a database and can thus outlive
the application scope when the application server restarts.
The conversation context is tremendously important in Seam not only because it is so unique and gives the
user a better experience, but because it makes makes working with an Object-Relational Mapping (ORM) tool
easier on the developer.
Extending the persistence context
When you talk to the database using ORM, you use a persistence manager (i.e. JPA EntityManager or
Hibernate Session). Each instance of a persistence manager maintains an internal persistence context, which
is an in-memory cache of entity instances that have been unmarshalled from the database. Given that databases
are among the most expensive and heavily used resources in your server room, you want to leverage this inmemory cache as much as possible to avoid redundant queries. Extending the persistence context across the
entire request is a step in the right direction (the so-called Open Session in View Pattern), but having it extend
across multiple page requests is even better. Prior to Seam, there was just no good place to stick it, and as a
result, each request reset the persistence context to a blank slate.
Seam takes control of the persistence manager, storing it in the conversation context, and is thus able to
carry it, along with its persistence context, across the duration of an entire user case, potentially spanning more
than one request, as shown in figure 1.9. Extending the persistence context across the three operations in this
feature allows the entity instance to be retrieved only a single time. The remaining operations perform on that
instance. This ensures object identity and can guarantee atomicity of the operation.
Licensed to Jaroslaw Gilewski <[email protected]>
Figure 1.9 Using the extended persistence context to keep an object in scope for an entire use case, even across multiple page
views. The extended persistence context avoids having to merge detached entity instances.
With Seam in control of the persistence manager, lazy initialization exceptions (LIE) are also a thing of
the past since the persistence manager remains open throughout the use case and can thus load additional
records as needed. The conversation and persistence context fit together so naturally that the conversation has
been dubbed Seam's unit of work. You learn all about the interaction between the two in part 3.
1.5.3 Gets you rich quick
Seam gives you tools to build rich, Web 2.0 applications or to gently weave this richness into an existing pageoriented application. Lately, the term "rich" has become synonymous with a desktop-like experience in the
web browser driven by Ajax. There are two approaches you can take to incorporate Ajax into a Seam
application. You can use Ajax-enabled JSF components, such as RichFaces or ICEFaces, or you can invoke
server-side components directly from the browser using JavaScript remoting. Seam extends the meaning of
rich to incorporate media such as PDFs, charts, and graphics.
Tapping into the JSF ecosystem
Web user interfaces are getting more and more sophisticated and it is unreasonable to think that you can code
the XHTML and JavaScript from scratch and get the job done cheaply. You need to build on what others have
done. That is one of the primary goals of JSF and why Seam went with JSF as the primary user interface
framework.
JSF is all about putting widgets on the screen. It decouples the design of a UI component from its use.
Similar to widgets in Swing, JSF components are general solutions to common controls. This time, the vendors
really did come through. There are loads of component libraries for JSF that range from basic data tables, to
tree structures, to drag and drop targets. A sampling of these component libraries can be found in appendix C.
Historically one of the most entangled parts of an enterprise application is the UI (let's share hideous JSP
files). By moving to JSF, the UI becomes a much simpler place. You really don't even need a WYSIWYG IDE
because visualizing what these components render is quite reasonable. They are human-friendly, rather than
tool-friendly. With JSF, the user-interface finally has an API too.
While JSF has its place, if you are looking for a lighter way to communicate with the server, Seam's
JavaScript remoting library is a great alternative.
JavaScript remoting
Invoking server-side components from JavaScript in Seam couldn't be easier. You simply add the
@WebRemote annotation to the Seam component method that you want to call from JavaScript, import the
remoting library into the web page, and then invoke the component method using a JavaScript client stub of
Licensed to Jaroslaw Gilewski <[email protected]>
the component. Seam handles the rest. JavaScript remoting opens the door to creating single-page applications
with Seam.
Although Ajax gets most of the attention these days when web-applications are discussed, there are other
ways to make your application rich. These fall under the heading of rich media.
Creating rich media
Seam is very adept at generating a variety of rich media. Seam uses the Facelets view library to support
alternate output based on XHTML templates, including PDF documents, RTF documents, charts, and multipart
emails with attachments that include the previous items. With the addition of two JSF component tags, Seam
can accept file uploads without any custom, low-level coding and can render dynamic graphics. All of these
tasks are typically passed off by web frameworks to third-party libraries. While it's true that Seam leverages
functionality provided by libraries such as iText and jFreeChart, the delegation is abstracted away. You are
provided with a consistent approach, based on Facelets composition templates, that allows these features to be
a native part of your Seam application.
1.5.3 Fosters an agile environment
In addition to being a framework, Seam also provides a collection of tools that help you setup a project,
generate code, and develop in an incremental manner.
Project generator
One of the main highlights of Seam is its project generator, seam-gen. This tool serves two main functions. It
sets up the structure of a Seam-based project, complete with a build script, environment profiles, and all of the
libraries and configurations required to start developing your application. It's the best way to get started with
Seam if you are new to the framework. The seam-gen tool can also create an application prototype by reverseengineering a database schema and generating all of the necessary artifacts to create, read, update, and delete
data in that database. In chapter 2, you learn all about seam-gen and use it to create a complete golf course
directory web application.
Hot deployment
Seam goes to great lengths to enable "instant change" in the develop cycle. Seam initializes a hot redeployable
classloader that is capable of reloading changed Java classes files just like they were JSP files. The project
build script takes care of incrementally compiling the changed source files and shipping them off to the server.
That means that you can change a Java file and a second later the change you made is reflected in the
application. When the modified files are moved into the hot deployment area of the application server, the
application server does not restart, nor does the application reload. This feature applies to Seam configuration
descriptors and uncompiled Groovy scripts as well. You can finally match the change-view-change-view cycle
that was previously only available with scripting languages such as PHP and Ruby.
Seam debug page
While developing your application, bad stuff happens. As a result, you get exceptions. Rather than always
having to race to the log file to find the cause, Seam gives you a head start. When you run Seam in debug
mode, any exception that occurs will be caught and summarized on a special debug page. In addition to the
exception, this page gives you a snapshot of the JSF component tree and any Seam component instances that
are present at the time of the exception.
Licensed to Jaroslaw Gilewski <[email protected]>
You don't even have to wait for an exception to occur to use this page. When the debug page is accessed
directly, it renders a list of all conversations and sessions that are currently active. You can then drill down on
any of the active contexts to inspect the component instances that are stored in them.
Testing without deploying
The primary reason developers grew wary of the standard Java EE platform was because of its inability to
operate in isolation. Testing an application meant packaging it up and shipping it off to a Java EE compliant
application server, a costly process.
In order to work around this problem, developers adopted the POJO programming model, which
encourages you to design code in such a way that it can be tested in isolation from the container and its
services. While POJOs are definitely a good thing, and encourage proper unit testing, there is no replacement
for integrating your components in a real environment to ensure that they work together. Previously, that
meant deploying to the application server once again. Seam has a better solution.
To support integration test environments (and also deployment to non-Java EE containers, such as
Tomcat), Seam ships with the embedded JBoss container. This portable container bootstraps a Java EE
environment to support services such as JNDI, JTA, JCA, and JMS in a standalone environment. With these
services up and running, you can test your application in place without having to deploy to a container. Seam
supports this testing scenario by bootstrapping the embedded JBoss container as part of its single class
integration framework, demonstrated back in section 1.4.6. This test infrastructure should prevent you from
having to deploy over and over again to verify that your action handlers talk properly to your persistence layer
and so on.
Between the incremental hot deployment support and the in place testing infrastructure, your valuable time
should rarely be wasted when working on a Seam application. If its your business logic that is hanging you up,
unfortunately there is not much Seam can do to help you there. That's all you.
1.6 Summary
The enthusiasm for Ruby on Rails was a real wake up call for the Java EE platform. It enlightened developers
to the fact that sacrifice is not a prerequisite for creating a successful application. Developers no longer wanted
to tolerate the burden of "XML situps" 3 and over-engineered flexibility. In response, the Seam founders
banded together best-of-breed Java EE technologies and created an agile platform that takes a bold stance
against Java EE's formalities, cutting back the XML descriptor overgrowth, accentuating the platform's recent
adoption of annotations and configuration by exception, and embracing the expressive syntax embodied by the
EL, Facelets, and Groovy. With Seam, creating applications in Java becomes exciting again, whether you are a
front-end designer, back-end developer, or jack-of-all-trades. Best of all, you can be confident that applications
built with Seam are scalable because the Java EE platform has proven itself in this regard, giving you
productivity without sacrificing performance.
First and foremost, Seam makes the task of defining and accessing stateful business-logic components
simple, regardless of whether they are EJB or non-EJB components. A basic @Name annotation atop a class
gains it admission into Seam's contextual container. The container wrap these components in method
interceptors, enabling enterprise services, such as transactions, security, and component assembly, to be
declared with equivalent ease by applying an annotation at the class, method, or field level. Seam grants the
technologies that it integrates access to the components in this container, primarily through the use of the
unified EL. This arrangement facilitates the use of JPA entity classes as "backing" beans in a JSF form, EJB
3
A term coined by the Ruby on Rails camp that equates XML authoring to strenuous exercise.
Licensed to Jaroslaw Gilewski <[email protected]>
session beans or transactional JavaBeans as action listeners on a JSF UI component, and variables to be
resolved on demand using Seam's factory or manager mechanism.
An important aspect of the container is its state management capabilities. It consolidates the variable
scopes in JSF with two of its own business-oriented scopes. Seam understands variable scoping and helps
components from different scopes to work with one another without violating thread-safety. Of particular note,
Seam can extend the lifetime of the persistence manager across multiple page requests to reduce load on the
database and eliminate complexities with using ORM in web-applications.
If you picked up this book because you believe that there is a better framework choice out there for you
(and you are not yet using Seam), my promise to you is that Seam is worth checking out and that the time you
spend reading this book will be worthwhile. But, merely knowing what framework someone recommends is
not enough to decide to use it. You have to know why a person prefers a particular framework. In this book, I
share with you my extensive knowledge of Seam and explain to you why I find it to be a compelling choice.
As you read along, I encourage you to develop your own reason for choosing Seam.
The key to agile development with Seam begins with the project generator, seam-gen. In the next chapter,
you learn how to use this tool to develop an entire application from scratch with only a handful of keystrokes.
Once the application is in place, I walk you through the project structure, show you how to get it setup in your
IDE, and demonstrate incremental hot deployment. While you must turn over some control when you opt to go
with seam-gen, you quickly find that you don't miss the work.
Licensed to Jaroslaw Gilewski <[email protected]>
2
Putting seam-gen to work
Learning a new framework can be challenging, risky, and time consuming. It requires you to leave the comfort
of your current tool set and venture into unknown territory. To justify your research, you search for early
victories. Trivial "Hello World" examples serve this purpose. After completing one, you proudly observe your
work and proclaim, "Yeah, it works!" Sadly, few others will be so impressed.
JBoss Seam lets you skip the putt-putt course. With seam-gen, Seam's rapid development tool, in your
hands, you can come out swinging. It enables you to create a functional, database-oriented application that is
ready for show and tell without having to write a single line of code. The seam-gen tool first gathers
information from you about your application, such as the project name and database connection properties. It
then uses that information to put in place the scaffolding of a Seam-based project. Finally, you point seam-gen
at your database, which it reverse engineers to create artifacts for serving dynamic web pages that can be used
to create, read, update, and delete (CRUD) the entities in the tables. The result? An achievement that is sure to
impress even the toughest crowd. How is that for in Action?
In this chapter, I demonstrate how you can quickly ramp up on the Seam framework by leveraging the
seam-gen tool. By the end of this chapter, you will have a working golf course directory application that you
can deploy to two different application servers. A cookie-cutter process is going to fall short in some areas, so I
also show you ways to customize the application that seam-gen kicks out. What you are left with is an
application that is far more functional and rewarding than what a typical "Hello World" has to offer. Don't you
know? CRUD is the new "Hello World".
2.1 The Open 18 prototype
It's 1:30 PM on Wednesday, two days before your summer vacation. Your boss taps you on the shoulder just as
you finish reserving tee times for your annual golf getaway in "Golf Heaven". You can't wait. You have
practiced all summer at the driving range so you can top last year's scores. Actually, let's be honest. You
seasoned your swing so that you can look like a pro in front of your fans (*cough* friends).
You look up and realize that your boss is still standing there, waiting for you to break out of your daze and
return to the real world. He looks serious. Obviously, this conversation is not going to be spent reminiscing
about golf. With a sobering tone, he informs you that he just ducked out of a management meeting in which he
was reminded of a web application that was supposed to have been completed months ago...
2.1.1 Consider yourself tasked
The sales team is expecting to present a working prototype of a golf course directory application at the biggest
trade show of the year, which happens to be this coming weekend. Without this application, they won't have
anything new to demonstrate. Showing up empty-handed will surely hurt the company image and jeopardize
its credibility. In truth, your manager should be sacked for letting the situation get to this point. The sad reality
is that it won't happen. Besides, turnover isn't going to help you now. The deed has been done; the promise has
been made. Someone is going to have to reach into the hat and yank the rabbit out by its neck. That someone is
you.
Licensed to Jaroslaw Gilewski <[email protected]>
If this were any other week, these antics would barely register on your annoyance meter. But this week is
different. The mercury is rising. If things don't go well, it may put your much anticipated vacation at risk. The
thought of not standing on the first tee at the break of dawn, in complete zen with the dew laden, green
landscape all around you is just killing you. But, you also get a kick out of being a hero, so you decide to crank
out a prototype by the end of the week and save the company before seeking your leisure. The question is, do
you have something in your toolbox that can solve your problem in a time crunch? A solution that you can live
with when you return?
You have read about how quickly you can create a functional Java EE 5-compliant application using JBoss
Seam, so you decide to put it to the test. The first step to writing any application is ironing out the
requirements. They come from your boss as an email:
You must build a web-based directory front-end for the company's extensive database of golf
facilities and courses. A user of the application should be able to browse, paginate, sort, and
filter the collection of the facilities and all related entities in the schema. By selecting one of
the facilities, they should be presented with its details, as well as a list of its courses. From
there, they should be able to drill down again to see the holes and tee sets for each course. An
administrative user should be able to take it a step further and modify any of the database
records. The marketing department also has some verbiage that you need to place on the
home page.
– Signed, Your Boss
There you have it, the only task standing between you and eighteen holes of serenity. The golf course
directory explained here is one part of Open 18, a golf community site that is used as the example application
in this book. Golf is a tremendously rich domain model and offers a nice opportunity to demonstrate Seam's
features. Some of the modules you will see later in the book include a data entry wizard, side-by-side course
comparison, score tracker, favorites and rating system, PDF score cards, and other real-world requirements. If
you want to follow along with the development of the project as you read, you will need Seam and its
prerequisites "installed". You can find instructions on how to get your software setup in appendix A. I
encourage you scan this supplementary material before continuing so that you get the most out of this tutorial.
The first step in building the prototype is getting your hands on the database. You and the database
administrator (DBA) need to sit down and have a chat to settle the age old debate between developers and
DBAs for this application: Which comes first, the entity or the schema?
2.1.2 Mapping entities to the database schema
Over the course of this book, you are going to encounter two development scenarios used by the sample
application, bottom-up and top-down. The difference is a matter of which one comes first, the database schema
or the Java entity classes, respectively.
If the database schema arrives into the world first, that is bottom-up development. The schema dictates, to
a large degree, how the Java entity classes will be formed. On the other hand, if the Java entity classes show up
first, that is top-down development. The classes have free reign over how the database schema will be
designed. Object-Relational Mapping (ORM) tools, such as Hibernate, give you some wiggle room in how the
Java entity classes are mapped to the database. For instance, it's possible to change the name of a column that
is mapped onto a property of the Java entity class without having to change the name of the property.
However, there are limits to how much the schema and the entity classes can deviate. The one that shows up
first is the master and the follower must adapt. As a developer, you have to be familiar with how to work in
both cases.
NOTE
Technically speaking there is a third scenario, meet-in-the-middle, where you have existing Java entity
Licensed to Jaroslaw Gilewski <[email protected]>
classes and an existing database. In this case, you are at the mercy of the mapping tool's capabilities. If you
have stretched the mapping tool to its limit and still can't cover the mismatch, you have to refactor either
the Java class or the database, bring you back to bottom-up or top-down semantics.
Seam-gen has tasks that support both bottom-up and top-down development. In this chapter, we take the
bottom-up approach by using seam-gen to reverse engineer the database schema. In chapter 4, you use the topdown approach to extend the schema to include golfer profiles.
Bottoms up!
To create the golf course directory portion of the application, you will be performing bottom-up development,
as showing in figure 2.2. Your job is to convert the five tables in the golf course directory schema (facility,
course, hole, tee_set, and tee) in the five Java entity classes (Facility, Course, Hole, TeeSet, and Tee) 4
that map to them. That sounds like it requires a lot of effort. Don't worry, working with an existing database is
where seam-gen really shines. It can reverse engineer the database and generate corresponding Java entity
classes for you, as well as some other artifacts. And honestly, the generated code is pretty nice! But first, you
need to get your hands on the schema.
Figure 2.2 Reverse engineering entity classes from an existing database schema, termed bottom-up development.
As a result of your last meeting, the DBA has prepared an H2 (Hypersonic 2) 5 database loaded with a
schema containing just the tables that you need for the golf course directory and a sampling of data. The entire
H2 database is handed to you as a compressed archive. Since the sales team needs to be able to get the
application running quickly, without the use of an internet connection, the embedded mode of H2 (linked with
the runtime of the application) is a good choice for the road show. It also helps that it requires zero installation
4
A introduction to the game of golf and the roles these entities play in it can be found in the beginning of this book.
5
The author of H2, Thomas Mueller, is also the creator of Hypersonic SQL (HSQLDB). H2 is a complete rewrite of Hypersonic SQL.
H2 has been built from scratch and does not share any code with its predecessor.
Licensed to Jaroslaw Gilewski <[email protected]>
and does not require that you run a separate server. Having one less command to worry about is always a good
thing.
Before you can dive into seam-gen, you need to put the database in its proper place. Section A.1.3 of
appendix A introduces the H2 database and gives details about where to find the database schema and the seed
data for the example application. Once you have a schema in place, its a good idea to inspect it before diving
into the reverse engineering process.
Inspecting the schema
You can connect to the provided database using the H2 administration console that is included with the H2
JDBC driver JAR. This step is not required to generate a CRUD application with seam-gen, but allows you to
ensure the database is setup properly and to have a chance to browse the schema.
Start the admin console by executing the Server class from the H2 JAR file:
java -cp /home/twoputt/lib/h2.jar org.h2.tools.Console
This command instructs you to visit the H2 console URL in your browser, http://localhost:8082. That URL
brings up the database connection screen in figure 2.3. Enter the connection URL and credentials shown in this
figure and click Connect.
Figure 2.3 The database connection screen for the H2 admin console.
TIP
The connection screen lets you save your connection profiles. Replace "Generic H2" with a name for your
connection and click Save. The connection information is stored in a file in your home directory. This
feature is useful for toggling between different database connections. You can also use the H2 admin
console to connect to any database with JDBC support. To use another database, you must add the
appropriate JDBC driver (JAR file) to the classpath argument of the java command used to start the
console. Refer to the H2 manual for more information.
Once the connection is established, a schema browser/query console will appear, shown in figure 2.4. This
database console should give you reason to understand why I chose to use the H2 database for the example
application. It packs a lot of functionality in a surprisingly small JAR file.
Licensed to Jaroslaw Gilewski <[email protected]>
Figure 2.4 The H2 admin console showing the database schema on the left and a query console on the right. The result set viewer
even allows you add and edit rows in the tables, as you can see in this screenshot.
At this point you do not need to do anything with the database, other than acquaint yourself with the
schema. Now that you know the database is setup properly, it's time to turn the job over to seam-gen.
2.2 Seam-gen as a learning tool
Seam-gen gives you the opportunity to learn how the Seam creators prefer to organize a Seam-based project.
It's also a great way to become familiar with Seam's moving parts so that you know what you are doing when
you take Seam out on the course. You may not be in a position to start an application from scratch. I ask that
you leave your reservations at the door, step out of your "working" environment if necessary, and play with
seam-gen at least once before messing with Seam on your own. There is something to be said for observing
Seam in its "natural" environment. From the words of Seam's founder, Gavin King:
There really are a LOT of advantages to starting with the seam-gen structure. I did a lot of
work on things that will take you a while to reproduce if you try to do it all from scratch.
(like, weeks of work!) However, there's nothing magical about it, and other structures can
work just as well.
– Gavin King, JBoss Seam Forums6
I, too, was hesitant to start with seam-gen at first. I viewed the tool as handcuffs for the creative mind;
hand-holding for a novice developer. After spending a lot of time using Seam (like, weeks of time!) I now
strongly encourage you to give seam-gen a try.
2.2.1 Seam-gen's specialty
The seam-gen tool is an application factory, as depicted in figure 2.1. Not only does this tool create a readymade application structure, it allows you to get ideas about how to use Seam's features. You may even find
yourself stealing some of the code that it generates to use in other applications.
6
The forum post can be found at http://www.jboss.com/index.html?module=bb&op=viewtopic&p=4030018
Licensed to Jaroslaw Gilewski <[email protected]>
Figure 2.1 The seam-gen application generator examines an existing database and creates a complete CRUD application that can be
used to browse, search, and manage the entities stored in the database tables.
In some regard, using seam-gen is like working alongside another developer. Sure, you aren't going to
agree with everything it creates, but it gives you a starting point; something to debate. By comparing your style
against the generated code, you gain a new perspective on how to use Seam. I'll admit that I have not been a
huge fan of code generation, mainly because it leaves behind lots of code that is scary to touch. With seamgen, I have come to realize that code generation is appropriate when used in a controlled environment. Seamgen is that controlled environment. The code it produces is very reasonable and likely equivalent to what you
would have written anyway. If you don't like how it generates the code, you can change it 7.
I must warn you, though, seam-gen is terribly addictive. I bet you can't use it just once. From the first
moment I watched seam-gen summon an application out of a database schema, I couldn't help myself from
pointing seam-gen at every database I had available just to see what it would drum up. By the end of the
chapter, you will likely exhibit my "kid in a toy store" behavior, raising hell like a urchin with a pop-gun.
HINT
To get the most bang for the buck, look for a database that is populated with data. The application created
by seam-gen isn't nearly as exciting without data to browse. If you have an existing database with no data, I
recommend you check out DBUnit as a way to populate it with sample data.
Seam-gen has proved to be an immensely helpful learning tool for figuring out how to define the JPA or
Hibernate mappings for an existing database schema. Setting up all of the association mappings between
entities can be an extremely tedious task. Just the other day I got wedged trying to remember how to map a
complex table relationship. Is it one-to-many or many-to-one? Do I need a composite key? To avoid wasting
time, I pointed seam-gen at my database and inspected the result. Seam-gen bailed me out and helped get me
back on the right page. If you have spent any length of time pouring through the JPA or Hibernate reference
documentation in search of a solution to these questions, seam-gen can help. That's just the beginning of what
a seam-gen project is capable of. Let's look at what other features it sets up for you.
2.2.2 Features that seam-gen provides
As I have mentioned already, seam-gen is capable of producing CRUD applications from an existing database
schema. It can also start from an existing set of entity classes, in which case the database schema is created
retroactively by Hibernate when the application starts. That's just the beginning, though. There are a lot of
other great features that seam-gen throws into the generated project, some of which will be covered in this
chapter. Additional features include:
z
7
Incremental deployment of static resources, view templates, and page descriptors
Seam-gen generates Java classes and Facelet views from Freemarker templates, making it easy to customize.
Licensed to Jaroslaw Gilewski <[email protected]>
z
Hot redeployment of JavaBean and Groovy components (excludes entity classes and EJB components)
z
Ready-made project files for quick import into Eclipse, NetBeans, and IntelliJ IDEA (using the Eclipse
project importer)
z
Facelet views (a JSF-centric view technology and alternative to JSP)
z
RichFaces or ICEFaces UI components (panels, tables, and menus)
z
A custom stylesheet that reinforces the look and feel of the rich UI components
z
Pagination of records on the listing page for each entity type
z
Tabs showing the parent/child entities on the detail screen of each entity
z
Pageflow for establishing an entity relationship when adding or editing a record
z
Entity model validations enforced in the UI with instant feedback using Ajax
z
Authentication and authorization stub, requiring users to authenticate before performing write
operations; the default configuration accepts any credentials
z
A component inspection page and developer-friendly error page
z
JCA datasource, JPA persistence unit, and JPA entity manager configured for managing the records in
the database; Hibernate 3.2 is used as the JPA provider
z
Seed database from import.sql script on classpath (Hibernate feature)
The application that seam-gen creates certainly goes beyond the CRUD prototyping I have seen done by
other frameworks. But don't fear that seam-gen is going to put you out of job. The truth is, while the
application that seam-gen creates is very capable, it is no replacement for hand-crafted work. You have to take
it for what it is, a prototype. It lacks the business knowledge necessary to properly categorize the entities.
Instead, it treats every entity with equal significance. This limitation is especially noticeable if you have a large
schema with lots of lookup tables. Seam-gen also falls short on rendering binary data properly in the user
interface because there is no way for seam-gen to know if that data represents an image, a document, or an
encrypted stream of data.
The point to take away from this section is that seam-gen eliminates all the tedious work that just wastes
time and that you don't want to do anyway. It sets you up with a foundation and turns the project over to you,
letting you rapidly develop and customize the application as you see fit. This chapter focuses on using seamgen as is, but I will mention to power users that you can modify the seam-gen templates to cater the generated
application to your needs.
INFO
The templates used by seam-gen can be found in the seam-gen folder of the Seam distribution. If you want
to work with the code generator in an iterative fashion, the best approach is to modify the templates to
preserve your customizations to the generated classes and views. Taking this approach allows you
customize the code without losing the ability to reverse engineer.
Whether you are in a pinch to create a prototype, or you are fortunate enough to develop an application
from scratch, seam-gen can be a great way to start. Do not assume, though, that seam-gen is for "green-fields"
projects only. You can certainly use it to generate code in a sandbox and merge that code back into an existing
application. In that regard, think of seam-gen as a tool in your toolbox that you can retrieve when needed. In
this chapter, we follow Gavin's advice and use seam-gen as the starting point for the example application. Let's
put seam-gen to the test.
Licensed to Jaroslaw Gilewski <[email protected]>
2.3 Kickoff your project with seam-gen
Starting an application from scratch is an extremely tedious process. You almost always want to start with
some sort of archetype. One approach is to create a new project in an IDE using a project template. This puts
you at the mercy of the IDE vendor, though.
Seam's answer to this call is seam-gen. Seam-gen can be used both from the command line and from the
IDE. The command line version is a wrapper around Ant and the IDE version is an Eclipse plugin that is part
of the RedHat Developer Studio. This section covers both interfaces, starting with the command line version.
However, the IDE version is not covered in as much detail here since user interfaces fall out of date too
quickly. You can find in-depth tutorials for using IDE-based seam-gen plugins on the book's online companion
resources which can be updated, as needed, after the book goes to press.
I begin by giving you an overview of the seam-gen command line script and then we will plunge into
executing the seam-gen commands.
2.3.1 A look at the seam-gen commands
You should have already downloaded the Seam distribution. If you haven't, please refer to appendix A, section
A.2. You should also have the example H2 database prepared as described in the section 2.1.2. Once you have
all of the prerequisites, change into the directory where you extracted Seam. You will always execute seam
commands from this folder8.
Let's begin by asking the seam script what it can do. In your console, navigate to the directory where you
extracted the Seam distribution and execute the command seam help. The output of this command gives a
brief description of the tool and a long list of all the commands it supports. The description is shown here:
seam (aka seam-gen) - Execute seam code generation. The seam.bat (Windows) and seam (Linux/Unix)
scripts support commands that use Ant build targets to setup a Seam project and generate source code. Ant
is not required to be on your path to use this script.
JBoss AS must be installed to deploy the project. If you use EJB3 components, the JBoss server must have
the EJB3 module installed. (JBoss AS >= 4.2.0.GA recommended)
The list of commands that the help command spits out can be divided into two categories. The first set of
commands, shown in table 2.1, are used to setup, manage, and deploy a seam-gen project.
Table 2.1 The setup, create, and and deployment commands that can be provided to the seam-gen script
Command
Description
setup
Generates the seam-gen/build.properties used to create projects. The key-value pairs are drawn from the responses
to the questionnaire that this target conducts. Information gathered includes the project directory, package names,
database connection information, and the location of JBoss AS. You can hand edit seam-gen/build.properties after
completing the questionnaire.
create-project
Create a Seam project complete with a build script, typical dependencies, and basic Seam component
configurations. Uses the values in seam-gen/build.properties to customize the project.
update-project
Update the generated project with the latest library dependencies.
delete-project
Undeploy and delete the generated project.
deploy
Deploy the project WAR and datasource to JBoss AS.
8
If you are using a Unix platform (Linux, Unix, Mac OSX), you need to make sure the seam script is executable. You also need to
prefix the seam command with dot-slash (./). See appendix A for more details.
Licensed to Jaroslaw Gilewski <[email protected]>
undeploy
Undeploy the project WAR and datasource.
explode
Deploy the project and datasource to JBoss AS as an exploded directory structure. Also performs incremental hot
redeployment of web artifacts and non-entity classes.
restart
Restart the project deployed as an exploded directory structure. Does not restart JBoss AS.
unexplode
Undeploy the exploded directory and datasource.
archive
Create a packaged archive (EAR or WAR) and put it in the dist folder at the root of the project.
clean
Remove all compiled files and staging directories in the generated project.
test
Run the tests in the generated project.
settings
View the current settings as defined in seam-gen/build.properties
reset
Remove the seam-gen/build.properties to start the process over from scratch.
The second set of commands, show in table 2.2, are used for generating code for a project that seam-gen is
currently managing.
Table 2.2 Code generation commands that can be provided to the seam-gen script
Command
Description
new-action
Create a new Java interface and Stateless Session Bean with key Seam/EJB3 annotations.
new-form
Create a new Java interface and Stateful Session Bean with key Seam/EJB3 annotations.
new-conversation
Create a new Java interface and Stateful Session Bean with key Seam/EJB3 annotations. Adds annotations and
stub methods for @Begin and @End. Also, creates a new TestNG test case that can be used to simulate a JSF
request/response and standard JUnit type tests.
new-entity
Create a new entity bean with key Seam/EJB3 annotations with example attributes.
new-query
Create a new class that extends EntityQuery to manage a custom JPA query and a view template to display the
results of the query.
generate
Generate JPA entity classes from an existing database schema and then a user interface to view and manage
them.
generate-ui
Generate a user interface to view and manage an existing set of JPA entity classes.
generate-model
Generate JPA entity classes from an existing database schema.
Don't worry right now about understanding each and every command. You are going to get an opportunity
to study most of the commands individually throughout the course of the book. These two tables are presented
to give you a feel for the capabilities of seam-gen before we take it into battle.
TIP
If you are super shell savvy, you can source the bash completion script in seam-gen/contrib/seamcompletion to get tab completion for the seam commands in Unix.
The steps that you are going to be taking to create the Open 18 prototype application are listed in order in
table 2.3.
Table 2.3 The steps to create and deploy a prototype application
Command
Purpose
1. seam setup
Enter information about the Open 18 prototype and the H2 database
2. seam create-project
Instruct seam-gen to create the open18 project
3. seam generate
Reverse engineer the Open 18 database to create a CRUD application to manage the tables
4. seam explode
Deploy the application to JBoss AS as an exploded Java EE archive
5. start JBoss AS
Start JBoss AS, which in turn starts the Open 18 application
Licensed to Jaroslaw Gilewski <[email protected]>
Once the steps in table 2.3 are complete, you will be ready to show the prototype to your boss. If he asks
for changes, you won't be at a loss for words. Customizing a seam-gen application is very straightforward.
Later in the chapter you learn how to get your hands dirty with the application by checking out the "instant
change" development environment that seam-gen sets up and deploying to multiple environments. Before you
can do any of that, you have to inform seam-gen about your project so that it knows what to created for you.
2.3.2 A Q&A session with seam-gen
Enough water-cooler talk. Let's get to work! You begin by running the seam setup command. When you
run this command, it launches a series of questions that allow seam-gen to gather the information it needs to
create your project. Each line consists of three parts: the question, the current value, and a list of valid
responses (if applicable). For each question, enter a response and hit <ENTER> to continue to the next
question. To create a working application, this is the only real work that you have to do. Seam-gen takes over
from there.
Listing 2.1 shows the full seam-gen setup questionnaire along with the responses used for creating the
Open 18 prototype in the WAR archive format. Anytime you see /home/twoputt in a response, replace it with
your development folder (according to the layout explained in Appendix A).
NOTE
If you are using a Windows OS, use the forward-slash (/) when entering file paths (e.g. C:/twoputt) in the
seam-gen interview, particularly when entering the path to the H2 database. The default file separator in
Windows is the backslash, which in Java is the escape character. For Java to accept a literal backslash, you
have to escape it with another backslash (\\). You can avoid this whole issue by always using forwardslashes in file paths, regardless of the operating system. Java is smart enough to convert the forward-slash
to the correct file separator for the host platform.
Let's start the interview.
Listing 2.1 Responding to the seam-gen setup questionnaire
[echo] Welcome to seam-gen :-)
[input] Enter your Java project workspace (the directory that
[CA]contains your Seam projects) [C:/Projects] [C:/Projects]
/home/twoputt/projects
[input] Enter your JBoss home directory [C:/Program
[CA]Files/jboss-4.2.2.GA] [C:/Program Files/jboss-4.2.2.GA]
/home/twoputt/opt/jboss-as-4.2.2.GA
[input] Enter the project name [myproject] [myproject]
open18
[echo] Accepted project name as: open18
[input] Do you want to use ICEFaces instead of RichFaces [n]
[CA](y, [n], )
y
[input] Select a RichFaces skin [blueSky] ([blueSky], classic, #1
[CA]ruby, wine, deepMarine, emeraldTown, sakura, DEFAULT)
emeraldTown
[input] Is this project deployed as an EAR (with EJB components)
[CA]or a WAR (with no EJB support) [ear] ([ear], war, )
#2
war
[input] Enter the Java package name for your session beans
#3
[CA][com.mydomain.open18] [com.mydomain.open18]
Licensed to Jaroslaw Gilewski <[email protected]>
org.open18.action
[input] Enter the Java package name for your entity beans
[CA][org.open18.action] [org.open18.action]
org.open18.model
[input] Enter the Java package name for your test cases
[CA][org.open18.action.test] [org.open18.action.test]
org.open18.test
[input] What kind of database are you using? [hsql] ([hsql],
[CA]mysql, oracle, postgres, mssql, db2, sybase, enterprisedb, h2)
h2
[input] Enter the Hibernate dialect for your database
[CA][org.hibernate.dialect.H2Dialect]
[CA][org.hibernate.dialect.H2Dialect]
<ENTER>
[input] Enter the filesystem path to the JDBC driver jar
[CA][lib/h2.jar] [lib/h2.jar]
/home/twoputt/lib/h2.jar
[input] Enter JDBC driver class for your database
[CA][org.h2.Driver] [org.h2.Driver]
<ENTER>
[input] Enter the JDBC URL for your database [jdbc:h2:.]
[CA][jdbc:h2:.]
jdbc:h2:file:/home/twoputt/databases/open18-db/h2
#4
[input] Enter database username [sa] [sa]
open18
#5
[input] Enter database password [] []
tiger
#5
[input] Enter the database schema name (it is OK to leave this
[CA]blank) [] []
PUBLIC
#6
[input] Enter the database catalog name (it is OK to leave this
[CA]blank) [] []
H2
#7
[input] Are you working with tables that already exist in the
[CA]database? [n] (y, [n], )
y
[input] Do you want to drop and recreate the database tables and
[CA]data in import.sql each time you deploy? [n] (y, [n], )
n
[propertyfile] Creating new property file:
[CA]/home/twoputt/opt/jboss-seam-2.0.1.GA/seam-gen/build.properties
[echo] Installing JDBC driver jar to JBoss server
[copy] Copying 1 file to
[CA]/home/twoputt/opt/jboss-as-4.2.2.GA/server/default/lib
[echo] Type 'seam create-project' to create the new project
BUILD SUCCESSFUL
<Annotation #1> Theme only applies to RichFaces
<Annotation #2> The two prominent Java EE archive types
<Annotation #3> Not necessarily EJB session beans
<Annotation #4> Location of extracted H2 database
<Annotation #5> Not the default H2 credentials
<Annotation #6> Precludes schema from being used in @Table annotation
<Annotation #7> Precludes catalog from being used in @Table annotation
Licensed to Jaroslaw Gilewski <[email protected]>
The goal of the seam setup command is to populate seam-gen/build.properties with the settings used to
create the seam-gen project, which is the next step. The build.properties file stores your responses in the
typical Java properties file format, using key and value pairs separated by an equals sign (=). If you mess up
any of the questions, don't fret. You can always run through the setup again. Seam will remember the answers
from your previous questionnaire, as long as you run it to completion. You can accept your previous response
for a question by hitting <ENTER>. If you would rather not use the setup wizard, you can just edit the seamgen/build.properties file directly. The wizard just makes the task more user-friendly.
This setup readies seam-gen to create a WAR project. You choose the WAR format if you only plan on
developing JavaBean components, as opposed to EJB3 components, and you want to take advantage of the hot
redeployable classloader. If you need to create EJB3 components, then the EAR format is the required choice.
You are going to learn about both of these archive formats, as well as hot redeployment, later in the chapter.
Choosing ICEFaces over RichFaces
ICEFaces and RichFaces are JSF component libraries for building rich, Web 2.0 user interfaces based
on Ajax. Seam-gen is capable of generating projects that uses either ICEFaces or RichFaces, made
possible by two equivalent sets of view templates that are maintained under the seam-gen folder of
the Seam distribution. Seam-gen selects the appropriate template folder based on your response in
the setup questionnaire and caters the application to that JSF component library.
If you choose ICEFaces, seam-gen automatically downloads the recommended version, thus requiring
no setup on your part. If you would like to have seam-gen use the version of your choosing, you specify
the root folder of the ICEFaces binary distribution when you are prompted for it in the seam-gen setup.
Being that RichFaces is a JBoss project, you can understand why RichFaces is the default choice in
seam-gen, but the ICEFaces contributors ensure that the generated application works equally well in
ICEFaces. I encourage you to give both a try and decide for yourself which one you prefer.
The seam-gen setup target performs two actions. It creates the file seam-gen/build.properties—or
overwrites it if it already exists—and copies the H2 JDBC driver, h2.jar, to ${jboss.home}/server/default/lib.
The JDBC driver is copied to JBoss AS so that a JCA datasource can be configured and registered in JNDI,
which is in turn used by the application to connect the JPA persistence unit to the database. You will learn
more about persistence units in chapter 9.
2.3.3 Creating a basic project structure
The setup target prepares seam-gen for transforming its templates into a customized project using the settings
in seam-gen/build.properties. To have Seam actually create a project, you must execute seam createproject. When you execute this command, Seam will create a new project in your Java workspace directory
and populate it with everything you need to start developing a Seam-based project, complete with an Antbased build for compiling, testing, packaging, and deploying the application. Listing 2.2 shows the output of
the create-project command. You will see a lot of other output fly by, which you can ignore.
Listing 2.2 Creating a new project with seam-gen
create-project:
[echo] A new Seam project named 'open18' was created in the
[CA]/home/twoputt/projects directory
[echo] Type 'seam explode' and go to http://localhost:8080/open18
[echo] Eclipse Users: Add the project into Eclipse using File >
[CA]New > Project and select General > Project (not Java Project)
[echo] NetBeans Users: Open the project in NetBeans
Licensed to Jaroslaw Gilewski <[email protected]>
BUILD SUCCESSFUL
As you can see in this output, seam-gen provides instructions to keep you moving right along. It told you
to run 'seam create-project' after running the setup command and 'seam explode' after running the createproject command. However, you will likely agree that setting up a project, particularly filling out the
questionnaire shown above, is best suited for a windowing environment. If you are turned off by the command
line interface, I have good news for you. The JBossTools plugin for Eclipse wraps these first two steps into the
Seam project creation wizard, which allows you to create a seam-gen project just like you would any other
Eclipse project. This wizard, a portion of which is shown in figure 2.5, provides a far more interactive
experience and lets you leverage existing servers, runtimes, and database connection profiles that you have
setup in Eclipse.
Figure 2.5 The Seam project creation wizard that is included with the JBossTools Eclipse plugin. This wizard performs the same work
as the seam-gen command line tool but offers a more interactive experience.
The JBossTools plugin will not be covered here. If you are interested in using it, you should be able to
graduate the foundation knowledge that you have learned in this chapter without any trouble. Be aware that the
project which the JBossTools wizard creates is not exactly the same as a project created by seam-gen. It is
designed to be used with an IDE (i.e. Eclipse) and only an IDE—meaning it doesn't have a build script. My
preference is to stick with the seam-gen tool because it allows me to use the generated project from the
command line or in the IDE of my choice. This flexibility will be demonstrated repeatedly throughout the rest
of the chapter.
2.3.4 Generating the CRUD
You now have a basic project scaffolding and it is ready to deployed. But, we are going to keep the code
generation going by doing some reverse engineering first. That is the function that the seam generate
Licensed to Jaroslaw Gilewski <[email protected]>
command 9 performs. This command generates JPA entity classes from an existing database schema as well as
a user interface, based on JSF action handler classes and Facelet view templates, to query entity instances and
perform create, read, update, and delete (CRUD) operations on them.
NOTE
By supplying values for the default schema and catalog in seam setup, it ensures that the @Table
annotation placed on the JPA entity classes remains agnostic to these volatile settings. The schema and the
catalog are more likely to change than the table and column names, so it's best not to tie your entity classes
to these values. The default schema and catalog can be set in the Hibernate or JPA configuration using the
properties hibernate.default_schema and hibernate.default_catalog. You may have to
play with these values to achieve an agnostic @Table annotation.
The output for the generate command is quite extensive as Hibernate feels the need to keep us informed
of its progress while doing its reverse engineering work. A truncated version of that output is is shown in
listing 2.3.
Listing 2.3 Reverse engineering the database to create entities and session beans
...
generate-model:
[echo] Reverse engineering database using JDBC driver
[CA]/home/twoputt/lib/h2.jar
[echo] project=/home/twoputt/projects/open18
[echo] model=org.open18.model
[hibernate] Executing Hibernate Tool with a JDBC Configuration (for
[CA]reverse engineering)
[hibernate] 1. task: hbm2java (Generates a set of .java files)
...
[hibernate] INFO: Hibernate Tools 3.2.0.CR1
[javaformatter] Java formatting of 8 files completed. Skipped 0
[CA]file(s).
generate-ui:
[echo] Building project 'open18' to generate views and controllers
...
[hibernate] Executing Hibernate Tool with a JPA Configuration
[hibernate] 1. task: generic exporter... view/list.xhtml.ftl
...
[hibernate] 2. task: generic exporter... view/view.xhtml.ftl
[hibernate] 3. task: generic exporter... view/view.page.xml.ftl
[hibernate] 4. task: generic exporter... view/edit.xhtml.ftl
[hibernate] 5. task: generic exporter... view/edit.page.xml.ftl
[hibernate] 6. task: generic exporter... src/EntityList.java.ftl
[hibernate] 7. task: generic exporter... view/list.page.xml.ftl
[hibernate] 8. task: generic exporter... src/EntityHome.java.ftl
[hibernate] 9. task: generic exporter... view/layout/menu.xhtml.ftl
[javaformatter] Java formatting of 15 files completed. Skipped 0
[CA]file(s).
[echo] Type 'seam restart' and go to http://localhost:8080/open18
9
Prior to Seam 2.0.1.GA, this command was named generate-entities. Both names are supported as of 2.0.1.GA.
Licensed to Jaroslaw Gilewski <[email protected]>
generate:
BUILD SUCCESSFUL
Believe it or not, that's it! The application is built. You might be wondering, though, why there is no
application running at the URL http://localhost:8080/open18, as promised by the seam restart command.
That's because you haven't deployed the application and started the JBoss server yet. In the next section, you
learn about the two options you have for deploying the application, how it affects development, and finally
how to start JBoss AS so that you can see the application that seam-gen has prepared for you.
2.4 Deploying the project to JBoss AS
As I mentioned earlier, seam-gen produced projects that are configured to deploy to JBoss AS out of the box.
For now, we are going to stick with the convention to keep the effort of getting the application running to a
minimum. Once we are comfortable, alternatives can be explored.
There are two paths that you can take when deploying your application to JBoss AS, which are illustrated
in figure 2.6. You can either deploy it as a packaged archive, using seam deploy, or you can deploy it as an
exploded archive, using seam explode.
Figure 2.6 The two deployment scenarios offered by seam-gen. On the left, an exploded war (or ear) directory. On the right, a
packaged war (or ear) file. The packaged format does not allow incremental changes to be applied.
NOTE
Despite its alarming name, the explode command will not perform a destructive process on your project.
It simply deploys the files to the application server without packaging them. The name is meant to indicate
that the archive is broken open as if it had been "exploded". A more reassuring name for this command
might have been "unfurl" (like a sail). But explode just sort of stuck.
Let's weigh the attributes of each command and decide which one is best to use.
Licensed to Jaroslaw Gilewski <[email protected]>
2.4.1 To deploy...
If you use the deploy command, the build will create an archive that uses one of two standard Java EE
package formats, Web Archive (WAR) or Enterprise Archive (EAR), depending on which one you choose
during the seam-gen setup. We chose the WAR format for Open 18 since we plan to use only JavaBean
components. In this case, the deploy command packages the prototype application as a WAR file, named
open18.war. If you intend to use EJB components in your application, then you must use the EAR format. In
that case, the deploy command will package the application as an EAR file, open18.ear. The sample code that
accompanies the book includes projects for both archive formats. Note, however, that once you make a choice
between WAR and EAR, you are stuck with it. There is no built-in command in seam-gen created projects to
toggle between archive formats.
Once the archive has been packaged, it is moved to the deployment directory in the default domain of the
JBoss server. The location of deployment directory for that domain is ${jboss.home}/server/default/deploy.
The JBoss server monitors the deployment directory and deploys any new or changed archives that it detects.
This feature is known as "hot deploy". But it's not just about deployment. The server also recognizes when the
archive has been removed and will subsequently undeploy the application. You can remove the deployed
archive by running seam undeploy.
Packaging Java EE applications
There are three prominent archive types that can be used to deploy components to a Java EEcompliant application server. A Web ARchive (WAR) is the format with which most people are familiar.
A WAR bundles servlets, JSPs, static resources, and supporting classes for the purpose of serving
dynamic web pages. EJBs are deployed in the pervasive Java ARchive (JAR) format, except that they
are augmented with a special XML deployment descriptor that identifies them as such. Both the WAR
and JAR formats are Java EE modules that can be bundled inside an Enterprise ARchive (EAR), which
deploys the containing modules under the same classloader, presenting them as a single application.
The EAR is the standard packaging unit for a Java EE application. Basic servlet containers, such as
Tomcat, can only handle WAR packages.
The fourth archive type is the Resource adapter ARchive (RAR), but given that most developers rarely
deal with them, it is not discussed here.
The downside to using a packaged archive is that every change that you want to have take effect requires a
complete build, package, and deploy cycle. At the end of this cycle, a fresh version of the archive is shipped
off to the server, the old application is unloaded, and finally, the new application is loaded. This process is not
capable of supporting incremental updates. It's all or nothing.
When a new archive appears in the server's hot deployment directory, the application is "reloaded". During
a reload, the JBoss server shuts down all the services that were started by your application. These services
include the database connection, the JPA EntityManagerFactory (or Hibernate SessionFactory), the
Seam container, and perhaps others. When the new application is deployed, all of these services are started
again under the new deployment. This process can be very time consuming. And the time it burns increases as
your project grows and more services have to be stopped and started.
To make matters worse, reloading an application terminates all of its active HTTP sessions. If the page
you are testing requires you to be authenticated, or otherwise holds state, you will be forced to start over from
the beginning. Needless to say, the packaged deployment is not recommended for development. The good
news is that there is a better way.
Licensed to Jaroslaw Gilewski <[email protected]>
2.4.2 ...or to explode
The alternative to a packaged deployment is an exploded archive. An exploded archive is a WAR or EAR
archive that has been extracted into a directory that has the same name as the archive. You can deploy the
application as an exploded archive using the seam explode command.
The explode command is a slight variation on the deploy command. The deploy command assembles
the deployable files in a build directory according the Java EE package format. It then bundles the contents of
the directory as an archive and copies it to the server. The explode command skips the bundling step and just
copies the build directory in its raw form to the server. Subsequent calls to seam explode synchronize the
changed files to the exploded archive directory on the server, thus supporting incremental updates. How the
application deals with the changes is determined by the hot deployment capabilities of both. You are going to
learn about incremental hot deployment, and what it means in terms of development, in section 2.5.1.
To force a reload of the application when using an exploded archive deployment, mimicking the behavior
that occurs when a package archive is dropped into the hot deployment directory of JBoss AS, you use the
restart command. Despite its name, this command does not restart the JBoss AS. It just reloads the
application.
The restart command begins by mirroring the work done by the explode command. As a final step, it
updates the timestamp of application deployment descriptor, which is application.xml, in the case of an EAR
deployment, or web.xml, in the case of a WAR deployment. When the application server detects that the
timestamp of the application deployment descriptor has changed, it triggers a reload. To remove an exploded
archive, and have it undeployed, you execute seam unexplode.
The explode command is best suited for development. With support for incremental updates, it gives you
the "instant change" capabilities that you may be familiar with if you have ever developed in a scripting
language like PHP. The degree to which a Seam application supports "instant change" will be put to the test
later.
To summarize your options, you can use the deploy command to deploy a package archive or you can
use the explode command to deploy an exploded archive. The undeploy and unexplode commands serve
as their complements by removing the archive from the server. That covers the deployment life cycle to a
single environment. Seam-gen sets up your project with additional deployment profiles, which allow you to
tune the archive for a particular environment, possibly even deploying it to a different server instance. We are
going to take one more tangent to learn about deployment profiles before we move on to starting JBoss AS.
2.4.3 Switching between environments
The build that seam-gen creates for your project supports the concept of deployment profiles. Profiles can be
used to customize deployment descriptors and configuration settings for different environments. To cite a
common case, you may need to use a different database in production than what is used in development. You
certainly don't want to be burdened with having to switch the database connection settings every time you cut a
production release. Instead, you can set up a profile for that environment, which you can activate with a single
flag when executing the production build. The appropriate database settings, and any other settings specific to
that environment, will then be applied.
Seam configures three profiles out of the box: dev, prod, and test. The test profile is a special case, which
we will look at next. By default, Seam uses the dev profile when commands are executed. You can enable a
different profile by changing the value of the Ant profile property. Adding the following key-value pair to
the build.properties file in the root of the project activates the prod profile, effectively deactivating the dev
profile:
profile=prod
Licensed to Jaroslaw Gilewski <[email protected]>
Having to change the build.properties file feels too manual for my taste. As another option, you can set the
profile property from the seam command:
seam clean deploy -Dprofile=prod
TIP
When you switch profiles, it is a good idea to run clean—and possibly even undeploy or
unexplode—prior to running deploy or explode to be sure that settings from the previous profile are
not left behind.
As I mentioned, the test profile is handled as a special case. It is not activated using the profile property.
Instead, there are special Ant targets for running tests that know to use the test versions of the environmentspecific files. Writing and executing tests will be covered in chapter 4.
Table 2.4 itemizes the files that are selected according to the active profile, indicating whether the file is
used in the case of the test profile. These files contain settings and configurations that you have control over
between environments. You can introduce your own profile, such as qa, by creating each of the files listed in
table 2.4, replacing %PROFILE%, a token for the profile name, with the profile name. For instance, the build
properties file for qa would be build-qa.properties.
Table 2.4 Files that are selected based on the profile property value
File selected based on profile value
Purpose of file
Used in test profile?
build-%PROFILE%.properties
Used to set Ant build properties, such as the
location of the JBoss AS deploy directory and
the debug mode flag.
no
resources/META-INF/persistence-%PROFILE%-war.xml
The JPA persistent unit configuration file.
yes
resources/import-%PROFILE%.sql
A SQL script that will be used to load seed
data into the database when the application
starts if the database is being recreated each
time.
yes
resources/open18-%PROFILE%-ds.xml
The data source configuration file for JBoss
AS.
no
If you want to change the location of the JBoss AS deploy directory for a production build, you can set the
jboss.home property in the build-prod.properties. You probably want to disable debug mode as well, since it
is only desirable in development:
jboss.home=/opt/jboss-production
debug=false
You now know how to deploy and undeploy the application to the JBoss AS deployment directory, both as
a packaged archive and an exploded directory structure. You can also control the settings and configurations
for the target environment, such as development, QA, or production, by toggling the value of the profile
property. Without further ado, let's fire up JBoss AS and see what the application looks like.
2.4.4 Launching JBoss AS
Without any customization, JBoss AS runs on port 8080, so first ensure this port is not already in use (Apache
Tomcat also runs on port 8080). In your console, navigate to the JBoss AS installation directory,
${jboss.home}, and then descend into the bin directory. Execute ./run.sh if you are using a Unix platform
Licensed to Jaroslaw Gilewski <[email protected]>
or run.bat if you are on Windows. These scripts start the default domain of the JBoss AS server. As an
alternative to using the console, you may also choose to launch JBoss AS from your IDE. Eclipse, NetBeans
and IntelliJ IDEA all support launching the JBoss Application Server.
Keep an eye on the console while the server is starting to watch for any exceptions that may occur. When
the console output settles down, the final line should give you the thumbs up that JBoss AS is running:
00:00:00,426 INFO [Server] JBoss (MX MicroKernel) [4.2.2.GA (build:
[CA]SVNTag=JBoss_4_2_2_GA date=200710221139)] Started in 17s:14ms
If you are using the Sun JVM, I can almost guarantee that when you start hot deploying applications to
JBoss AS, you are going to get out of memory errors using the default JVM options. I strongly urge you to
follow the advice in the sidebar that discusses the Sun JVM options. These settings can be applied to the JBoss
AS runtime configuration either by adding them to the ${jboss.home}/bin/run.conf or by using the
configuration screen in the IDE.
Sun JVM options when running JBoss AS
While you are on the task of setting up the JBoss AS runtime, I recommend a set of JVM options to use
when running JBoss AS with the Sun JVM. The default memory allocation settings in Java are
extremely conservative. On top of that, Sun's JVM has a concept of PermGen space, a separate
allocated memory region from the heap10. Even though the JVM garbage collector frees up memory
automatically, there are certain objects (such as class and method objects) that can evade removal
because they are in this isolated memory region. A flag must be provided that enables garbage
collection for the PermGen space. In addition, the garbage collector has trouble keeping up when
memory is tight. The limited resources quickly become a problem when running a hefty application like
JBoss AS that has high memory demands.
Allen_MEAP.doc
To avoid an untimely death of the JVM, and in turn, JBoss AS, you should supply the following
parameters in the VM options of the JBoss AS runtime configuration11. I have never experienced an
out of memory error when using these settings.
-Xms128m
-Xmx512m
-Dsun.rmi.dgc.client.gcInterval=3600000
Dsun.rmi.dgc.server.gcInterval=3600000
-XX:+UseConcMarkSweepGC
XX:+CMSPermGenSweepingEnabled
-XX:+CMSClassUnloadingEnabled
XX:MaxPermSize=512m -Xverify:none
-
There are ramifications of using the Concurrent Mark Sweep (CMS) garbage collector, such as a longer
startup time, so I wouldn't blindly apply them to production. However, I have found them to be
sufficient for development purposes. The other option is to switch to the IBM JVM, which does not
experience this problem.
If JBoss AS started cleanly, you can open your browser and point it to http://localhost:8080/open18 to see
the result of your work. In the next section, I walk you through the application and point out the word seamgen has done for you. I then demonstrate how you can continuously make changes to the application without
having to suffer the wait of restarting either the application or the server.
10
For more information on the design of the garbage collection mechanism in Sun's JVM, please see
http://java.sun.com/javase/technologies/hotspot/gc/gc_tuning_6.html or a more abbreviated list at
http://performance.netbeans.org/howto/jvmswitches/index.html
11
If you would like to know more about these settings and why they were chosen, please see
http://my.opera.com/karmazilla/blog/2007/03/13/good-riddance-permgen-outofmemoryerror
Licensed to Jaroslaw Gilewski <[email protected]>
2.5 Show and tell, change, and repeat
You might recall the activity of "show in tell" from your school days. In today's "show an tell", I will be
demonstrating the Open 18 prototype application that you have created thus far using seam-gen, both from
outside and the inside. This exercise is important because it's necessary to know how seam-gen assembled the
application so that you are familiar with how to make modifications to it. In this section, you learn how you
can alter the application by controlling the reverse engineering process. In the next section, you learn how to
make changes after the fact. Before we get to that, it's time for the unveiling.
Tada! The opening screen of the Open 18 application is shown in figure 2.7.
Figure 2.7 The splash page of an application created by seam-gen. The links in the upper menu bar represent each of the entities that
seam-gen extracted from the database schema.
I'll admit, the home page isn't all that interesting. At the very least, it's clear that the application has a nice
look and feel, which is typically rare for starter applications. The folks on the floor of the trade show pavilion
probably don't care to read about the benefits of seam-gen, though. In fact, you already have an e-mail in your
Inbox from the marketing team that includes something flashier to use on this page. You will learn how to
replace the contents soon enough.
At the top of the page, there are links for each of the entities in the data model and one to authenticate.
Clearly there is some substance behind this curtain. Let's dig deeper to find out what's inside.
2.5.1 Walking the course
From the top level menu in figure 2.7, you can verify that seam-gen produced five entities: Facility, Course,
Hole, TeeSet, and Tee. The List suffix on each link indicates that these pages are listings. We will focus on
these pages first and then see where else they take us.
Entity listings
Clicking on one of the entity links at the top of the page brings up the listing for that entity. In this walk
through, we are going to focus on the Course listing, shown in figure 2.8. However, the tour I am about to
give applies to the other entities as well.
Licensed to Jaroslaw Gilewski <[email protected]>
Figure 2.8 The Course List screen as created by seam-gen. This page includes a collapsible search form in the top half and a
pageable, sortable result set of courses in the bottom half.
The listing screen has two parts. The featured content is a table of all the courses found in the database,
seen in the bottom half of the screen. This table has two key features. It can be sorted and paginated. The result
set is broken into pages, truncated at a page size of 25 by default. There are links at the bottom of the screen
that allow you to flip through the pages in the result set. The columns of the table are also sortable. The
heading of each column is rendered as a link, allowing you to sort the table by that column. Now, tell me, how
much time have you burned implementing sortable and paginated tables in an application in your career? If
you winced at that question, then you should be happy to see that seam-gen takes care of all that for you. So go
ahead and revel in it by clicking on a couple of the column headers and flipping through the pages. You should
notice that the sort is not lost when you paginate, and the offset is not lost when you sort. Another seam-gen
special.
The page also includes a search feature. The search form, shown in the top half of figure 2.8, includes an
input field for every string property of the Course entity class. You can type text in any of the fields that
appear above the table and Seam will use that text to perform a search to filter the rows displayed in the table.
The best part is that when you sort and paginate, your search filter is not reset.
From the course list page you can either create a course or you can view the details of a particular course.
Let's drill down to see the details of one of the courses.
Drill downs
In the last column of the courses table there is a link to view the course. When you click on this link, it brings
up a page showing the details of the course, shown in figure 2.9.
Licensed to Jaroslaw Gilewski <[email protected]>
Figure 2.9 The course detail screen as generated by seam-gen. This page displays all the data for a given course and also uses a
tabbed pane to show the associated facility, tee sets, and holes.
The course detail screen shows all the properties of the course at the top of the screen and each of the
course's related entities at the bottom. Since each course has eighteen holes, there are eighteen rows in the table
in the holes tab pane. The teeSets tab pane shows a table with rows for each set of tees (the colored markers at
the start of each hole). Each course is also associated with a golf facility, the details of which are shown in the
facility tab pane. You can continue to drill down further by clicking on the Select link in the row of any related
entity. But application really gets interesting when you add a new record or edit an existing one.
Entity editors
There are buttons for both editing and adding entities. However, pages that alter the state of the database
require you to be logged in. When you click on one of these buttons, you will be directed to the login screen,
shown in figure 2.10. The login requirement is enforced in the page descriptor that coincides with the JSF
page. For the Course entity, the editor page is CourseEdit.xhtml and its page descriptor is
CourseEdit.page.xml. You will learn about page descriptors in chapter 5.
Licensed to Jaroslaw Gilewski <[email protected]>
Figure 2.10 The login screen of an application created by seam-gen. The authentication provider is just a stub, so any
username/password combination is accepted.
Any credentials will do since the authentication module is just a stub. You will learn how to tie
authentication to the database and how pages are locked down in chapter 12.
Once you authenticate, you will be taken to the course editor, shown in figure 2.11. If you are editing an
existing entity, Seam will remember which record you selected so that it can update it accordingly when you
press Save. Otherwise, a new record will be inserted into the database.
Figure 2.11 The course editor screen as generated by seam-gen. Not only does this form allow you to edit the basic properties of a
course, but it also allows you to associate it with a facility.
Licensed to Jaroslaw Gilewski <[email protected]>
At first glance, the forms for editing an entity appear straightforward, perhaps even a bit dull. A closer
look reveals that they are enriched with subtle features that would be very time consuming to prepare. Figure
2.11 reveals that there are several marked as required. At the bottom half of the page, there is a button to select
a facility to satisfy the foreign key relationship. If you delete a record, it will cascade, deleting child records as
well. All of these features are a direct reflection of the database constraints. Let's take a closure look at how the
column constraints are translated into form requirements, which happens when seam-gen reverse engineers the
database schema.
Database column to form field
The generate seam-gen command creates JPA entity classes by reverse engineering the database and then
uses those entity classes to build the CRUD pages. This command is just a wrapper around the Hibernate
reverse engineering tool, which performs the database inspection and code generation.
The database tables are translated into entity classes and the columns become properties on the respective
classes. Each entity gets its own set of CRUD web pages, as you have seen already. The properties on the
entity class manifests as form fields in the entity's editor. To get from the database to the UI, the database
column names are converted into entity property names, which because the form field labels in the UI.
Seam-gen is fairly intelligent about creating form fields that adapt to the type accepted by the
corresponding database columns. To start, the Java type chosen for each property is derived from the SQL type
of the corresponding column. If the column type is numeric, then the editor will only accept numbers in the
input field. Even better, the validation is performed in real time using Ajax. If you try entering letters into the
yearBuilt field on the course editor, then click somewhere else on the page, you will see that you get a
validation error, as shown in figure 2.12. You will learn how configure the form field to enforce real time
validations in the next chapter.
Figure 2.12 The course editor enforcing a number value for the yearBuilt field. Validations occur in real time using Ajax.
Seam-gen also picks up on non-nullable column constraints in the database and enforces them as required
fields in the editor as indicated by the star (*) after the field label in figure 2.11. Where appropriate, the
property on the entity class is defined using a primitive type in this case. If a primitive type cannot be used,
such as when the column type is a string, then the @NotNull annotation from the Hibernate Validator library
is added to the property on the entity class. If the column allows null values, then a nullable Java type is used
for the property and the field is not required in the UI. There are a number of other Hibernate Validator
annotations that seam-gen applies to the entity classes and in turn enforces in real time in the UI. One such
example is maximum character length, defined using the @Length annotation and reflecting the constraint on
the database column.
Seam-gen can also identify foreign key relationships between tables and use them to establish associations
between the corresponding entities. One the one hand, these relationships are used to display child collections
on the detail screens, such as the set of holes associated with a course. The entity editor supports the selection
of a parent entity when adding or updating a record. For instance, the Course entity has a foreign key
relationship to the Facility entity. At the bottom of the editor screen is a tab pane which shows the current
facility selection—if one has been selected—and provides a button to allow you to select a different facility.
Clicking on this button takes you to the facility listing page. In the table of facilities, the action in the last
column has changed from view to Select. Clicking on one of these links takes you back to the course editor,
which now reflects your selection. Being able to satisfy the entity relationships in the editor is critical. Without
this feature, the edit functionality would be severely crippled.
Licensed to Jaroslaw Gilewski <[email protected]>
I don't know about you, but I am pretty impressed. This application satisfies just about every requirement
for the golf course directory and you have hardly touched a line of code. With all of the extra time, you may be
tempted to start daydreaming about your golf weekend again or head out for one last round at the range. Well,
don't head out just yet. You have some work to do to clean up the application so that it is presentable. The
reverse engineering does a pretty good job, but it isn't always perfect. To take the application the last mile, you
are going to customize seam-gen so that it produces an application that is ready for the sales team to start
gawking over.
2.5.2 Guiding the reverse engineering process
Hibernate's reverse engineering tool is very capable at interpreting what the database schema has to say about
the entity relationships. But, it cannot read what is not there and it cannot do much about poorly named
columns. For example, the property for men's par on the Hole entity is mPar, which is not very clear. The
relative position of a tee set is represented by the pos property on the TeeSet entity, the name again lacking
clarity. These names are a reflection of abbreviated column names. As the saying goes, "garbage in, garbage
out." What's worse is the situation where the information isn't in the database at all. If a foreign key is missing
in the table, seam-gen cannot make the leap that the table is related to another. Thus, the corresponding entities
won't be linked and that means you won't get any of the support in the UI that you observed during the walk
through.
Both of the shortcomings just cited can be corrected by tuning the Hibernate reverse engineering
configuration. Despite its long, foreboding name, the configuration is quite straightforward. Its purpose is to
tweak the decisions made by seam-gen when generating the entity classes. These adjustments then affect all
the downstream UI code generation. Using the reverse engineering configuration, you can alter any of the
following settings:
z
customize a property name on an entity class
z
customize the Java type of a property on an entity class
z
change the mapping between SQL column type and Java property type globally
z
exclude tables from participating in code generation
z
establish an entity relationship that isn't represented by a foreign key
z
exclude the reverse mapping of an entity relationship
z
enable generation of the toString() and hashCode() methods
z
add extra code and imports to the generated class
Let's apply customization to clean up the prototype application. We need to fix the problematic property
names mentioned above, as well as several others. We will also add convenience methods that will be needed
later on. The reverse engineering configuration file that seam-gen uses is resources/seam-gen.reveng.xml
inside the generated project. Listing 2.4 shows the contents of this file populated with the customizations just
mentioned.
Listing 2.4 Customization of seam-gen code generation to alter property names and add code
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-reverse-engineering SYSTEM
"http://hibernate.sourceforge.net/
[CA]hibernate-reverse-engineering-3.0.dtd">
<hibernate-reverse-engineering>
<table name="HOLE">
<column name="M_PAR" property="mensPar"/>
<column name="L_PAR" property="ladiesPar"/>
Licensed to Jaroslaw Gilewski <[email protected]>
#1
#2
<column name="M_HANDICAP" property="mensHandicap"/>
<column name="L_HANDICAP" property="ladiesHandicap"/>
</table>
<table name="TEE_SET">
<meta attribute="extra-import">
javax.persistence.Transient
</meta>
<meta attribute="class-code">
@Transient
public int getFrontNineDistance() {
int distance = 0;
for (Tee tee : tees) {
if (tee.getHole().getNumber() &lt;= 9) {
distance += tee.getDistance();
}
}
return distance;
}
#4
#3
@Transient
public int getBackNineDistance() {
int distance = 0;
for ( Tee tee : tees ) {
if ( tee.getHole().getNumber() > 9 ) {
distance += tee.getDistance();
}
}
return distance;
}
</meta>
<column name="POS" property="position"/>
<column name="M_SLOPE_RATING" property="mensSlopeRating"/>
<column name="M_COURSE_RATING" property="mensCourseRating"/>
<column name="L_SLOPE_RATING" property="ladiesSlopeRating"/>
<column name="L_COURSE_RATING" property="ladiesCourseRating"/>
</table>
</hibernate-reverse-engineering>
<Annotation #1> Customizations for HOLE table
<Annotation #2> Sets property name that maps to M_PAR column
<Annotation #3> Extra code appended to the end of TeeSet class
<Annotation #4> Add import needed for extra class code
When you are done customizing, you can run seam generate restart to apply the changes. Please
note that running generate will clobber any changes you previously made to the generated files. Thus, you
need to be cautious about enhancing the application if you plan on executing the reverse engineering again.
One way to avoid losing the customizations to entity classes is to define the extra class code in the reverse
engineering configuration, as shown in listing 2.4. You also have the option of tweaking the seam-gen
templates to achieve the output you desire.
Instead of running the code generation full scale, you can split the code generatation into two steps. As of
Seam 2.0.1.GA, the generate command is actually a combination of the generate-model and
generate-ui commands. You can run generate-model to incrementally develop the JPA entity classes
and then run generate-ui once you have tweaked the domain model to your liking. You can even skip
Licensed to Jaroslaw Gilewski <[email protected]>
generate-model altogether and simply use generate-ui to build a CRUD user interface from an existing
set of JPA entity classes 12 that reside under the src/model tree.
Quite often, you will find yourself having to reverse engineer a much larger, perhaps more loosely defined
database then the one prepared for the Open 18 prototype application. Faced with this task, you may encounter
the need to specify a name for the generated entity class or indicate a foreign key relationship explicitly. These
two changes can be made using additional elements within the <table> node. You may find the need to
exclude portions of the schema that contain secondary or temporary tables. You may even want to focus the
CRUD application on just a handful of tables. The reverse engineering tool offers table filters to satisfy these
requirement, specified using the <table-filter> element. The <table-filter> accepts a schema name
and a table name, which it uses in a LIKE clause to locate tables in the database. You can use the string .* in
the name, which will be replaced with % when the lookup is executed to allow for a fuzzy search. The
configuration in listing 2.5 presents an example of how to work around the challenges just described for a
hypothetical golf equipment database.
Listing 2.5 Customization of seam-gen code generation to filter tables and establish relationships
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-reverse-engineering SYSTEM
"http://hibernate.sourceforge.net/
[CA]hibernate-reverse-engineering-3.0.dtd">
<hibernate-reverse-engineering>
<table-filter
match-schema="EQUIPMENT" match-name="PRDCT" exclude="false"/>
<table-filter
match-schema="EQUIPMENT" match-name="MFR" exclude="false"/>
<table-filter
match-schema="EQUIPMENT" match-name="EQ_TYP" exclude="false"/>
#1
<table name="EQ_TYP" class="org.open18.model.EquipmentType"/>
<table name="PRDCT" class="org.open18.model.Product">
<foreign-key foreign-table="MFR">
<column-ref local-column="MFR_ID" foreign-column="ID"/>
</foreign-key>
<foreign-key foreign-table="EQ_TYP">
<column-ref local-column="EQ_TYP_ID" foreign-column="ID"/>
</foreign-key>
</table>
<table name="MFR" class="org.open18.model.Manufacturer"/>
</hibernate-reverse-engineering>
<Annotation #1> Including implies excluding all tables first
The goal of this section was not to cover the reverse engineering exhaustively, but to introduce you to it
and inform you of the fact that it can help you fine tune your application. There are a handful of features that
were not highlighted here, so I encourage you to do some more digging, starting with the reference
12
If you are working without an existing database schema, you can set the Hibernate flag hibernate.hbm2ddl.auto to create-drop,
create, or update to have the database schema auto-generated when the application starts.
Licensed to Jaroslaw Gilewski <[email protected]>
documentation13. Even then, there are still some areas where the reverse engineering comes up short handling
advanced mappings such as embeddable entities and Enum properties.
If you are still not satisfied with the classes that the reverse engineering tool creates, you can further
customize them by modifying the FreeMarker templates used to construct them. Hibernate includes an entire
reverse engineering API that you can tap into during this process. The templates it uses reside in the seamgen/pojo directory. Actually, what you will find there are only the customizations put in place by the Seam
developers. Hibernate falls back on its default template if it is not present in this directory. Again, consult the
reference documentation for more information.
Once you are satisfied with the entity model that seam-gen creates, you are ready to move into the
development phase. Being that you don't know anything about the structure of the project that seam-gen has
created, you may feel uneasy about this task. Rest assured that seam-gen has prepared a well-organized source
tree for you. I will now take you under the covers and help you get acquainted with that structure just as I
showed you around the application itself.
2.5.3 Exploring the generated project
One of the difficult parts of starting with a project generation tool is becoming familiar with the layout that it
leaves behind. A period of adjustment is to be expected. It's akin to inheriting someone else's code. In this
section, you explore the code that seam-gen leaves you with and how the generated project can stand on its
own without seam-gen's reigns.
Project layout
The layout of a seam-gen project is not what you might consider standard, nor does it follow the popular
Maven 2 structure. However, the structure does strike a nice balance between the multiple masters that it
serves. It is capable of:
z
building from the command line
z
hooking into the IDE build cycle
z
running integration tests in an embedded Java EE environment
z
incremental hot deployment to the application server
Listing 2.8 shows the structure of the seam-gen WAR project created in this chapter. The contents of
folders prefixed with a plus (+) symbol are hidden either because the contents are evident by the folder name
or not important to understand right now. In some places, tokens are used to reduce the output, such as
<lang> and <entity>, which represent an iteration of supported language codes and entity names,
respectively. This is just an introduction to the WAR structure, don't get caught up in trying to understand
every file and folder.
Listing 2.8 The structure of a seam-gen WAR project
open18/
|-+ bootstrap/
|-+ exploded-archives/
|-+ lib/
|-+ nbproject/
|-- resources/
|
|-- META-INF/
|
|
|-- persistence-<profile>-war.xml
|
|
|-- persistence-test-war.xml
13
#1
#2
#3
#4
#5
#5
http://www.hibernate.org/hib_docs/tools/reference/en/html/reverseengineering.html
Licensed to Jaroslaw Gilewski <[email protected]>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|-|
|
|
|-|
|
|
|
|
|
|
|
|
|
|
|
|-|-|-|-|-|-|-`--
|-- WEB-INF/
|
|-- components.xml
|
|-- faces-config.xml
|
|-- jboss-web.xml
|
|-- pages.xml
|
`-- web.xml
|-- components.properties
|-- import-<profile>.sql
|-- import-test.sql
|-- messages_<lang>.properties
|-- open18-<profile>-ds.xml
|-- seam-gen.reveng.xml
|-- seam.properties
`-- security.drl
src/
|-+ action/
|-+ model/
`-+ test/
view/
|-+ img/
|-+ layout/
|-+ stylesheet/
|-- <entity>.page.xml
|-- <entity>.xhml
|-- <entity>List.page.xml
|-- <entity>List.xhml
|-- error.xhtml
|-- home.xhtml
|-- index.html
|-- login.page.xml
`-- login.xhtml
.classpath
.project
build-<profile>.properties
build.properties
build.xml
explode.launch
hibernate-console.properties
open18.launch
#6
#7
#8
#9
#9
#10
#11
#12
#13
#14
#15
#15
#16
#16
#17
#17
#18
#19
#20
<Annotation #1> Embedded JBoss configuration for tests
<Annotation #2> Exploded Java EE package structure
<Annotation #3> JAR libraries on classpath; Seam sources included
<Annotation #4> NetBeans project files
<Annotation #5> JPA persistence unit configurations
<Annotation #6> Seam component descriptor
<Annotation #7> Seam page descriptor
<Annotation #8> Tokenized properties
<Annotation #9> Database seed data
<Annotation #10> Internationalization messages
<Annotation #11> Database connection information
<Annotation #12> Component configuration properties
<Annotation #13> Classes that can be hot redeployed
<Annotation #14> Classes that cannot be hot redeployed
<Annotation #15> Entity detail template and navigation configuration
<Annotation #16> Entity listing template and navigation configuration
<Annotation #17> Eclipse project files
<Annotation #18> Profile-specific build properties
<Annotation #19> Global build properties
<Annotation #20> Ant build file
Licensed to Jaroslaw Gilewski <[email protected]>
I want to draw your attention to the persistence-<profile>-war.xml and open18-<profile>-ds.xml file sets,
because they will be most relevant in the early stages of working with the project. There is an instance of each
file per deployment profile. At build time, the persistence-<profile>.xml is renamed to persistence.xml and
moved to the classpath of the application. This file is the JPA persistence unit descriptor and hosts various
Hibernate settings, given that Hibernate is the default JPA provider used in seam-gen projects. You will learn
about Java Persistence in chapters 8 and 9. The open18-<profile>-ds.xml is renamed to open18-ds.xml and
moved to the JBoss AS deployment folder. This file is the JCA DataSource descriptor and is where the
database connection profiles for the various deployment environments are specified.
An EAR project differs from WAR structure by only a couple of files. The difference primarily concerns
packaging, which is not evident by looking at the project structure alone. In an EAR project, the
resources/WEB-INF/jboss-web.xml is absent and the following three files appear, which are specific to an
EAR archive:
resources/
|-- META-INF/
|
|-- application.xml
|
|-- ejb-jar.xml
|
|-- jboss-app.xml
Although the WAR and EAR structures are nearly identical, the build process used to create them varies
widely. Once you choose a deployment format for a seam-gen project, you are pretty much stuck with it.
Switching from a WAR build to an EAR build, and vice versa, is not trivial.
Bootstrapping the database
Before moving on, I want to mention an important Hibernate configuration flag. The Hibernate configuration
properties are specified in the persistence-<profile>-war.xml files. The most important setting is
hibernate.hbm2ddl.auto, which controls whether Hibernate will attempt to create or modify the
database schema when initialized. The default value is validate. Table 2.6 shows all the possible values for
this flag. If the database is slated to be created when Hibernate initializes, the seed data in the import.sql at the
root of the classpath is loaded into the database automatically. The import.sql file is derived from the import<profile>.sql file according to the build profile. The Open 18 application works with an existing database. To
support ongoing development, the best choice in this scenario is update.
Table 2.6 Possible values for hibernate.hbm2ddl.auto
Value
Affect on startup
Affect on shutdown
Imports seed data from
import.sql?
create
creates database (destroying it if it exists)
none
yes
create-drop
creates database (destroying it if it exists)
destroys database
yes
update
updates database to reflect changes to mappings
none
no
validate
validates database from mappings
none
no
none
none
none
no
Getting to know the project structure is going to take some time. But don't worry. You have the whole
book to become more familiar with how to use and enhance a seam-gen project. Regardless of whether you use
an EAR project or a WAR project, the project is built and deployed the same way. That brings us to the next
point, how seam-gen projects are built.
Licensed to Jaroslaw Gilewski <[email protected]>
Ant build targets
Ant has been mentioned throughout this chapter, but I haven't provided definitive information on how it is
being used. The seam command operates using Ant. Projects that seam-gen creates are also built using Ant.
Once the project is generated, seam-gen remotely controls the project's build script to compile, test, and deploy
the application. There are two Ant scripts at work here, the one used by the seam-gen script and the one in the
root of the project directory.
Project-specific seam-gen commands are passed down to the project's Ant build where they are executed,
as shown in the left-hand side of figure 2.13. This arrangement works so long as seam-gen is still "connected"
to the project—meaning the settings in seam-gen/build.properties haven't been modified since the project was
generated. Seam-gen becomes "disconnected" from the project the minute that you use it to create a different
project. When you do, it switches its control to the new project. Once you alter the seam-gen settings, the path
on the left-hand side can no longer be used.
Figure 2.13 The seam script invokes Ant targets in the project's build. This image shows the difference between executing the
explode command from seam-gen and executing the explode target directly from the project.
As it turns out, the Ant build script in the root of the project can stand on its own. It supports all the same
project-specific targets as seam-gen. Thus, you can call the build script directly, as shown in the right-hand
side of figure 2.13.
You must have Ant available on your path in order to execute the project's Ant targets without the
assistance of the seam command. Appendix A has information about how to install Ant. By moving to the
project's build, you can execute all of the build-related targets that were available with the seam command.
These targets include test, deploy, undeploy, explode, restart, unexplode, clean and archive.
The only change in how you run these targets is that you prefix the command with ant rather than seam. For
instance, to deploy the application as an exploded archive, you run ant explode. You cannot, however, run
any of the code generation commands. You have to complete all of that work using the seam command before
you remove its reins on your project. Just remember, you use the seam command when you are in the Seam
distribution directory and ant when you are in the project directory.
Licensed to Jaroslaw Gilewski <[email protected]>
2.6 Rapidly developing a seam-gen project
One of the best features of seam-gen is that sets up a build that supports a continuous development cycle. By
that, I mean that you can make changes to the application and have them immediately swept away to the
server. If you deploy your application to JBoss AS as an exploded archive, you can take advantage of
incremental hot deployment which allows the changes to take effect immediately. In this section, you learn
what is meant by incremental hot deployment, how it can speed up development, and how to get the IDE to
handle the last remaining bit of manual work necessary to make development truly continuous.
2.6.1 Incremental hot deployment
The term incremental hot deployment means that the deployed application can be updated while it is running
with the latest changes from development. However, what this term doesn't tell you is what types of files can
be redeployed and what affect they will have on the application. Meaning, what really qualifies for "instant
change"?
The reason that incremental hot deployment is such a gray area is because the Java EE specification
doesn't discuss it anywhere, thus providing no real standard. So, to begin with, it's an extension to Java EE. On
top of that, the implementation is not consistent amongst vendors. In some cases, its just marketing jargon,
with no useful implementation to back it up.
This section clarifies which files get to participate in incremental hot deployment in a seam-gen project,
when and under what conditions, and what happens when the files get deployed. We are looking for the
answers to two questions: How hot is "hot"? and Does Seam deliver the promise of "instant change"?
Synchronization of static resources
Anytime you are working with an exploded archive, regardless of whether you're using the EAR or WAR
package format, you can push out changes made to static resources—stylesheets, graphics, and JavaScript—by
running seam explode. The Ant build properly detects which files have been added or modified since the
last run and copies them to the exploded archive directory on the server.
When working with static resources, the application server is merely acting as a web server. It reads the
contents of the static resource from the filesystem and serves it to the browser in response to an incoming
request for the resource. The application server uses the file, unaware of any change it has undergone. The
application does not have to reload to use the updated static resource. If the server supports runtime JSP
compilation, which JBoss AS enables by default, then the server will recompile JSP files that have changed—
so it does acknowledge the change—but the application still does not reload.
While certainly nice, I don't consider this feature enough to warrant the label incremental hot deployment
as some application server vendors like to label it. To me, it is just common sense. If a build cannot, at the very
least, handle this scenario, then it isn't much of a build. The absence of this feature puts an exploded archive on
par with a packaged archive.
Let's take it a step further and look at two additional web resource that can be hot redeployed in seam-gen
projects.
Instant JSF
A majority of your time working in the view will be spent developing JSF pages. You certainly want changes
to them to be picked up as well. If you are using JSP for your JSF pages, you are already covered. However,
projects created by seam-gen use Facelet templates for the JSF pages. Facelets will not read a template more
than one time unless it is running in "development mode". This mode is the compliment to runtime JSP
Licensed to Jaroslaw Gilewski <[email protected]>
compilation. To enable development mode, you just need to ensure that the profile you are using has the
debug property set to true:
debug=true
When the build is run, this property will be applied to the web.xml file, setting the
facelets.DEVELOPMENT servlet context parameter to true:
<context-param>
<param-name>facelets.DEVELOPMENT</param-name>
<param-value>true</param-value>
</context-param>
To test that this works, use your file explorer to locate the project directory. Open the view/home.xhtml
page in a text editor. With the file open, replace the bulleted list of Seam benefits with a description of the
Open 18 application (you can make it up). When you are done, run seam explode, and confirm that the
change was picked up by refreshing the home page. You should also be able to verify that the server log is
silent, not reporting a reload of the application. Feel free to try out other changes.
The debug property has another affect. It controls whether Seam is running in "debug mode". When this
mode is active, Seam will detect changes to the page descriptor files (pages.xml and *.page.xml) and reload
their definitions. Page descriptors are a substitute for faces-config.xml. They provide navigation, page-oriented
actions, exception handling, security, and conversation controls for JSF requests. Since they are capable of
being hot redeployed, all of the aforementioned features can be modified without having to restart the
application. You will learn about page descriptors in chapter 5.
Even with static resources, Facelet templates, and page descriptors being incremental hot deployed, there
is still an important piece missing. Java classes.
The Holy Grail: Hot redeployment of Java classes
Seam doesn't draw the line at web resources. The Seam developers recognized that it is highly unlikely that
you will make a change to a JSF page without also needing to make a change to the Java class or classes used
by the page. There is a strong bond between the two. Thus, only being able to deploy web resources offers a
false promise of "instant change".
Seam goes the extra mile by using an isolated, development classloader that supports incremental hot
deployment of JavaBean components. Java classes eligible for hot redeployment must reside in the src/action
directory of the project. Both Java and Groovy classes are supported, although in slightly different manners.
The seam explode command compiles Java classes in the src/action directory of the project and moves
them to WEB-INF/dev in the exploded WAR archive, the root of the development classloader. Seam leverages
the Groovy classloader to load Groovy scripts dynamically, so Groovy scripts in the src/action directory are
copied to the development classpath directly, without being compiled. When Seam detects that a file has been
changed or added to WEB-INF/dev, it initiates a new component scan on this directory. During the scan,
components that were previously loaded from this directory are removed and the new components are
instantiated. Any component properties defined in components.xml or seam.properties are then applied to the
new components.
There are two prerequisites for using the development classloader. The application must be using the
WAR package form and Seam must be running in "debug mode", which, by default, means using the dev
profile.
Seeing is believing, so let's give this feature a try. Referring back to the course listing screen, you should
notice that the courses are listed in the order that they are fetched from the database, which isn't very intuitive.
Licensed to Jaroslaw Gilewski <[email protected]>
How about we change the default sort property? To do that, we need to add an order clause to the query that is
used to back this table.
Once again, use your file explorer to navigate to the project directory. Open
src/action/org/open18/action/CourseList.java in your editor. With this file opened in your editor, add the
getOrder() method shown in listing 2.6. This method contributes the order clause to the JPQL that is built
by this class. If an order has not been specified explicitly, then we instruct the query to sort the courses by
name, in ascending order. CourseList inherits from EntityQuery, a base class in the Seam application
framework that acts as a data provider. You will learn more about the Seam application framework in chapter
9.
Listing 2.6 Method that sets the sort order for the course list
public String getOrder() {
if (super.getOrder() == null) {
setOrder("name asc");
}
return super.getOrder();
}
Save the CourseList class file and run seam explode to migrate your changes to JBoss AS. Reload
the page in your browser and you should see that the list is sorted based on the name of the course. If you take
a look at your JBoss AS server log, you should see messages appearing similar to listing 2.7. Note that JBoss
AS does not reload the application. What you see here is Seam dumping the classes in the development
classloader and rereading the components into memory.
Listing 2.7 Incremental redeployment as reported by the development classloader
00:00:00,385 INFO [Initialization] redeploying
00:00:00,395 INFO [Scanner] scanning:
/home/twoputt/opt/jboss-as-4.2.2.GA/server/default/deploy
[CA]/open18.war/WEB-INF/dev
00:00:00,424 INFO [Initialization] Installing components...
...
00:00:00,720 INFO [Component] Component: courseList, scope: EVENT,
type: JAVA_BEAN, class: org.open18.action.CourseList
...
00:00:00,491 INFO [Initialization] done redeploying
Pretty cool, huh? The best part is that the application experiences minimal disruption. Your HTTP session
remains intact and therefore you should be able to continue testing the interface without starting over from the
beginning.
I'll agree that we still haven't quite achieved "instant change", if you count from the time you saved the
file, because it is still necessary to run the command seam explode. You will soon learn how to get the IDE
to handle this task for you. Assuming we have the IDE integration setup, then the development classloader
delivers on the promise of "instant change" for Java files to complement the "instant change" already available
for non-Java resources. Few Java web framework offer such comprehensive incremental deployment
capabilities. It is by far one of the coolest, and competitive, features of Seam. (Grails is alternative framework
that offers incremental hot redeployment).
There are some limits to the hot redeployment feature. Seam cannot hot redeploy any of the following:
z
Classes outside of the src/action directory
Licensed to Jaroslaw Gilewski <[email protected]>
z
JPA entity classes
z
EJB session bean components
z
Seam components defined in components.xml
If you want to make changes to any of the files just listed, you must run seam restart. In addition, hot
redeployable components cannot be used by classes outside of the development classloader, nor can they be
referenced in the components.xml descriptor.
The incremental hot deployment features discussed in this section are summarized in table 2.5. This table
marks the conditions that must be true for the resource to be eligible for redeployment.
Table 2.5 Incremental hot deployment resources when using an exploded archive
Resource
Available with WAR
Available with EAR
Debug mode required
images, CSS, JavaScript, static HTML, JSP
no
Facelet templates, pages.xml, *.page.xml
yes
Java classes in src/action
yes
Groovy scripts in src/action
yes
Seam developers are working on expanding the capability of the development classloader to be able to
handle all Java types. It is just a matter of time before all changes in the Java code can be seen immediately in
the running application. But even without these improvements, the incremental hot deployment feature puts the
productivity of Seam development on par with PHP and Ruby development.
The multitude of command line tasks that you have to perform are likely taking their toll on you. Having
to navigate the file system in order to edit the project files hasn't been fun either. The goal of the next section is
to learn how to bring the project into an IDE so that you can leverage the IDE to customize your application
without having the burden of executing the seam-gen commands manually.
2.6.2 Accelerating development by using an IDE
Importing a preexisting project into an IDE can be like trying to force a square peg into a round hole. It takes
time to enlighten the IDE about the structure of the project. Seam-gen removes this hurdle by generating
ready-made project files for the two most popular open-source Java IDEs, Eclipse and NetBeans. As a result,
importing the project becomes an effortless task.
Although seam-gen generates the IDE project files automatically, the Ant build is the key to IDE
portability. Seam-gen hooks the Ant targets into the build life cycle of the IDE to leverage the auto-build
feature to make the instance change feature even more instant. Let's start by importing the project into Eclipse
and explore how the Ant targets are hooked into Eclipse's build.
Importing the project into Eclipse
Seam-gen lays down the very same project files that Eclipse creates when you use the New Project wizard, as
well as a handful of additional configurations, shown here:
z
.project
z
.classpath
z
explode.launch
z
debug-jboss.launch
z
open18.launch
cueballs in numbered list and text
Licensed to Jaroslaw Gilewski <[email protected]>
. When you point Eclipse at the project folder, Eclipse immediately recognizes it as one of its own projects.
Eclipse is blissfully unaware of that fact that it was not the originator.
If you have explored the contents of a project managed by Eclipse, you should recognize the main Eclipse
project file [#1] and the classpath definition file [#2]. The explode launch configuration [#3] hooks the
execution of Ant targets into the Eclipse build cycle, thus allowing Eclipse to assume all responsibility of
executing the Ant targets as part of its automatic, continuous compilation cycle. You see this integration in
action once you pull the project into your Eclipse workspace. A launch configuration for attaching the
debugger to an external instance of JBoss [#3] and a launch configuration for using the Hibernate Console
from the JBossTools Eclipse plugin are also created. These last two configurations are not covered here, but
you can find additional information about them online in the bonus chapters.
To perform the import, start Eclipse and choose the File > Import... menu item. When the import dialog
appears, select "Existing Projects into Workspace", which you can quickly find by typing the first few
characters into the text entry field in that dialog. In the next dialog, click "Browse..." next to the "Select root
directory" option. When the file selection dialog appears, locate the project directory. If you are following
along with the example, the location of the project is /home/twoputt/projects. Figure 2.14 shows the import
dialog acknowledging open18 as a valid project. To edit the project in place, ensure that "Copy projects into
workspace" is not checked. If you were to enable this option, Eclipse would make a copy of the project and put
it in the Eclipse workspace directory—typically ${user.home}/.eclipse/workspace.
Figure 2.14 The Eclipse import wizard identifying the seam-gen project as an existing Eclipse project and a candidate for import.
When you click Finish, Eclipse incorporates the project into your Eclipse workspace and lists it in the
Project Navigator view. In the console, you should notice a flurry of activity as the project builds for the first
time. If you don't, then you likely have auto-build disabled and you will need to run the build manually.
NOTE
After the import, you may be immediately prompted with an error dialog notifying you of problems during
the build. The message states that "the builder launch configuration could not be found." You may safely
disregard this error, as Eclipse functions as expected despite its moaning.
Licensed to Jaroslaw Gilewski <[email protected]>
That's it! You now have the project running in Eclipse. Now you can really get serious about developing
the source code. To aid in development, the Seam source code has been attached to the Seam library in the
Eclipse project. That means for any class in the Seam API you get context-sensitive JavaDoc, you can view the
class source, and you can step into the class during a debug session. The other benefit of having the project in
Eclipse is the integration between the Ant build script and Eclipse's build life cycle. Let's explore how that
works and what it means for development.
Hooking into Eclipse's Auto Build
Before launching into a technical discussion, I want to give you a feel for how Eclipse lightens the
development load by initiating the project's incremental hot deployment facility. In the process, we are going
to add some color to the application.
Golf scorecards are filled with color. In particular, each tee set has a color associated with it. Yet our golf
course directory is looking pretty monotone. It's time to give it some flair. In the tee sets table on the course
detail page, shown back in figure 2.9, the color column simply displays the name of the color. A nice
improvement would be to have this column render a colored boxed instead.
In Eclipse, navigate to the view/Course.xhtml file and open it. You can also get there by using the
Ctrl+Shift+R key combination and typing in the name of the file in the input box. With the file open, look for
the component tag <rich:tab label="teeSets">. Next, find the <h:outputText> component tag
that uses the #{teeSet.color} expression. You should expect to find this tag in the third <h:column> of
the <rich:dataTable> that is contained in that teeSets tab. You are going to change this column to render a
colorized box rather than the name of the color, but still use the color in the title attribute for Section 508
compliance:
<rich:tab label="teeSets">
...
<rich:dataTable
value="#{courseHome.teeSets}"
var="teeSet"
rendered="#{not empty courseHome.teeSets}"
rowClasses="rvgRowOne,rvgRowTwo"
id="teeSetsTable">
...
<h:column>
<f:facet name="header">color</f:facet>
<div title="#{teeSet.color}"
style="background-color: #{teeSet.color}; height: 1em;
[CA]width: 1em; outline: 1px solid black; margin: 0 auto;"/>
</h:column>
...
</rich:dataTable>
...
</rich:tab>
NOTE
A better approach is to create CSS classes for each of the colors and then reference the class in the
template, but that goes beyond the point of this exercise.
When you save the file, you should be able to immediately check it in your browser, thanks to incremental
hot deployment. You don't even have to refresh the page for the change to take effect. Just click on the facility
Licensed to Jaroslaw Gilewski <[email protected]>
tab and then the teeSets tab again. The contents of the tab are retrieved from the server using an Ajax request.
Ladies and gentlemen, we have color! The colorized tee boxes are shown in figure 2.15.
Figure 2.15 The list of tee sets for a course. The value of the color property is being used to display a colorized box.
Notice that you did not have to execute ant explode for your change to take effect. How is it that
Eclipse knows to run this Ant target? As mentioned earlier, seam-gen configures Eclipse to fire Ant targets
during various stages in the Eclipse build life cycle. The configuration of Eclipse's build is shown in figure
2.16.
Figure 2.16 The external tool build configuration for Eclipse that seam-gen installs to execute Ant targets during the Eclipse build
process. On the left is the list of Eclipse builders. The right screen shows the details of the Ant builder.
The screen on the left confirms that the Ant launch configuration, identified by the ant icon next to the
name, is activated after Eclipse's native Java Builder. The screen on the right shows the details of the Ant
launch configuration. The Targets tab shows the Ant targets that are executed during each stage of the Eclipse
build life cycle. You can use this screen to change the targets as you see fit.
Notice that the combination of explode and buildtest is executed whenever an Auto Build—also
referred to as an incremental build—is triggered. This execution is equivalent to running ant explode
buildtest from the command line in the project directory. The Auto Build runs whenever a file in the editor
is saved. All you have to do is save the file to have your changes carried to the exploded archive on the JBoss
Licensed to Jaroslaw Gilewski <[email protected]>
server. Eclipse won't slack off while you're editing. When you really get going, you can keep Eclipse in a
constant build loop. I'm sure you will agree that keeping Eclipse busy is better than having to switch to the
command line and repeatedly type ant explode .
Earlier, I promised to relieve you of your command line duties and fully deliver on the promise of "instant
change". There you have it! Eclipse is doing your dirty work. Change, save, view in browser, and repeat. That
goes for web resources and JavaBean components. No marketing jargon here. The only thing you need now is
for Eclipse and seam-gen to write the business logic for you. Of course, if that were true, we'd be out of a job.
Eclipse isn't the only way to achieve "instant build". NetBeans works just as well, if not better. Let's give
NetBeans a try to contrast it with Eclipse and help you decide which environment you prefer.
Importing the project into NetBeans
Seam-gen also generates project files for NetBeans. The steps for importing the project into NetBeans do not
differ all that much from Eclipse, though the way the project build is integrated is quite different. NetBeans has
native Ant integration, which means that the Ant build is the NetBeans build. You will witness the benefits of
this as you step through this section. The screenshots in this section were taken using NetBeans 6, but
NetBeans 5.5 will work just as well.
The NetBeans project files that get put into the nbfolder of the project, are as follows:
z project.xml
z ide-file-targets.xml
z debug-jboss.properties
cueballs in numbered list and text
Project.xml [#1] is the main NetBeans project file. It manages the classpath and the mapping between
NetBeans build targets and Ant targets from the project build. The other two files, [#2] and [#3], augment the
build with a target to connect a debug session to a running JBoss server.
To begin the import, start NetBeans and choose File > Open Project... from the menu bar. When the file
browser appears, navigate to the /home/twoputt/projects directory. You will notice either an emblem or a
different colored icon (depending on the version of NetBeans), indicating that NetBeans recognizes this folder
as a valid NetBeans project. Select the folder and click Open Project Folder, as shown in figure 2.17. There
are added benefits if you make this the main project, so go ahead and leave the Open as Main Project option
checked.
Figure 2.17 Opening a seam-gen project in NetBeans. The icon color indicates that NetBeans recognizes the folder as a valid project.
Licensed to Jaroslaw Gilewski <[email protected]>
Notice that I have avoided using the word "import" when describing how to bring the project into
NetBeans. Unlike Eclipse, NetBeans does not maintain a "workspace" of projects. Therefore, opening a project
is similar to opening a document in a word processor.
AUTHOR OPINION
NetBean's strategy for opening projects makes more sense to me. The Eclipse workspace quickly becomes
littered with projects and I feel like a bad owner when I have to kick them out. Closing a project folder in
NetBeans just seems more humane, making me feel comfortable with keeping my project navigator in
order.
You can now work with the project in NetBeans.
Leveraging NetBeans' native Ant integration
As I mentioned, NetBeans uses the Ant build script as its build life cycle. NetBeans is just a UI wrapper that
can execute the build targets. Thus, the project created by seam-gen is right at home in the NetBeans
environment.
To dig into the extensiveness of this integration, begin by right clicking on the open18 node in the Project
view and selecting Properties. The Build Script property in the Java Source pane, shown on the left-hand side
of figure 2.18, acknowledges that the Ant build script is a first class citizen in the NetBeans project. The Build
and Run pane, shown on the right-hand side of figure 2.18, reveals how the Ant targets are mapped to each
stage of the build cycle in NetBeans. Contrast this approach with the custom builder that is required to tie Ant
into the Eclipse build cycle. My feeling is that the NetBean's integration makes managing a seam-gen project
more straightforward.
[[TODO: figure 2.18 is going to have to be rearranged because it is too hard to see]]
Figure 2.18 The NetBeans project properties screens showing the direct integration of the Ant targets from the seam-gen project
build.
The integration goes one level deeper. The Ant targets that are configured in the Build and Run pane in
figure 2.18 are included directly in the context menu, shown in figure 2.19. In the same figure, you see the
targets of the build.xml are shown as child elements of the file node. Although build.xml is not a directory,
NetBeans understands the parent-child relationship between the Ant script and its targets.
Licensed to Jaroslaw Gilewski <[email protected]>
Figure 2.19 The context menu of a project in NetBeans, which includes items that map directly to Ant targets in the build file created
by seam-gen. The build.xml node can also be expanded to reveal all of the available Ant targets.
I would like to draw your attention to the Debug menu item. If you have the JBoss server running in debug
mode, this target allows you to attach the NetBeans debugger to it. The reason I find this interesting is because
NetBeans uses the same target to debug a remote JBoss server as it does to debug one started from within
NetBeans. Eclipse requires two different configurations to accomplish the same set of scenarios.
There is an important difference between Eclipse and NetBeans in how the two integrate with Ant. Eclipse
ties Ant into its Auto Build life cycle, so it fires off the Ant build every time you save any file (assuming you
have Auto Build enabled). If you have an idle computer, Eclipse can give it plenty to chew on while operating
in this mode. NetBeans, on the other hand, waits for your instruction to run the Ant targets. I happen to prefer
the NetBeans approach. Eclipse, in its Auto Build configuration, is just wasting processor cycles with its
constant build activity. Watch out when you start Eclipse too, since it will immediately deploy all of your
seam-gen projects to JBoss AS. You can remedy these problems in Eclipse by disabling Auto Build, but that
really goes too far because it eliminates all automatic build tasks, including incremental Java compilation.
In NetBeans, if the project is set as the main project, then you can build and rebuild using either the build
buttons on the main build toolbar or the build keybindings. You will likely find the keybinding to be the best
choice. Then, it's just a matter of hitting the build key when you are ready to send your changes off to the
application server.
TIP
The default keybinding for building the main project is F11. On Linux, this keybinding is reserved by the
Gnome desktop for toggling full-screen mode. Therefore, you need to remap the Build Main Project action
to an alternative key, such as F10.
Let's make a change to the application to see what it is like to develop with this setup. Look back to figure
2.15. You may notice that the list of holes and tee sets at the bottom of the course detail screen have one major
Licensed to Jaroslaw Gilewski <[email protected]>
flaw: they are out of order. For holes, the holeNumber property dictates the order, while the tee set uses the
position property to maintain order. We need to honor those values when rendering the respective tables.
You can track down the backing value expression of these two tables in view/Course.xhtml. The list of
holes is provided by #{courseHome.teeSet} and the list of tee sets it provided by
#{courseHome.holes}. Both of these collections, which reside on the CourseHome class, are converted
from a java.util.Set to a java.util.List and then returned. This conversion is necessary since
UIData components, such as <h:dataTable>, cannot iterate over a java.util.Set. These methods
provide a good opportunity to use a Comparator to sort the collections before they are returned.
Fortunately, CourseHome is a Seam component in the src/action directory, which means that it can be hot
redeployed. Listing 2.9 shows the modified version of the property getter methods that sort the collections
before returning them.
Listing 2.9 TeeSet and Hole collections the respect sort order
public List<Hole> getHoles() {
if (getInstance() == null) {
return null;
}
List<Hole> holes =
new ArrayList<Hole>(getInstance().getHoles());
Collections.sort(holes, new Comparator<Hole>() {
public int compare(Hole a, Hole b) {
return Integer.valueOf(a.getNumber())
.compareTo(Integer.valueOf(b.getNumber()));
}
});
return holes;
}
public List<TeeSet> getTeeSets() {
if (getInstance() == null) {
return null;
}
List<TeeSet> teeSets =
new ArrayList<TeeSet>(getInstance().getTeeSets());
Collections.sort(teeSets, new Comparator<TeeSet>() {
public int compare(TeeSet a, TeeSet b) {
return a.getPosition() == null ||
b.getPosition() == null ? 0 :
a.getPosition().compareTo(b.getPosition());
}
});
return teeSets;
}
NOTE
Licensed to Jaroslaw Gilewski <[email protected]>
A better solution is to specify the sort order globally by adding the @OrderBy JPA annotation to the
parent entity, Course. The @OrderBy annotation instruments the search as part of the query so that
when they are retrieved from the database, they are already sorted. The modification of CourseHome
is simply to demonstrate the hot redeploy feature. A change to an entity class would require an
application restart.
Once you have made those changes, hit the build keybinding or right click on the project's root node and
select Build. Behind the scenes, NetBeans will run the explode Ant target. To give you a taste of the fruits of
your labor, figure 2.20 shows a screenshot of the course detail page with the tee set tab selected.
Figure 2.20 The list of tee sets for a course ordered based on the value of the position property.
Having the project set up in both Eclipse and NetBeans allows it to stand on its own and puts you right
where you need to be to start developing significant enhancements to the application and applying refactorings
to the code. These two IDEs where selected because seam-gen generates their respective project files, allowing
you to import the project into either IDE without having to apply any force. To take a different approach, you
could have used the JBossTools plugin for Eclipse to create a seam-gen project from within the IDE. There is a
similar, yet less sophisticated plugin for NetBeans.
Choosing an IDE
You may find it helpful to know which IDE I recommend. As you observed in this section, you can get started
using either IDE very quickly. But, if you are on the fence, I find NetBeans easier to get into if you are a new
user. It has less clutter and it is geared specifically towards Java EE development out of the box. However, if
you are a power user that wants to take advantage of the JBossTools plugin, you are not afraid to muck around
with installing various other plugins for the better part of a day, and you want every feature under the sun, then
Eclipse is the IDE for you. It was once true that Eclipse had much better refactoring support than NetBeans,
but even that gap is closing as of NetBeans 6.
There is nothing limiting you from using another IDE to develop a seam-gen project, such as IntelliJ
IDEA. The knowledge that you picked up in this chapter about the Ant-based build and existing project files
should give you enough information to make the leap to an alternative IDE environment.
Speaking of alternative environments, there has been a heavy slant in this chapter toward JBoss AS, and it
is time to consider another option in the application server department. Let's deploy the application on
GlassFish.
2.7 Running the application on GlassFish
The prototype application has come a long way and you are ready to show your boss. He likes what he sees,
but just when move toward the door, he drops one more requirement on you. He informs you that there are
several other existing applications that the sales team runs for their demo and they are all configured to run on
Licensed to Jaroslaw Gilewski <[email protected]>
the GlassFish V2 server that was setup on their road box. Unfortunately, you won't be able to use JBoss AS to
deploy the application. So, its back to work to get the prototype running on GlassFish V2.
Hypothetical requirements aside, it is a good idea to always be prepared to deploy to an alternate
application server, even if just to understand what files are dependent on the JBoss environment. Out of the
box, seam-gen creates a build process that is biased towards JBoss AS. With a few small modifications, you
can get the application ready for a GlassFish deployment. GlassFish is a good candidate since it adheres very
closely to the Java EE specifications and it has a pretty sexy and user-friendly administration console. You can
find instructions on how to install GlassFish in appendix A, section A.1.2.
2.7.1 Configuring a JDBC DataSource
The first step to preparing a seam-gen project for a GlassFish deployment is to setup the JDBC DataSource
that can be used by Hibernate. Storing the database credentials—username and password—inside of your
application isn't a very secure approach, especially in a production environment. The compliant way to connect
to a database is to lookup a JCA connection manager in the JNDI tree of the application server and have it
hand you a database connection, which is drawn from a pool of connections. This centralized approach also
allows database administrators to swap databases at runtime and is a key aspect of guaranteeing high
availability. Clearly decoupling the database configuration is beneficial in many ways.
In the JBoss AS, this DataSource configuration is provided by the open18-ds.xml descriptor, which is a
proprietary JBoss resource deployed alongside the application. GlassFish cannot take advantage of the
information in this descriptor and thus requires you to configure a DataSource specifically for that application
server.
There are two ways to setup a data source in GlassFish. You can use the web-based administration console
or the command line script. I will cover the command line approach here.
Before you start GlassFish, you need to add the JDBC driver library for the H2 database to the application
server's classpath. This step is handled automatically by seam-gen for JBoss AS, but we must do it manually
for GlassFish. The JDBC driver must be available on the application server's classpath in order to register a
JNDI data source for the corresponding database.
Execute the following copy command to place the JDBC driver for the H2 database into the extension
directory in GlassFish V2:
cp /home/twoputt/lib/h2.jar ${glassfish.home}/domains/domain1/lib/ext
The next step is to start GlassFish so that you can register the JDBC resources:
${glassfish.home}/bin/asadmin start-domain domain1
While GlassFish is starting, create an XML file named glassfish-datasource.xml in your home area and
populate it with the contents of listing 2.10.
Listing 2.10 The JDBC connection pool and JDBC datasource definitions for GlassFish
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE resources PUBLIC "-//Sun Microsystems, Inc.//DTD Application
[CA]Server 9.0 Resource Definitions //EN"
"http://www.sun.com/software/appserver/dtds/sun-resources_1_3.dtd">
<resources>
<jdbc-connection-pool
name="open18Pool"
datasource-classname="org.h2.jdbcx.JdbcDataSource"
Licensed to Jaroslaw Gilewski <[email protected]>
res-type="javax.sql.DataSource">
<property name="user" value="open18"/>
<property name="password" value="tiger"/>
<property name="url"
value="jdbc:h2:file:/home/twoputt/databases/open18-db/h2"/>
</jdbc-connection-pool>
<jdbc-resource
jndi-name="open18Datasource"
pool-name="open18Pool"
enabled="true"
object-type="user"/>
</resources>
Now add the JDBC connection pool and JDBC data source definitions by feeding that file into the
GlassFish server. It is important to use the absolute pathname to the XML file:
${glassfish.home}/bin/asadmin add-resources
[CA]/home/twoputt/etc/glassfish-datasource.xml
TIP
You may be prompted for a username and password. The default username is "admin" and the default
password is "adminadmin".
You should see a confirmation in the console stating that the command finished successfully. As an
alternative you can use the GlassFish Admin Console to setup this data source. GlassFish's Admin Console is
easy to use, powerful, and looks very professional.
2.7.2 Prepping the persistence unit
Now it is time to shift attention to the project directory, where some modifications must be made. Apply the
changes highlighted in bold in listing 2.11 to the resources/META-INF/persistence-dev-war.xml persistence
unit descriptor in the project.
Listing 2.11 The persistence-dev-war.xml file that prepares the JPA persistence unit for use in the GlassFish
application server
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version="1.0">
<persistence-unit name="open18" transaction-type="JTA">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<jta-data-source>open18Datasource</jta-data-source>
<properties>
<property name="hibernate.dialect"
value="org.hibernate.dialect.H2Dialect"/>
<property name="hibernate.hbm2ddl.auto" value="validate"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="hibernate.transaction.manager_lookup_class"
Licensed to Jaroslaw Gilewski <[email protected]>
#1
[CA]value="org.hibernate.transaction.SunONETransactionManagerLookup"/> #2
</properties>
</persistence-unit>
</persistence>
(Annotation) <#1 The java:/ prefix is removed from the jta-data-source name>
(Annotation) <#2 Controls the UserTransaction JNDI name. GlassFish is semantic with SunONE>
Now you just need to put some libraries in place. Seam-gen uses Hibernate as the persistence provider.
Hibernate happens to be the default JPA provider in JBoss AS. That means, of course, that JBoss AS already
has all of Hibernate libraries on its classpath. GlassFish does not. Instead, GlassFish is prepared to use the
TopLink Essentials implementation out of the box. You must fix this discrepancy. What you need to do is
augment the lib directory of the GlassFish domain with the Hibernate libraries so that the Hibernate JPA
configuration that seam-gen puts in place can be loaded.
QUESTION
Why not just use TopLink Essentials? Projects created by seam-gen do not entirely honor the JPA
abstraction. There are a couple of places where assumptions are made that you are using the Hibernate JPA
implementation, such as an attempt to set the flush mode to MANUAL, a Hibernate extension. At this
point, we will experience the least amount of pain by just sticking to the Hibernate implementation.
All of the libraries that you need are located in the generated project. Move them to the GlassFish server.
Copy each of the following JAR files from the lib directory to ${glassfish.home}/domains/domain1/lib:
z
antlr.jar
z
asm-attrs.jar
z
asm.jar
z
cglib.jar
z
commons-collections.jar
z
commons-logging.jar
z
dom4j.jar
z
hibernate-annotations.jar
z
hibernate-commons-annotations.jar
z
hibernate-entitymanager.jar
z
hibernate-validator.jar
z
hibernate.jar
z
javassist.jar
z
jboss-common-core.jar
There are two more small changes that you need to make. The first is to make a change to the name of the
exploded archive directory. GlassFish mistakenly assumes that a directory with a file extension is a file. The
convention of naming the exploded archive with the .war suffix confuses GlassFish into thinking that the
directory is actually a packaged archive. You need to change the build to use directory explodedarchives/open18 rather than exploded-archives/open18.war so that GlassFish understands that it is deploying
an exploded archive. First, run ant clean. Next, open the project's build.xml file and remove the .war suffix
from the value of the war.dir property:
<property name="war.dir" value="exploded-archives/${project.name}"/>
You can now run a build using seam explode. The new exploded archive directory will be created with
the new name. You are ready to deploy to GlassFish. To take advantage of the hot deployment features of the
Licensed to Jaroslaw Gilewski <[email protected]>
seam-gen setup, you will use the in-place deployer in GlassFish. Run the following command to deploy the
exploded directory.
${glassfish.home}/bin/asadmin deploydir
[CA]/home/twoputt/projects/open18/exploded-archives/open18
Check your work using the same URL as earlier. You should see the application running, now powered by
GlassFish. Your work here is done. Get out the door while you can and enjoy your golfing vacation.
Eventually, this manual labor of setting up the seam-gen application in GlassFish will not be required. At
the time of writing, there is an outstanding feature request for seam-gen to support GlassFish out of the box.
Please check issue JBSEAM-1619 in the JBoss JIRA14 for the latest status. Even when this bug is fixed, the
exercise that you went through here gives you the skills you need to get the project working on additional
application servers.
2.8 Summary
The Open 18 prototype developed in this chapter is the start of the example application used throughout this
book. At the beginning of this chapter, you had this overdue project dropped on your lap at the last minute.
This type of situation is not one that leaves a lot of room for exploration to find the perfect framework. You
decided to transfer the burden to seam-gen because of what you heard about its ability to quickly produce
CRUD applications. This decision paid off, as seam-gen was able to build out a working prototype from an
existing database schema, complete with JPA entities and a user interface capable of listing, sorting,
paginating, filtering, persisting, updating and deleting these entities, in just a couple of hours. Even better, you
discovered that the project scaffolding and build script are suitable to be used as the foundation of the project
long term, most notably because of the instant change feature and its ability to prepare the application for
multiple environments. In chapter 4, you are going to continue using seam-gen to build out new modules from
scratch, starting with user registration.
While taking a look around the Open 18 prototype, you discovered that it exceeds expectations. It is not
only able to read and write database records, but can also display and link related records. Validations are
interpreted from the database schema and enforce in real time. It helps, too, that the interface has a nice look
and feel. This chapter also gave you an overview of the standard Java EE archive formats and how the two
options offered by seam-gen affect development. You discovered that the most compelling feature of the
generated project is the incremental hot redeployment of static web resources, JSF templates and page
descriptors, and JavaBean components. You recognized that Java development can be as productive as any
scripting environment.
Having felt out the prototype, you took a look under the hood to see how it is laid out. The seam command
was demystified, revealing that it is actually a branded Ant script. You learned that the Ant build is the key to
IDE portability. You had the opportunity to import the project into both Eclipse and NetBeans, contrasting the
approach that each takes to managing the project. To push the boundaries of flexibility, and prove that the
application the project generates is truly Java EE-compliant, you tweaked it and deployed it to GlassFish.
If you started from scratch, it would literally take you weeks to get to this point. Instead, you can start a
prototype in the second half of the week and have it done in time to hit the road for your weekend getaway!
By far, the best part of seam-gen is not what it creates, but what it enables you to create. Without much
effort, you have a project that you can use to learn about Seam in action. But seam-gen cannot teach you about
how to use Seam. You now stand at the trail head that begins your journey into Seam, its components,
concepts,
and
life
cycle.
14
http://jira.jboss.org/jira/browse/JBSEAM
Licensed to Jaroslaw Gilewski <[email protected]>
3
The Seam life cycle
There is a stark difference between hitting balls at the driving range and taking your shot out on a golf course.
The driving range offers a nice level surface that is perfect for making square contact with the ball at the base
of your swing. The golf course surfaces are rarely so ideal. Surface variation include the tee box, the fairway,
the rough, the sand trap, from behind a tree, in a creek, and—if you are my brother—on top of a warehouse.
The point is, the real world is not as manageable as the practice area.
The standard JavaServer Faces (JSF) implementation lives in the ideal world with driving ranges, where
everything works by design. As long as your application doesn't need to pass data across redirects, use EJB3
session-beans in a JSF page, execute actions on an initial request, or execute stateful navigation—to cite
several problem cases—JSF appears like a pro. The JSF component model and event-driven design
conveniently mask the underlying HTTP communication between the browser and the server-side logic. Where
JSF comes up short is in catering to nonconforming use-cases. Unfortunately, the real world is full of
nonconformity. To adapt JSF to these less than ideal situations, Seam taps into the extension points in the JSF
life cycle, resulting in a much more sophisticated request handling facility known as the Seam life cycle. The
Seam life cycle envelopes the JSF life cycle to provide a front-controller, advanced page navigation, and
managed services.
If you aren't familiar with JSF, you may be worried at this point that you cannot use Seam without
understanding JSF. What you have to keep in mind is that Seam was developed to solve shortcomings in JSF
and to improve the integration between JSF and EJB 3. Therefore, it's necessary to have at least a rudimentary
understanding of JSF. I recommend that you read through the section in the introduction of the book entitled
What you need to know about JSF to use Seam and also consult the recommended articles mentioned. If you
are a quick learner, or have spent enough time in the Java enterprise landscape to master a new web framework
with little guidance, then this chapter will serve as a short, yet effective introduction to JSF for you. If you
would rather just move beyond the presentation layer and the shortcomings in JSF that Seam solves, you can
boldly skip this chapter and advance to learning about Seam components and contexts.
This chapter sorts out which aspects of JSF that Seam keeps and which parts are tossed to the side. By the
end of the chapter, you will have an understanding of the difference between an initial JSF request and a
subsequent postback and how Seam weaves its enhancements into both styles of request to form the Seam life
cycle. You will then be ready to learn about Seam components—those are beans for you Spring fans—which
are used to control the user interface and respond to actions triggered from it. Seam components are the classes
you see in the seam-gen project with the @Name annotation above the class name. Seam stores components in a
container much like the Spring Framework does its beans, effectively replacing the managed bean facility in
JSF. As you advance through the next couple of chapters, what you will discover is that Seam components can
strictly function as presentation layer components or they can contain business, process, domain, and
persistence logic. In fact, you can even have a single component serve all of these roles. Seam does not force
your hand into a particular architectural design.
Since this chapter focuses on the Seam life cycle, let's begin by looking at how Seam registers itself to
participate in both JSF and basic servlet requests.
Licensed to Jaroslaw Gilewski <[email protected]>
3.1 Exploring how Seam participates in a request
To use Seam in an application server environment, it must be hooked into the life cycle of the servlet
container. The servlet container bootstraps Seam when it starts up, at which time Seam loads components and
gears up its container to begin serving out component instances. Once running, the servlet container also
notifies Seam when HTTP sessions are opened and closed. Additionally, Seam enrolls itself in servlet requests
by registering a servlet filter, a servlet, and a JSF phase listener. It is through these servlet and JSF phase
events that Seam manages its container and enhances the default JSF life cycle.
Before getting knee deep into configurations, I want to provide context to the phrase life cycle, as it is
being thrown around quite casually. I have made reference to a servlet context life cycle, a request life cycle, a
JSF life cycle, and a Seam life cycle. Let's sort them out.
The servlet context life cycle represents the entire lifespan of the web application. It is used to bootstrap
services, such as the Seam container. The request life cycle, on the other hand, is the overarching life cycle for
a single request. It envelops the JSF and Seam life cycles. It lasts from the time the browser requests the
application URL to the time the server finishes sending the request to the browser. The JSF life cycle is more
limited in scope. It is confined to the service() method of the JSF servlet and does not concern itself with
non-JSF requests. In between is the Seam life cycle. On the one hand, the Seam life cycle works alongside the
JSF life cycle, weaving in extra services at strategic extension points. On the other hand, the Seam life cycle
extends beyond the JSF life cycle, both vertically, capturing events that occur outside the scope of the JSF
servlet, and horizontally, by participating in non-JSF requests. In some sense, it is a rebirth of the JSF life
cycle.
In this section, you can expect to learn how Seam starts, how to configure it to participate in servlet
requests, and what comprises the Seam life cycle. The configurations covered hear are merely a review of what
seam-gen installed when it created the application. However, if you are starting an application from scratch
without seam-gen, these lessons will be valuable for getting Seam setup. Let's begin by learning how to "turn
on" Seam.
3.1.1 Flipping Seam's switch
A servlet life cycle listener is notified as soon as the application in which it is registered is initialized. Seam
uses this life cycle event to bootstrap itself. You register the SeamListener by placing the following stanza
in the appropriate spot in your application's web.xml descriptor, which is located in the WEB-INF directory:
<listener>
<listener-class>org.jboss.seam.servlet.SeamListener</listener-class>
</listener>
As soon as Seam is called into action, it begins scanning the classpath for components. The component
scanner places the definition for components that it locates into the Seam container. If any of those
components are marked as application-scoped startup components (i.e. annotated with both @Startup and
@Scope(ScopeType.APPLICATION)), Seam automatically instantiates them at this time. This style of
component is ideal for performing "bootstrap" logic such as updating the database or registering modules. You
will learn about startup components and the Seam scopes in the next chapter.
The SeamListener also captures notifications when new HTTP sessions are started, at which time it
instantiates startup components that reside in the session-scope (i.e. annotated with both @Startup and
@Scope(ScopeType.SESSION)). All other components are instantiated on demand during the processing
of an HTTP request. You will learn all about components and how Seam locates, starts, and manages them in
the next chapter.
Licensed to Jaroslaw Gilewski <[email protected]>
Once Seam is running, it is ready to lend a hand with incoming requests. A majority of that work is done
in the JSF servlet, so let's see how Seam ties in with JSF.
3.1.2 The JSF servlet, the workhorse of Seam
Given that Seam is so firmly rooted in JSF, it should come as no surprise to you that the main servlet in a Seam
application is the JSF servlet. This servlet could easily be named the Seam servlet because of how much Seamrelated activity occurs within it. If you are already using JSF in your project, then your web.xml descriptor
already has the necessary servlet configuration. If not, place these two stanzas in your application's web.xml
descriptor to enable the JSF servlet (regardless of JSF implementation):
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.seam</url-pattern>
</servlet-mapping>
Notice that the mapping pattern is defined as *.seam rather than the default for JSF requests, *.jsf.
Applications created by seam-gen are configured to this Seam-branded extension for the JSF servlet mapping.
You are free to use the extension of your choosing. The change to the servlet pattern is, for the most part,
merely cosmetic. However, the choice of which view technology to use with JSF is far more significant.
A commitment to Facelets
There is an important change that you may want to consider making to your JSF technology stack. Examining
the application created by seam-gen, you should recognize an abundance of files ending in .xhtml in the view
directory. The .xhtml extension is the default suffix for Facelets templates. Seam developers strongly
recommend using Facelets as the view handler for JSF in place of JavaServer Pages (JSP) because it solves the
mismatch between JSF and JSP and offers an extensible templating system that shares some similarity with
Struts Tiles.
Facelets is a lightweight view technology that is specifically designed for creating JSF pages. It can
accommodate JSF component markup natively and builds the UI component tree from it directly, avoiding the
unnecessary JSP tag layer. Facelets templates use the same familiar XML-based tags as JSP, but escape the
problems that plague JSP, such as its overhead, its mismatch with the JSF life cycle, and the permitted use of
Java scriptlets. In simple terms, no more <f:verbatim> tag 15. You can also use inline EL notation without
having to embed it in an <h:outputText> tag. These are just two small improvements that Facelets offers
over JSP. In addition, Facelets offers a feature known as a composition, which is a fancy way of saying a
reusable page fragment. These page fragments can act as UI components, allowing you to build new UI
component without having to write any Java.
To use Facelets, you need to register the Facelets view handler in the faces-config.xml descriptor, which
can be found in the WEB-INF directory:
<faces-config>
<application>
15
The <f:verbatim> tag is necessary in JSP to wrap plain HTML markup as a UI component. Otherwise, it is skipped when
the UI component tree is being built since it is not associated with a JSP tag handler.
Licensed to Jaroslaw Gilewski <[email protected]>
<view-handler>com.sun.facelets.FaceletViewHandler</view-handler>
</application>
</faces-config>
Using Facelets with Ajax4jsf/RichFaces
At one time the Facelets view handler had to be registered using a web.xml context parameter
org.ajax4jsf.VIEW_HANDLERS when Facelets was used in combination with
Ajax4jsf/RichFaces. This requirement is no longer necessary. Regardless of whether you are
using Ajax4jsf/RichFaces or not, you can still register the Facelets view handler in the facesconfig.xml descriptor.
The org.ajax4jsf.VIEW_HANDLERS context parameter sets the order of third party view
handlers if more than one is used. It can still can be used to define the order for other view
handlers, but is not necessary if you are just using Facelets.
You also need to change how JSF maps an incoming request to a JSF view identifier, or view ID for short.
The default behavior of JSF is to map the incoming request to a JSP file with the file extension .jsp. However,
the default file extension for Facelets templates is .xhtml. To change this mapping, you register the .xhtml
extension as the default suffix for JSF views using a servlet context parameter:
<context-param>
<param-name>javax.faces.DEFAULT_SUFFIX</param-name>
<param-value>.xhtml</param-value>
</context-param>
Figure 3.1 illustrates how an incoming JSF request is processed and translated into a UI component tree.
The .seam extension tells the servlet container to direct the request to the JSF servlet. The .seam extension is
stripped and replaced with the javax.faces.DEFAULT_SUFFIX value to build the view ID. The JSF
servlet then hands the view ID to the registered view handler, FaceletViewHandler. Facelets uses the view
ID to locate the template. It then parses the document and builds the UI component tree.
Figure 3.1 Translation from servlet path to UI component tree, which is built by Facelets.
Licensed to Jaroslaw Gilewski <[email protected]>
One of the pitfalls in JSF, which even Facelets cannot solve, is that the file extension of the view template
is hard-coded in the view ID. The view ID not only determines what template is to be processed, but also used
to define navigation rules. Therefore, a change to javax.faces.DEFAULT_SUFFIX affects all places where
the view ID is referenced.
Seam goes beyond just swapping in the Facelets view handler. Seam leverages Facelets compositions to
keep the JSF views DRY (Don't Repeat Yourself).
Facelets compositions
JSF is very clumsy when it comes to including dynamic markup in a page. In order to include one page as a
child of another, you have to either wrap the include tag (e.g. <jsp:include>) in <f:subview> or the
included page has to have <f:subview> as the root tag. Don't worry too much about the details because
when you use Facelets, you no longer have this problem.
Facelets is founded on the idea of compositions. A composition is a branch of a UI component tree,
defined using the <ui:composition> tag. But, unlike JSF sub-views, a composition fuses the branch into
the component tree without leaving a trace of itself. A composition is very similar to a macro in the C
programming language. Indeed, a composition tag has templating abilities, which means that it can take
markup as parameters and incorporate them into the branch while fusing it into the main component tree.
Let's consider how Facelets compositions are used in a Seam application. As you would expect, every
form has fields and those fields must have labels. But to do it right, you also need to have a marker to indicate
required fields (often represented as a "*") and a spot for an error message when validation fails. To round
things off, you want to standardize the HTML used to layout the label and input for each field so that the
interface is consistent and easily changed. Considering you have to do that work for every field, it can lead to a
lot of typing (or a lot of copy-paste). One of the input fields from the course editor screen is shown in listing
3.1 using plain JSF markup.
Listing 3.1 A field in a JSF form using standard markup
<div class="prop">
<h:outputLabel for="name" styleClass="name">
Name: <span class="required">*</span>
</h:outputLabel>
<span class="value">
<s:validate>
<h:inputText id="name" required="true"
value="#{courseHome.instance.name}">
<f:validateLength minimum="3" maximum="40"/>
</h:inputText>
</s:validate>
</span>
<span class="error">
<h:message for="name" styleClass="errors"/>
</span>
</div>
Cueballs in code and text
#1
#2
#3
#5
#4
The main problem with this markup is not so much that it is verbose, but that there is a lot of repetition.
The id of the field appears three times at [#1], [#3], and [#4]. The required marker [#2] has to be consistent
with the required attribute on the field [#3]. There is no easy way to add an error icon that is rendered only if
there is an error on the field [#4] because JSF does not make that flag available on a per-field basis. Each field
Licensed to Jaroslaw Gilewski <[email protected]>
must be validated explicitly using nested validation tags [#5]. Finally, the layout and associated CSS classes
are hard-coded into each field, which makes them hard to change or augment later on a page-wide or site-wide
basis.
In contrast, you will find the markup in listing 3.2, which calls on the Facelets composition template
view/layout/edit.xhtml, to be far more reasonable. Instead of laying out the field, the focus of the markup is on
providing the template with what it needs to build the output rather than worrying about the layout and
formatting. Nearly every character typed is providing vital information with minimal repetitive elements.
Listing 3.2 A field in a JSF form using a Facelets composition
<s:decorate id="nameField" template="layout/edit.xhtml">
<ui:define name="label">Name:</ui:define>
<h:inputText id="name" size="50" required="true"
value="#{courseHome.instance.name}">
<a:support event="onblur" reRender="nameField"
bypassUpdates="true"/>
</h:inputText>
</s:decorate>
#1
#2
#3
<Annotation #1> An inline template wrapper
<Annotation #2> Named template parameter
<Annotation #3> Unnamed template parameter
With the typing that was saved by using this template, the input field has been augmented to support
instant validation upon losing focus, handled by the Ajax4jsf <a:support> tag. The absence of the
<f:validateLength> tag does not preclude validation. Instead, the validations are applied to the input
component automatically using the <s:validateAll> tag, which reads Hibernate Validator annotations
defined on the corresponding property in the entity class. All of this footwork is handled in the template
view/layout/edit.xhtml, shown in listing 3.3.
Listing 3.3 A composition template for input fields
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:s="http://jboss.com/products/seam/taglib">
<div class="prop">
<s:label styleClass="name #{invalid ? 'errors' : ''}">
<ui:insert name="label"/>
<s:span styleClass="required"
rendered="#{required}">*</s:span>
</s:label>
<span class="value #{invalid ? 'errors' : ''}">
<s:validateAll>
<ui:insert/>
</s:validateAll>
</span>
<span class="error">
<h:graphicImage value="/img/error.gif"
rendered="#{invalid}" styleClass="errors"/>
<s:message styleClass="errors"/>
</span>
Licensed to Jaroslaw Gilewski <[email protected]>
#1
#2
#4
#3
</div>
</ui:composition>
Cueballs in code and text
Although this template is complex, it isolates all of that complexity into a single document. The root of the
template is <ui:composition> [#1], which indicates that the markup should be fused into the parent JSF
tree. Tag libraries are defined as XML namespaces, similar to the XML-based JSP syntax. The composition
accepts two insertions. The first is a named insertion for supplying a label [#2] and the second is an unnamed
insertion for supplying any number of input fields [#3]. The <s:decorate> tag sets two implicit variables,
required and invalid, which indicate if the input field is required and whether it has an outstanding error,
respectively.
You may also notice that this template does not use any standard JSF component tags. Instead, they have
been replaced with Seam component tags, which are mapped to the namespace
http://jboss.com/products/seam/taglib and use the prefix s. The benefit is that the Seam tags do
not require id attributes as the id is derived from the id of the <s:decorate> tag that inserts this template.
The <s:decorate> tag supports other features not shown here, such as the ability to perform common
insertions before and after the markup that it contains.
The <s:validateAll> tag [#4] is wrapped around the input elements, which enforces the Hibernate
Validator annotations that appear on the associated fields in the entity. For example, the following annotation
on the name property of the Course entity would ensure that the number of characters entered is greater than 3
but doesn't exceed 40:
@Length(min = 3, max = 40
public String getName() {
return this.name;
}
The Hibernate Validator validations are applied twice, once in the UI to provide the user feedback, thanks
to the Hibernate Validator-JSF validator bridge registered by the <s:validateAll> component tag, and
when the entity is being persisted to ensure no bad data ends up in the database. These model validations are
applied alongside validations registered on the component using nested validation tags.
Seam offers a similar template for displaying field values, view/layout/display.xhtml, and a master
composition template, view/layout/template.xhtml, that provides the layout for each page. The master template
accepts a single named insertion, body, which injects primary page content into the template. Any markup
outside of the <ui:define> tag is ignored. Here is an example page that uses the master template:
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:s="http://jboss.com/products/seam/taglib"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:a="http://richfaces.org/a4j"
xmlns:rich="http://richfaces.org/rich"
template="layout/template.xhtml">
{this text will be ignored}
<ui:define name="body">
main context goes here
</ui:define>
{this text will be ignored}
Licensed to Jaroslaw Gilewski <[email protected]>
</ui:composition>
Facelets offers additional features that you may find helpful for defining your JSF views. One of the most
enticing features is the ability to create JSF components purely using XHTML markup (no Java code). Given
that the task of creating a JSF component in the standard way is so involved, this can save you a lot of time. It
is also a great way to prototype a JSF component before you commit to creating it. To learn more about what
Facelets has to offer I suggest that you consult the Facelets reference documentation 16.
Most of the calls in a JSF application will go through the JSF servlet. However, there are cases when you
need to send other types of resources to the browser that are not managed by the JSF life cycle. Seam uses a
custom servlet to handle this task.
3.1.3 Serving collateral resources via the resource servlet
The JSF specification does not provide guidance on how to push supporting resources, such as images,
cascading style sheets (CSS), and JavaScript files, to the browser. The most common solution is to implement
a custom JSF phase listener that traps requests matching designated path names, writes content to the response
directly, and short-circuits the remainder of the JSF life cycle by issuing a call to the responseComplete()
method on the FacesContext. This example shows the low-level interaction with the
HttpServletResponse that is required to produce this output:
FacesContext context = event.getFacesContext();
HttpServletResponse response =
(HttpServletResponse) context.getExternalContext().getResponse();
response.setContentType("text/javascript");
// write the JavaScript to the response
event.getFacesContext().responseComplete();
Rather than getting mixed up in the JSF life cycle, Seam uses a custom servlet to serve such resources,
sidestepping the life cycle and thus avoiding the unnecessary overhead. Using an separate servlet is justifiable
since the steps involved in serving a resource are inherently different than those of processing an application
page, eliminating the need for a comprehensive life cycle.
To configure the resource servlet, place the following servlet stanzas above or below the JSF servlet
configured above. The URL pattern for this servlet must be different than the pattern used for the JSF servlet:
<servlet>
<servlet-name>Seam Resource Servlet</servlet-name>
<servlet-class>
org.jboss.seam.servlet.SeamResourceServlet
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Seam Resource Servlet</servlet-name>
<url-pattern>/seam/resource/*</url-pattern>
</servlet-mapping>
The SeamResourceServlet uses a chaining model to minimize the configuration you perform in the
web.xml descriptor. Seam uses this servlet to:
16
https://facelets.dev.java.net/nonav/docs/dev/docbook.html
Licensed to Jaroslaw Gilewski <[email protected]>
push down JavaScript files for the Ajax Remoting library
handle Ajax Remoting requests
serve CAPTCHA images (visual-based challenges to circumvent bots)
serve dynamic images
integrate with Google Web Toolkit (GWT)
integrate with other RPC view technologies in the future
Keep in mind that Seam will work just fine without this servlet, but the features listed here won't be
available to you. Seam may use this servlet for other purposes in the future. If you have it installed, you won't
have to worry about changing your application configuration as Seam adds new features.
In addition to the resource servlet, which allows Seam to process non-JSF requests, Seam also offers a
servlet filter, which Seam uses to operate outside of the reach of both the JSF servlet and the custom resource
servlet. Let's see how the servlet filter is registered and what it can do for you.
3.1.4 Seam's chain of servlet filters
Servlet filters wrap around the entire request, executing logic before and after the servlet handling the request.
Seam uses a single filter to wrap the JSF servlet in order to trap scenarios that fall outside of the JSF life
cycle—or that JSF fails to capture. But Seam's filter is not limited to JSF requests. It canvases all requests,
allowing non-JSF requests to access the Seam container as well. Seam can function without relying on filters,
but the services it adds are worth the small effort of getting the filter installed.
The Seam filter must be positioned as the first filter in the web.xml descriptor. By not putting this filter
first, you run the risk that some of the features will not function properly. Place the following two stanzas
above all other filters in the web.xml descriptor to register the Seam filter:
<filter>
<filter-name>Seam Filter</filter-name>
<filter-class>org.jboss.seam.servlet.SeamFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>Seam Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Although there is only a single filter definition shown here, I have alluded to the existence of more that
one filter. Seam uses a chaining model, trapping all requests and delegating to any number of filters registered
with the Seam container. This delegation model 17 minimizes the configuration that you need to add to the
web.xml descriptor. Filters are registered with the Seam container just like any other Seam component,
allowing you to set and forget the filter configuration in the web.xml descriptor. The remaining configuration
happens in the Seam configuration descriptor.
Seam's built-in filters
Filters registered in the Seam component descriptor are managed by the master SeamFilter using a chain
delegation model. Every filter supports two properties, url-pattern and disabled, to control which
incoming requests are trapped. By default, Seam applies all of the filters in the chain to all requests captured by
the filter. You can reduce the matched set of requests by providing an override pattern in the url-pattern
17
The article http://www.javaworld.com/javaworld/jw-08-2003/jw-0829-designpatterns.html gives a nice explanation of the
chain of responsibility pattern that employed by the SeamFilter.
Licensed to Jaroslaw Gilewski <[email protected]>
attribute of the servlet configuration. It is also possible to disable a filter outright by setting the disabled
attribute to true.
The filters that are included with Seam at the time of writing are summarized in table 3.1. The table
explains the purpose of each filter, lists the additional configuration properties, and indicates under what
conditions they are installed.
Table 3.1 The built-in Seam filters
Component
Purpose
Additional configuration
ExceptionFilter
Handles exceptions generated in the JSF life
cycle; performs transaction rollbacks.
none
yes
RedirectFilter
Propagates conversations and page parameters none
across redirects for navigations defined in facesconfig.xml.
yes
MultipartFilter
Processes file uploads from the Seam upload UI
component.
yes
create-temp-files
Installed?
use a temporary file instead of
holding the file in memory
max-request-size
Abort request if file being uploaded
is larger than this limit (in bytes)
LoggingFilter
Binds the username of the authenticated user to none
the Log4j Mapped Diagnostic Context (MDC) 18,
referenced using the literal pattern
%X{username}
CharacterEncodingFilter
Set the character encoding for submitted form
data.
encoding
requires Log4j
on the
classpath;
depends on
Seam identity
no
the encoding to use (i.e. UTF-8)
override-client
ignore client preference
Ajax4jsfFilter
Configure the Ajax4jsf filter that ships with the
Ajax4jsf library. Eliminates the need to have to
setup this filter individually in the web.xml
descriptor.
force-parser
apply XML syntax checker to all
requests, not just Ajax ones
requires
Ajax4jsf on the
classpath
enable-cache
cache generated resources
ContextFilter
Enable Seam container and contexts for non-JSF none
requests. Should NOT be applied to JSF requests
as it causes duplicate logic to be performed,
leading to undefined results.
no
AuthenticationFilter
Supports HTTP Basic and Digest authentication.
no
realm
the authentication realm
auth-type
basic or digest
key
used as a salt for the digest
nonce-validity-seconds
length of time the security token is
valid, helping to prevent replay
attacks
This filters each offer very specific features, yet each one contributes to enhancing the narrowly-scoped JSF
life cycle. For instance, the ExceptionFilter allows Seam to capture all exceptions that are thrown during
request processing, something that servlets alone cannot encompass. Exception handling in Seam will be
covered at the end of the chapter. For as fine-grained as the JSF life cycle is, its scope is limited to the JSF
servlet alone. The ContextFilter opens access to the Seam container and its context variable to non-JSF
18
The MDC is a thread-bound map that allows third-party libraries to contribute to the log message. The MDC is described in this
wiki page: http://wiki.apache.org/logging-log4j/NDCvsMDC
Licensed to Jaroslaw Gilewski <[email protected]>
servlets, such as Struts, Spring MVC, and Direct Web Remoting (DWR). Though a major of Seam's work is
done within the JSF servlet, these additional filters allow Seam to extend the boundaries of its life cycle above
and beyond the boundaries of the JSF servlet. That covers the configurations necessary to hook Seam into the
servlet life cycle. But wait a minute, aren't we missing the JSF phase listener configuration? After all, that is
how Seam is able to tap into the JSF life cycle.
3.1.5 The Seam phase listener
Considering that many of Seam's enhancements to JSF are performed in a JSF phase listener,
SeamPhaseListener, it would appear as if there is one more configuration to put in place. But there isn't.
At least, it isn't necessary. Seam leverages a design feature of JSF that allows for any faces-config.xml
descriptors available on the classpath to be picked up automatically. In Seam 2.0, the Seam phase listener is
declared in a faces-config.xml descriptor that is included in the core Seam JAR file, jboss-seam.jar. Thus, the
phase listener is available as soon as you include this JAR file in your application. The stanza that registers the
Seam phase listener is as follows:
<lifecycle>
<phase-listener>
org.jboss.seam.jsf.SeamPhaseListener
</phase-listener>
</lifecycle>
Although you are not required to add this declaration to your faces-config.xml descriptor, that doesn't
mean you cannot adjust how it operates. Configuration settings for the phase listener are handled in the Seam
component descriptor. You can control such things as transaction management, debug mode, and the JNDI
pattern used to locate EJB components. The details of these configurations are explained in chapter 5.
That wraps up the necessary configuration to allow Seam to partake in requests. From here on out, the
combination of the Seam servlet filter and the JSF life cycle, which Seam now has a hand in, will be referred to
as the Seam life cycle. Figure 3.2 illustrates the two paths that a request can take as it enters into the Seam life
cycle. The SeamFilter is not limited to wrapping the two servlets shown in figure 3.2, of course. It can also
be applied to additional servlets such as Struts, Spring MVC, or DWR, allowing you to tap into the Seam
container from these third-party frameworks as well.
Figure 3.2 Requests are preprocessed by the Seam filter, proceeding to the JSF servlet or the Seam resource servlet.
Licensed to Jaroslaw Gilewski <[email protected]>
With the configuration of Seam out of the way, I want to take a step back and consider what the JSF life
cycle is like without Seam. The goal of this exercise is to gain an appreciation for the assumptions made by the
JSF specification and where those assumptions come up short. By contrasting the native JSF life cycle with the
version enhanced by Seam, it helps to understand why Seam is relevant and makes the decision clear as to
when you should choose the Seam facilities over the JSF equivalents that it replaces. We will start by
reviewing the general principles of JSF and then walk through the JSF life cycle.
3.2 The JSF life cycle sans Seam
It is possible to learn how to develop a JSF application while remaining naïve of the fact that there is an
underlying life cycle processing each request. Figure 3.3 shows the leap the JSF designers want you to make
between the click of a command button (e.g. <h:commandButton>) and the invocation of an action handler
on a server-side component.
Figure 3.3 A rudimentary look at event-driven behavior in JSF. Method binding expressions on command buttons trigger methods on
server-side components, in this case a Seam component, when activated.
This event-driven relationship is one of the fundamental ways that JSF is supposed to make web
development easier. The direct binding between the command button and the server-side component weeds out
most, if not all, of the low-level details of the HTTP request that you would otherwise have to address, instead
getting you right into the business logic. There is no HttpServletRequest, HttpServletResponse, or
ActionForm to have to concern yourself with. It's perfect. Almost too perfect.
Unfortunately, JSF is plague by the leaky abstraction 19. When the use case fits, life is grand and you don't
need to know about how the request is handled by JSF. You just know that your action handler gets executed.
However, when things get messy, as they often do in the real world in which we must write applications, you
need to know what is going on underneath the covers. That means it is time to hit the books and learn the
phases of the JSF life cycle. Since this book is focused on Seam, we are only looking at the life cycle phases as
a means to better understand what Seam does to improve on them. To study JSF and its life cycle in greater
depth, please see the JSF resources recommended in the introduction of this book (The JSF for nonbelievers
IBM developerWorks series, Java ServerFaces in Action published by Manning publications, and Pro JSF and
Ajax published by Apress).
3.2.1 The JSF life cycle phases
The JSF life cycle decomposes a single servlet request—typically sent over HTTP—into six distinct phases.
Each phase focuses on one task in a progressive chain that ultimately results in sending a response back to the
browser. By executing these steps incrementally, it gives frameworks, such as Seam, the ability to get involved
in the process rather than only having the before and after picture. Each phase raises an event both before it is
invoked and after it completes. Classes that want to be notified of these phase transition events are called phase
listeners. Phase listeners must implement the PhaseListener interface and be registered with the JSF
application, the configuration of which you saw in the last section. A phase listener can, for example, execute
19
http://www.joelonsoftware.com/articles/LeakyAbstractions.html: According to Joel Spolsky, all non-trivial abstractions are, to
some degree, leaky.
Licensed to Jaroslaw Gilewski <[email protected]>
logic immediately before, or after, the action handler method is invoked, having access to the converted form
values that were translated in an earlier phase. The phase listener is the backbone of Seam's life cycle.
The six phases of the JSF life cycle are laid out in figure 3.4. Execution occurs in a clockwise manner. We
have not yet activated the life cycle, which is why there are no arrows in this initial diagram. In the next two
sections, you will discover the path that a request takes through this life cycle as we put it into motion.
Figure 3.4 The six phases of the JSF life cycle. The phases are executed in a clockwise manner in this diagram.
The JSF life cycle phases perform their work on a component hierarchy. This tree of components is similar
to the Document Object Model (DOM) that you use to manipulate HTML a page using JavaScript. Each node
of the component tree is responsible for an element in the rendered page. JSF uses this tree to keep track of
events that are triggered from those components. Having an object representation of the rendered view makes
it possible to apply incremental updates, fostering Ajax interaction with the server.
There are two types of requests that JSF can handle: an initial request and a postback. Let's start by
looking at how an initial request is processed.
3.2.2 The initial request
Every user interaction in a web application starts with an initial request for a URL. The source may be a link in
an e-mail, a link on a webpage, a bookmark, or from the user typing in the URL directly. However it occurs,
there is no prior saved state. That means there are no actions and there is no form data to process. Initial
requests to the JSF servlet use an abbreviated life cycle of only two phases, as depicted in figure 3.5.
Figure 3.5 The life cycle phases used on an initial request for a JSF page.
Licensed to Jaroslaw Gilewski <[email protected]>
In the first phase, Restore View, the only activity that occurs is that an empty component tree is created.
The request then proceeds immediately to the Render Response phase, skipping most of the life cycle. It does
not pass Go, it does not collect the proverbial $200 20. An initial request is not designed to handle any events or
process form data 21.
The main activity on an initial request happens in the Render Response phase. In this phase, the request
URL is examined to determine the view ID and locate the associated template, which was illustrated in figure
3.1. The template is then processed by the view handler.
While the template is being read, two things happen. The component hierarchy is built and the response to
the client is prepared by "encoding" each component. Encoding is a fancy way of saying that the component
spits out generated markup, typically XHTML, though JSF can accommodate any type of output (hence the
fancy name). The generated response includes elements that are "bound" to server-side components. Input and
output elements bind to properties on backing bean components, while links and buttons bind to methods on
action handler components. A single component can serve in both roles. In the absence of Seam, components
must be defined in the faces-config.xml descriptor as managed beans. When an event is triggered on the page,
it launches JSF into a postback, which we will get to next.
Once the entire response has been rendered, the UI component tree is serialized and either stuffed into the
response to be send to the client or it is saved in the HTTP session under a unique key. The state of the UI
component tree is stored so that on a subsequent postback, changes to the form input values can be applied to
the properties bound to them and any events can be enqueued and subsequently invoked.
Server-side versus client-side state saving in JSF
No technology platform would be complete without a vanilla versus chocolate debate. For JSF, that
debate is whether to store the UI component tree on the server or on the client. Let's consider the two
options.
In server-side state saving, the UI component tree is stored in the user's HTTP session. A token is sent
along with the response and stored in a hidden form field. The token value is used to retrieve the
component tree from the session on a postback. Server-side state saving is good for the client, bad for
the server.
In client-side state saving, the UI component tree is serialized (using the standard Java serialization
mechanism), compressed, encoded, and sent along with the response. The whole process is reversed
on a postback to reinstate the component tree. Client-side state saving is bad for the client, good for
the server.
So which should you choose? In my opinion, the choice is clear. Never make your customer suffer. If
you have the opportunity to reduce bandwidth usage, take it. The connection to the client is often
unpredictable, so although some customers might be able to stomach the large pages, others will
experience significant slowdown. There is a more compelling reason for choosing server-side state
saving, though. JSF-based Ajax requests require access to the component tree, so if you use clientside state saving, what was once a trickle of information going from the browser to the server on an
Ajax request is now a massive exchange. Server-side state saving limits the extra communication to
the value of the token.
20
This reference is to the game of Monopoly by Parker Brothers. When you get sent to Jail, you pass by the payday square on the
board.
21
It's possible to implement a phase listener to perform this work, but as you will learn, Seam handles these tasks without any
work on your part.
Licensed to Jaroslaw Gilewski <[email protected]>
The state saving method is set using a top level context parameter named
javax.faces.STATE_SAVING_METHOD in the web.xml descriptor. The web.xml descriptor
installed by seam-gen does not include this context parameter, so the setting falls back to the JSF
default, which is server-side state saving.
<context-param>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>server</param-value>
</context-param>
Each JSF implementation offers ways to tune memory settings used for state saving. Seam-gen
projects use Sun's JSF implementation (code named Mojarra). Consult the Mojarra FAQ 22 to learn
more about the available settings.
Before moving on to postback, let's look at a couple of the assumptions made by this abbreviated life
cycle. The initial request assumes that:
no logic needs to happen before the response is generated
the user has permission to view this page
the page request is an appropriate place to begin within the application flow.
I am willing to bet that by the time you are ready to move on to the next section, you can think of at least
five situations in the applications you have developed in which these assumptions do not hold true. The initial
request is the Achilles' heel of JSF. You will soon discover there is a better way, thanks to Seam.
As bad as JSF is at handling the initial request, it actually does a pretty good job of handling a postback.
After all, that is what JSF was primarily designed to do.
3.2.3 The postback
Unless a condition occurs that short circuits the process, a postback exercises the full JSF life cycle. A short
circuit may be caused by a validation or conversation error, an event designated as "immediate", or a call to the
renderResponse() method on the FacesContext. In the event of a short circuit, control is passed to the
Render Response phase to prepare the output to be sent to the browser. If a call is made to the
responseComplete() method on the FacesContext at some point during the life cycle, even the Render
Response phase is skipped.
Figure 3.6 illustrates the full life cycle in action. The ultimate goal of a postback is to invoke an action
handler during the Invoke Application phase, though various ancillary logic accompanies this primary activity.
22
http://wiki.glassfish.java.net/Wiki.jsp?page=JavaServerFacesRI
Licensed to Jaroslaw Gilewski <[email protected]>
Figure 3.6 During a postback, the full JSF life cycle is used unless short-circuited by an error.
The Restore View phase of a postback restores the component hierarchy from the stored state as opposed
to just creating an empty shell. The Apply Request Values phase captures values submitted by the form into the
component tree and also looks for events that were triggered and queues them. The next two phases deal with
massaging the data that came in from the form and ultimately assigning the values to the properties on the
object to which the individual form inputs were bound.
The life cycle hands control back over to the application during the Invoke Application phase, which
triggers the method bound to the link or button that initiated the postback. This method is termed an action
handler. In addition to the action handler, one or more action listeners may also be invoked. Unlike the action
handler, though, action listeners simply execute, but do not trigger, declarative navigation.
Following the execution of the action handler, the navigation rules are consulted to decide where to go
next. If the return value of the action handler method is null or the method return type is void, and there are no
rules that match the signature of the method expression, then the same page is rendered again. If the return
value of the action handler method is a non-null string value, or there is a rule matching the method expression,
then the navigation rules defined in faces-config.xml are used to determine the next page to be rendered. The
presence of the <redirect/> element indicates that a redirect should be issued before requesting the page,
resulting in an initial request, rather than rendering the page immediately, which is the default behavior. An
example of a navigation rule is shown here:
<navigation-rule>
<from-view-id>/register.xhtml</from-view-id>
<navigation-case>
<from-action>#{registerAction.register}</from-action>
<from-outcome>success</from-outcome>
<to-view-id>/welcome.xhtml</to-view-id>
<redirect/>
</navigation-case>
</navigation-rule>
That concludes your crash course in JSF. Let's reflect on what you just learned and address a several holes
in the aforementioned life cycle. This discussion will lead us into Seam's most targeted improvement to the
JSF life cycle, advanced page orchestration.
Licensed to Jaroslaw Gilewski <[email protected]>
3.2.4 Shortcomings of the JSF life cycle
As I have mentioned a number of times in this chapter, JSF is well designed, but there is no denying that it has
some quirks. In this section I want to enumerate them so that it is very clear what problem Seam is attempting
to solve. The goal here is not to pick on JSF, but to evaluate the opportunities where it can be improved. What
is interesting about Seam is not that it fixes problems with JSF, but that it uses the strengths of JSF to make it a
better framework. I am hard on JSF in this section, perhaps unnecessarily so, because I want to emphasize that
Seam poignantly addresses the concerns people have with JSF so that the Seam/JSF combination is an
attractive choice as a web framework.
The weaknesses in JSF begin with the initial request. So, let's start at the top.
Life before the first action
The two styles of request in JSF are very lopsided. On the one hand, you have an anemic initial request that
hardly does more than serve the page. On the other, you have a robust and sophisticated postback that
exercises the entire life cycle and triggers all sorts of activity. There are times when you need the services of a
postback on an initial request.
Frameworks like Struts allow you to invoke an action as soon as a page is requested. JSF, on the other
hand, goes on the assumption that the first activity will be a user interaction, such as a button click. Lack of a
front controller makes it very difficult to implement functionality such as RESTful URLs, security checks, and
pre-render navigation routing. It also makes it tough for other frameworks to interact with a JSF application
since JSF does not expose a mechanism for invoking actions from a regular link. We will see in the next
section that Seam allows page actions, and navigation events that result from them, to occur on an initial
request.
NOTE
For those savvy JSF readers, I will admit that you can use a custom PhaseListener to execute code
prior to the Render Response phase. However, doing so requires a lot of redundant work on your part to
achieve what Seam gives you right out of the box. Not only do you have to instrument a lot of builder-plate
code, you also end up hard-coding the view IDs—easily the most irresolute component of the application—
into your compiled Java code. Seam allows you to externalize these mappings in a configuration file.
The back-loaded design of JSF leads us into the next problem, that the postback becomes the predominate
means of navigation in JSF.
Everything is a POST
The critics of JSF often point to its reliance on POST requests (i.e. submitting a form) as its biggest downfall.
On the surface, this may just appear to be a cosmetic problem, but in fact it is more severe. The browser is very
boorish when dealing with POST requests. If the user hits the refresh key after invoking a JSF action, the
browser might prompt them to make a decision about whether to allow the form to be resubmitted. That might
not be frightening to you and me, but this may cause customers a great deal of stress and paranoia. Consider
the fact that the user just submitted a very large order and the browser is now asking them if they want to
resubmit the form. If I were that customer, I would just force quit my browser at that point to prevent any
damage from being done. How would I know that the developer was smart enough to check for a duplicate
response (and that the QA team confirmed that the logic works)?
Here is an example of a navigation rule, as defined in the faces-config.xml descriptor, that would direct the
user back to the course detail page when the save button is clicked on the course editor page:
<navigation-rule>
<from-view-id>/CourseEdit.xhtml</from-view-id>
Licensed to Jaroslaw Gilewski <[email protected]>
<navigation-case>
<from-action>#{courseHome.update}</from-action>
<to-view-id>/Course.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
Submitting the form issues a POST request. When the browser comes to rest after rendering the
Course.xhtml, the location bar will still end in /CourseEdit.seam, appearing as if it is behind by one page. This
situation happens because JSF posts back to the same URL that rendered the form and the selects a different
template to render.
routes on the server-side according to the navigation rule. If this navigation rule were changed to perform
a redirect instead of a render, by adding a nested <redirect/> tag with the <navigation-case> element,
the result would be that the URL bar would reflect the new page. However, in the process, all of the request
values and parameters would be dropped.
The lack of flexibility in these two options puts the developer in a difficult place. The way to avoid these
problems altogether is to make heavy use of the session, to avoid lost data, and to perform redirects on every
navigation event, to keep the location bar updated and make bookmarking pages possible. However, using the
session precariously is dangerous because it can lead to memory leaks, concurrency problems, multi-window
complications, and over-eager state retention.
Rudimentary navigation rules
The other problem with the JSF life cycle is that the navigation model is very rudimentary. Navigation rules
defined in the faces-config.xml descriptor are declarative. They define circumstances under which a navigation
event should occur. The rules match against an originating view ID, the outcome of the action handler method
(i.e. the return value), and that method's EL signature. The rule dictates the navigation event that should take
place. But, it has no awareness of the context, such as the value of other scoped variables. All it can go on is
the return value of the method. You find that many applications grow out of these vague rules rather quickly.
An overly complex life cycle
Some people consider the JSF life cycle to be overly complex. This problem I will actually defend in JSF's
favor. I don't believe the problem is being accurately represented. The JSF life cycle is a good decomposition
of the logical phases that occur in just about any web application, written in any programming language, and
run on any platform. The problem is that the life cycle is missing some areas of coverage. The lack of an
action-based front controller, like Struts, is a perfect example. As a result of these oversights, developers have
been forced to use the framework in ways that it was not intended or bolt on haphazard solutions that seek to
fill these voids 23. Having to constantly compensate for these problems is where the pretense of complexity
manifests itself.
The vast majority of JSF's limitations come down to the fact that JSF has poor page-oriented support for
things like page-level security, pre-render actions, and RESTful URLs. Seam focuses heavily on solving this
problem by adding advanced page controls to the JSF life cycle, remedying the shortcomings just mentioned
and stretching its the capabilities beyond these expectations. Let's explore these page-oriented enhancements.
23
The On-Load module of the jsf-comp project (http://jsf-comp.sourceforge.net/components/onload/index.html) is a perfect
example of a haphazard solution to JSF's lack of a front-controller.
Licensed to Jaroslaw Gilewski <[email protected]>
3.3 Seam's page-oriented life cycle additives
In this section, you are going to learn how Seam weaves page orchestration into the JSF life cycle, saving you
from having to patch JSF with the missing page-related functionality yourself. This section introduces Seam's
page descriptor, pages.xml, which gives you a way to configure Seam's page-oriented enhancements, namely
more advanced navigation and page actions. By the end of this section, you will have all but forgotten about
faces-config.xml and the pain it has caused you.
3.3.1 Advanced orchestration with pages.xml
For as much control as the JSF life cycle gives to Seam, the JSF descriptor, faces-config.xml, offers no support
for extension. Its structure simply cannot accommodate additional configurations. Thus, Seam introduces a
new configuration descriptor to support its advanced page orchestration.
Seam's page descriptor offers a much wider range of navigation controls than what the faces-config.xml
descriptor is capable of supporting. Describing the page descriptor as a navigation configuration, though,
doesn't do it justice. It's really about the page and everything that happens around it, hence the term page
orchestration. The page descriptor can be used to:
define contextual navigation rules
generate messages and pass parameters on a redirect
invoke actions before rendering
enforce security restrictions and other prerequisites
control conversation boundaries
control page flow boundaries
control business process boundaries
control business process task boundaries
map request parameters to EL value bindings
bind context variables to EL value bindings and vice-versa
raise events
handle exceptions
The global page descriptor is named pages.xml and resides in the WEB-INF directory. It can be used to
configure an unbounded set of pages, each represented by a <page> element, as well as a handful of non-page
specific configurations.
The page-oriented configuration can also be split up into fine-grained configuration files. These individual
files serve a single JSF page. They are named by replacing the suffix of the JSF page template with .page.xml.
For example, if the page is Facility.xhtml, then the fine-grained page configuration file for that view ID would
be Facility.page.xml.
In seam-gen projects, there is a fine-grained page descriptor for each page that is generated by the seam
generate command. Given that Seam boasts about avoiding the use of XML, the abundance of page
descriptor files in a seam-gen CRUD application may seem contradictory to this goal. The configurations
could have been crammed into one file using multiple <page> elements. Even then, you may wonder why you
need to XML at all. The reason is that seam-gen projects are designed to support RESTful behavior, which is
best implemented with page descriptors. It's certainly possible to design a Seam application that works without
these page-oriented features. What you will discover, though, is that the page descriptors give you the best
control over the incoming request and is worth the XML you have to endure to get that control.
Licensed to Jaroslaw Gilewski <[email protected]>
Making the switch to Seam's navigation rules
If you decide to make the switch to Seam's page descriptor for defining navigation rules, which I
strongly recommend, then you can disable Seam's built-in RedirectFilter. The
RedirectFilter is designed to backport two of Seam's page-oriented enhancements, page
parameters and the conversation id, to standard JSF navigation rules defined in faces-config.xml,
specifically those that issue a redirect (i.e. have the nested <redirect/> element). The
RedirectFitler filter is removed from the delegation chain of the SeamFilter by adding the
following declaration to the Seam component descriptor, WEB-INF/components.xml:
<web:redirect-filter disabled="true"/>
You learn how to use the Seam component descriptor to configure built-in components in chapter 5.
Seam's page descriptor offers a plethora of additional features above what the faces-config.xml descriptor
has to offer. While the added palette of XML tags is important, the real benefit is its awareness of context,
emphasized in the first bullet point. Context—the current state of the system—is a common thread in Seam.
By leveraging context, navigation rules defined in Seam's page descriptor are cognizant of the big picture. In
other words, they are intelligent.
3.3.2 Intelligent navigation
Seam gives you a much more intelligent mechanism for orchestrating the transition between pages than what is
available with faces-config.xml. When defining navigation rules, you can take advantage of the following extra
controls:
use an arbitrary value binding to determine the outcome of an action rather than using the return method of
the action handler
make navigation cases conditional using a value binding expression
indicate how the conversation should be propagated through the transition
control page flows and business processes through the transition
add JSF messages before rendering or redirecting
add parameters to a redirect
The rules defined in Seam's page descriptor know what is going on and are able to make decisions not just
based on where the request is coming from, or what action was executed, but on what the objects in context—
or scope—have to tell.
Negotiating where to go next
Let's consider a hypothetical conversation that may occur between the navigation handler and the navigation
rules when taking the user through a very basic wizard for adding a new golf facility to the directory (if only
code could speak):
navigation handler: The user wants to register a new facility
navigation rule: Take the user to the /FacilityEdit.xhtml page
navigation handler: The #{facilityHome.persist} method was called from the
/FacilityEdit.xhtml page and it returned an outcome of "persisted"
navigation rule: Does the user want to add a course?
context variable #{facilityHome.addCourse}: yes
navigation rule: Take the user to the /CourseEdit.xhtml page
Licensed to Jaroslaw Gilewski <[email protected]>
navigation handler: The #{courseHome.persist} method was called from the
/CourseEdit.xhtml page and it returned an outcome of "persisted"
navigation rule: Does the user want to add a tee set?
context variable #{courseHome.addTeeSet}: no
navigation rule: Is there somewhere that we need to return the user?
context variable #{courseFrom}: Facility
navigation rule: Take the user to the /Facility.xhtml page and tell them they have finished
registering the facility
The critical piece of this negotiation are the context variables. They are used to make the navigation rules
conditional. You are going to learn about context variables in the next chapter. You may recognize them as
request or session-scoped attributes, though they go well beyond the limits of these two scopes. For now, just
think of them as representing the state of the application. Using them in conjunction with the extensive set of
controls in the pages.xml descriptor gives you fine-grained control of page-specific handling and transitions
between pages.
Putting words to action
Let's translate the previous example into navigation rules. Using JSF navigation rules defined in facesconfig.xml, we can only make a decision based on the signature of the action handler method and the outcome
of that method:
<navigation-rule>
<from-view-id>/FacilityEdit.xhtml</from-view-id>
<navigation-case>
<from-action>#{facilityHome.persist}</from-action>
<from-outcome>persisted</from-outcome>
<to-view-id>/Facility.xhtml</to-view-id>
<redirect/>
</navigation-case>
</navigation-rule>
#1
#2
#3
#4
#4
<Annotation #1> The view ID where the action was initiated
<Annotation #2> The method expression bound to the activated submit button
<Annotation #3> The return value of the action handler method
<Annotation #4> The view ID to render next, preceded by a redirect
The trouble with this rule is that it cannot be used to make a decision as to where to go next, whether to
return to the Facility.xhtml or move on to the CourseEdit.xhtml page. It's blind to the context. You could put
additional logic in the action handler and return finer-grained outcomes, but that reduces the flexibility of these
methods and is more laborious.
Using Seam's page descriptor, it's possible to define a contextual navigation rule. In listing 3.4, we
implement
the
conversation
presented
earlier
by
consulting
the
value
of
the
#{facilityHome.addCourse} context variable, assuming it is represented as a checkbox in the facility
editor. We also keep the user informed about why they are being directed to the course editor by adding a
message to the redirect.
Listing 3.4 Contextual navigation rule consulted after persisting a facility
<page view-id="/FacilityEdit.xhtml">
<navigation from-action="#{facilityHome.persist}">
<rule if-outcome="persisted"
if="#{facilityHome.addCourse}">
Licensed to Jaroslaw Gilewski <[email protected]>
#1
<redirect view-id="/CourseEdit.xhtml"/>
<param name="courseFrom" value="Facility"/>
<message severity="INFO">
Please enter course information for
#{facilityHome.instance.name}.
</message>
</redirect>
</rule>
<rule if-outcome="persisted"
if="#{!facilityHome.addCourse}">
<redirect view-id="/Facility.xhtml"/>
</rule>
</navigation>
</page>
#2
#3
#4
<Annotation #1> Checks if user wants to add course
<Annotation #2> Track where we started
<Annotation #3> EL value expression used in message
<Annotation #4> User does not want to enter a course
During a redirect, Seam can add both request parameters and JSF messages. The courseFrom parameter
is added explicitly to track how we arrived at the course editor. Seam also adds implicit request parameters
based on page parameter configuration. One such parameter is facilityId, which is added to the redirect
for the purpose of associating the facility just entered with the new course about to be entered. You will learn
about page parameters in the next section. In the JSF message that is added, you can see that it is possible to
use an EL value expression. In this case, the message includes the name of the facility. The EL is commonly
used to reference Seam components as shown here. You will learn more about accessing Seam components in
the next two chapters.
A similar set of navigation rules is added for the /CourseEdit.xhtml page to complete the flow. Here, we
define a navigation rule that decides whether to return the user to the course editor or to the facility detail page
after saving a course based on the value of the #{courseFrom} context variable:
<page view-id="/CourseEdit.xhtml">
<navigation from-action="#{courseHome.persist}">
<rule if-outcome="persisted"
if="#{courseFrom ne null}">
<redirect view-id="/#{courseFrom}.xhtml">
<message severity="INFO">
The data entry for #{courseFrom} is complete.
</message>
</redirect>
</rule>
<rule if-outcome="persisted"
if="#{courseFrom eq null}">
<redirect view-id="/Course.xhtml"/>
</rule>
</navigation>
</page>
If you are getting the sense that these navigation rules could be better written as a page flow, you are right.
In addition to declarative navigation rules demonstrated here, Seam also supports stateful page flows. Page
flows are tied closely with conversations. You will study both in chapter 7, which covers conversations and
stateful page flows. The purpose of this example is to show you that you can consult EL value expressions to
determine which navigation rule to apply. You might want to direct the user to a special page if they entered a
Licensed to Jaroslaw Gilewski <[email protected]>
private golf facility versus a public facility. The actual use case is going to depend heavily on the requirements
for your application.
Up to this point, you have relied on the action handler method to return an outcome value. The problem
with this requirement is that all your business methods must have contrived return values for the purpose of
performing navigation. These values likely won't make sense in terms of the business logic that they perform.
This problem becomes especially noticeable when you start attaching EJBs to your JSF pages. EJB
components are supposed to be business components. Using return values for the purpose of driving navigation
is just wrong. A far better way is to expect the business object to maintain state that can be consulted to
determine the appropriate navigation.
Let's ignore the return value of the persist() method, assuming that it doesn't tell us anything particular
about navigation (even though it does). Instead, the entity state (persisted, deleted, updated) will be made
available through the property lastStateChange. The evaluate attribute of the <navigation> element
accepts an EL value expression. This expression is evaluated to determine the outcome value that is matched
against the if-outcome attribute on the <rule> node for selecting the navigation rule to follow:
<page view-id="/FacilityEdit.xhtml">
<navigation from-action="#{facilityHome.persist}"
evaluate="#{facilityHome.lastStateChange}">
<rule if-outcome="persisted"
if="#{facilityHome.addCourse}">
...
</rule>
<rule if-outcome="persisted"
if="#{!facilityHome.addCourse}">
...
</rule>
</navigation>
</page>
In simple terms, the pages.xml steps the navigation capabilities of JSF up a notch. Before we get into the
features of the page descriptor, I want to quickly mention two UI components that Seam introduces that
complements the page oriented controls.
3.3.3 Seam UI command components
It was mentioned earlier that one of the main criticisms of JSF is that "everything is a POST." What this means
is that any link or button that is slated to execute an action listener when activated does so by submitting a
POST request, or, in other words, a form post. While this design is suitable for saving form data, it is not so
ideal for creating bookmarkable links. Seam offers two UI command components, one for creating links,
<s:link>, and one for creating buttons, <s:button>, that stand in for the corresponding command
components <h:commandLink> and <h:commandButton> from the standard JSF component set. The
Seam UI command components can perform all the same functions as the standard command components,
with one exception. They cannot submit form data. But then again, if you are submitting form data, then you
likely aren't concerned with the fact that a POST request is being issued; in fact, that's what you want.
You are going to see <s:link> and <s:button> used a lot in this manuscript. Of the two, <s:link>
is probably the most attractive because what it allows a user to do is right click and open the link in a new tab
or window, something that was not possible with plain old JSF. In addition to being able to execute an action
listener, the Seam UI command components can also navigate directly to a specific view ID, bypassing the
need to execute an action:
Licensed to Jaroslaw Gilewski <[email protected]>
<s:link view="/CourseList.xhtml" value="Course List"/>
What <s:link> has over <h:outputLink> in this case is that Seam will prepare page parameters
associated with that view ID as part of the URL, which you are going to learn about next. As you jump into the
next section, pay attention to how Seam frees JSF from the grips of postbacks and allows it to behave more
like an action-based framework.
3.3.4 Page parameters
Page parameters are a truly unique feature in Seam. They are used to bind request parameters to model
properties through the use of value expressions. The "model" can be any Seam component, whether it be a
presentation model or business domain model. Again, Seam does not force your hand. When a request for a
given JSF view is received, as identified by its view ID, each page parameter associated with that view ID is
evaluated upon entering the page—just prior to the Render Response phase. That value is then assigned to the
model property using its JavaBean setter method as mapped by the value expression.
In the native JSF life cycle, value binding expressions are only passed on to the underlying model during
the Update Model Values phase on a JSF postback. Seam brings this feature to initial requests by allowing
non-form parameters to bind to properties on a model object as well.
Let's consider the course detail page, Course.xhtml, from the Open 18 directory application generated in
the last chapter. Assume that there is an incoming request for a Course whose identifier is 1:
http://localhost:8080/open18/Course.seam?courseId=1
Prior to the rendering of the Course.xhtml page, the value of the courseId query string parameter is
assigned to the courseId property on the courseHome component as a result of the following page
parameter assignment in pages.xml:
<page view-id="/Course.xhtml">
<param name="courseId" value="#{courseHome.courseId}"/>
</page>
INFO
For now, you can think of components and managed beans as being synonymous. The courseHome
component is the action controller for Course entity pages.
Alternatively, you could specify this stanza in the fine-grained configuration file, Course.page.xml, which
sits adjacent to the Course.xhtml view template. The fine-grained configuration file is intended to serve only a
single view ID. Therefore, the view-id attribute can be excluded in the declaration:
<page>
<param name="courseId" value="#{courseHome.courseId}"/>
</page>
For the remainder of the chapter, I will always include the view-id attribute for clarity.
The <page> node is used to designate which pages the configurations apply to. The view-id attribute
can either be an exact match or it can match multiple view IDs by using a wild card character (*). In this case,
we are configuring the /Course.xhtml JSF view.
Licensed to Jaroslaw Gilewski <[email protected]>
Home and Query components
Throughout this chapter, you have seen a lot of references to components that end in either Home
(e.g. courseHome) or List (e.g. facilityList). Components ending in Home extend from the
EntityHome class and components ending in List extend from the EntityList class, both which
are part of the Seam Application Framework. The EntityHome class is used for managing the
persistent state of single entity instance, while the EntityQuery component manages a JPQL query
result set. Chapter 10 provides comprehensive coverage of the Seam Application Framework and how
to take advantage of these two components.
Page parameters aren't limited to accepting values. They are bidirectional. In addition to taking request
parameters, they are also used to rewrite links by reading the value of the mapped value bindings and
appending them to the query string. For instance, on the course editor page, there is a link to cancel and return
to the course detail page. The courseId parameter is automatically appended to the page by reversing the
page parameter declaration shown above:
/Course.seam?courseId=1
This rewriting happens in the following cases:
the URL generated by Seam command tags (<s:link> and <s:button>)
JSF postbacks from UICommand components (i.e. <h:commandLink>)
navigation redirects defined in the page descriptor (pages.xml or *.page.xml)
For links and redirects, the parameters that are applied to the URL are read from the configuration for the
target view ID. JSF forms post back to the same page, so the target view ID is the same as the page that
rendered the form. It's a bit trickier with <s:link> and <s:button> since the target page can be different
than the current page. For example, consider the following link:
<s:link view="/Course.xhtml" value="View course"/>
The page parameters will be read from the Course.page.xml page descriptor, even if this link is included
on a different page, such as CourseList.xhtml.
Let's explore how the page parameters and the Seam UI component exchange request parameters.
The search form paradox
When would you want to use page parameters to pass on values? Consider the paradox of providing both
search capabilities and sorting for a data set. I can almost guarantee you have been in this seat before. When
the user searches, you need to retain the sort and pagination, and when they sort or paginate, you have to retain
the search. This leads to heavy use of hidden form elements within a form that wraps the entire page or a very
liberal use of JavaScript to move values between forms. One way or another, it becomes a tangled mess and
leads to many headaches.
Page parameters in Seam make this situation trivial. Consider the golf course facility listing page in the
same application. The facilityList component is the action class that manages the collection of courses.
The page parameters are defined as:
<page view-id="/FacilityList.xhtml">
<param name="firstResult" value="#{facilityList.firstResult}"/>
<param name="order" value="#{facilityList.order}"/>
<param name="from"/>
<param name="name" value="#{facilityList.facility.name}"/>
Licensed to Jaroslaw Gilewski <[email protected]>
<param name="type" value="#{facilityList.facility.type}"/>
<param name="address" value="#{facilityList.facility.address}"/>
<param name="city" value="#{facilityList.facility.city}"/>
<param name="state" value="#{facilityList.facility.state}"/>
<param name="zip" value="#{facilityList.facility.zip}"/>
<param name="county" value="#{facilityList.facility.county}"/>
<param name="country" value="#{facilityList.facility.country}"/>
<param name="phone" value="#{facilityList.facility.phone}"/>
<param name="uri" value="#{facilityList.facility.uri}"/>
<param name="description"
value="#{facilityList.facility.description}"/>
</page>
For any Seam command component on the page (<s:link> or <s:button>), the expressions are
evaluated during page rendering and the resolved value and corresponding parameter name are combined and
added to the query string of the link. If you searched for PUBLIC facilities and then sorted on the name of the
facility, the URL would appear as:
/FacilityList.seam?zip=&phone=&state=&type=PUBLIC&uri=&cid=25&country=
[CA]&city=&order=name+asc&county=&address=&description=&name=
The link used to sort is built using the link component tag from the Seam UI component set (trimmed
down for clarity):
<s:link value="name">
<f:param name="order"
value="#{facilityList.order=='name asc' ?
'name desc' : 'name asc'}"/>
</s:link>
#1
#1
<Annotation #1> The order clause is divided into property name and sort direction
The <s:link> tag automatically targets the current view ID if one is not explicitly provided. Both the
type and order parameters are maintained in the URL generated by this link. When you submit the search
form you don't see these parameters in the browser's location bar because they are passed through the UI
component tree instead. Since page parameters transcend forms, being propagated because of their association
with a given view ID, it isn't necessary that the page elements be children of the same form. When a search is
executed, the sort order is maintained and when a sort is issued, the search parameters are maintained. All of
this happens without any custom URL rewriting on your part. Another place page parameters are extremely
valuable is on a navigation redirect.
Surviving redirects
As I pointed out earlier, JSF normally drops all previous data when issuing a redirect. Page parameters offer a
way to retain these values across the redirect since they are automatically appended to the URL on account of
being associated with the requested view ID.
Parameter peace of mind
By now you are probably getting excited about these page parameters. But here is the real kicker. Page
parameters can also register converters and validators. That means you are not blindly stuffing request
parameters into properties on your model. You get the peace of mind of the conversion and validation process
just as you would on a postback. Let's assume that we need to enforce some validations for our facility search.
Licensed to Jaroslaw Gilewski <[email protected]>
In the following excerpt, the value of the state parameter is checked using the validator with a validator-id of
org.open18.StateValidator. The validator for the facility type is retrieved from a value expression,
#{facilityTypeValidator}, which might be satisfied by a JSF managed bean or a Seam component,
either of which must implement the JSF validator interface:
<page view-id="/FacilityList.xhtml">
...
<param name="state" value="#{facilityList.facility.state}"
validatorId="org.open18.StateValidator"/>
<param name="type" value="#{facilityList.facility.type}"
validator="#{facilityTypeValidator}"/>
...
</page>
You can follow the same pattern for converters except that they must implement the JSF converter
interface. For instance, you may want to define a converter for parsing a phone number and giving the user a
better chance of locating the facility, without getting tripped up over errant symbols in the number.
It's also possible to mark a page parameter as required. Such a feature will come in handy for the facility
detail page where a facilityId is needed to pull the record:
<page view-id="/Facility.xhtml">
<param name="facilityId" value="#{facilityHome.facilityId}"
required="true"/>
</page>
The only downside of adding the required flag is that it merely generates a JSF error message. It does not
prevent the page from being displayed. There are other ways to handle this problem which we will explore
later.
Page parameters essentially emulate a form submission on an initial request. But what makes them
especially valuable is that they are also transparently propagated through JSF forms so they are consistently
propagated.
From query string to page scope and back
Here's something easy to overlook the first time you encounter page parameters. They don't necessarily have to
refer to a value binding expression. When the value is left off of the declaration, the request parameter is
simply placed into the page scope under the same name. Valueless parameters are useful when you just need to
carry values around, but don't want to map them into a model object. You can retain the source page using the
following declaration:
<page name="returnTo"/>
It is then possible to make decisions when creating navigation buttons based on this value:
<s:button value="Cancel"
view="/#{empty returnTo ? 'FacilityList' : returnTo}.xhtml"/>
What about the case when you need to execute an action on an initial request, not just apply request
parameters to the model? Executing code prior to rendering a page is the forte of Seam's page actions.
Licensed to Jaroslaw Gilewski <[email protected]>
3.3.5 Page actions: execute me first!
Page actions are what drew me to Seam and encouraged me to stick with JSF. In my mind, they are the saving
grace of JSF. More times than not, information is retrieved by pulling up a URL in the browser, or following a
link from another site, not from pressing a button on the page. However, the JSF specification focuses heavily
on the latter use case. JSF has the groundwork in place for RESTful URLs, it just had to be implemented,
which is exactly what Seam does.
RESTful URLs
Without page actions, you have to think about the world in terms of form submissions. That is a stark contrast
with the direction the world is actually taking, which is to rely on a REST architecture style—or the more
pertinent term, RESTful URLs. Once you have the power of page actions at your finger tips, any request can
be made to perform pre-render logic that retrieves and prepares data and then renders the appropriate page to
view it. In this case, there isn't an automatic mapping between the URL that is requested and the page template
that is to be rendered, like the default behavior of JSF.
What is a RESTful URL?
A RESTful URL is one that permanently represents a resource that is returned by the server when that
URL is requested by a client, typically a browser. A resource is any item of interest, such as information
about a golf course or a golfer's profile. The URL contains all the information necessary to pull up a
unique resource (citing the read operation). REST is an acronym for Representational State
Transfer. The state is the resource (the golf course information or golfer's profile) as
it exists on the server, typically stored in a database. The representation of that
state is the URL that refers to it. When the URL is requested, the state is transferred
from the server to the client 24. A link to a RESTful URL is known as a "permalink", short for
permanent link.
In essence, page actions tack a front controller onto the JSF life cycle. Page actions behave similarly to
Struts actions. In either framework, the action is selected by the controller based on a URL-to-action mapping
and subsequently invoked prior to any page rendering. The front controller is the design pattern used by actionbased frameworks, including Struts, WebWork (Struts 2), and Spring MVC. You can take comfort in the fact
that you don't have to abandon your action-based way of thinking or the ability to serve RESTful URLs when
you make the move to JSF, as long as you allow Seam to provide the functionality.
Page actions are specified using method binding expressions. There are two ways to associate a page
action with a view ID. The action can be defined on a page node in Seam's page orchestration descriptor,
pages.xml, either in the action attribute on the <page> node or in a nested <execute> node. The action
can also be specified in the action attribute on a Seam UI command component, <s:button> or
<s:link>. You may wonder how the latter can be considered a page action since it is triggered by a user
action just like how the UICommand components work. However, the Seam command components construct
URLs that issue an initial request (not a postback) and therefore contain all the information necessary to trigger
an action handler method. If the URL created by one of these components is bookmarked, the action is
executed despite the user not clicking on a button or link.
One of the most common use cases for page actions is preloading data prior to rendering a view. To satisfy
this use case, it is most appropriate to use the pages.xml descriptor to associate the action method with the
view ID since the idea is to handle all requests for the resource, even invalid ones.
24
http://www.xfront.com/REST-Web-Services.html
Licensed to Jaroslaw Gilewski <[email protected]>
Preloading data
Suppose you wanted to preload the list of golf courses before rendering the directory listing. Doing so will
allow you to trap possible errors that may occur when retrieving the results from the database before the page
begins rendering.
To execute an action before rendering, you specify a method binding expression in the action attribute
of the <page> node in any page descriptor. The page parameters will be applied to the model before the page
action executes. Here, the Facility result list is fetched in the page action, perhaps eagerly fetching lazy
associations on the Facility entity as part of the query:
<page view-id="/FacilityList.xhtml"
action="#{facilityList.preloadFacilities}">
...
</page>
Now let's assume that you want to bring up the list of facilities that are in the home state of the user if the
user is authenticated. You will learn about authentication and security in chapter 11. Assuming that there is a
mechanism
available
to
access
the
current
user's
information,
the
#{facilityList.applyRegionalFilter} method will apply it to the search parameters, but only if an
active search is not detected so as not to interfere with it. The facilities will then be preloaded as before since
the actions are executed in the order they appear in the page node. To apply multiple page actions to a single
page node, you use nested <action> nodes:
<page view-id="/FacilityList.xhtml">
<action execute="#{facilityList.applyRegionalFilter}"
if="#{identity.loggedIn and !facilityList.searchActive}"/>
<action execute="#{facilityList.preloadFacilities}"/>
...
</page>
Having the ability to execute a method prior to rendering is only half the benefit. The true value of page
actions is their ability to support declarative navigation. This is one feature you don't get by putting the prerender logic in the beforePhase() method of a JSF PhaseListener.
3.4 Combining page actions with navigation
The navigation that follows a page action works just like the navigation used after the Invoke Application
phase on a JSF postback. Thus, Seam's page actions can be combined with its intelligent navigation
capabilities in order to make decisions about how to direct the user in the event that a page action diverts the
user from the requested page. If you perform a redirect—not a <render>—in the navigation rule, then it is
possible that the ensuing page will also use a page action. Thus, it is possible to chain actions prior to
rendering a page.
The most obvious use for page actions used in conjunction with navigation is to validate that the URL
being requested is legitimate and that the page can be rendered successfully.
3.4.1 Sanity checking a request
Consider what commonly happens when a user requests the course detail screen directly, perhaps from a
bookmark. The course is looked up by id using the value supplied in the courseId request parameter. What
happens when the requested courseId is empty or no such id exists in the course table? JSF is notoriously
Licensed to Jaroslaw Gilewski <[email protected]>
awful at handling this situation. Because the JSF controller works in a passive manner, it doesn't figure out that
the request is missing information until half way through the rendering process. Once the page begins
rendering you cannot reroute the user to a more appropriate page, even when it becomes apparent that the
target data is absent—unless you throw an exception. You end up displaying a page with blank values and
other potential rendering glitches.
Page actions to the rescue! Let's implement a method validateEntityFound() that verifies that a
course can be found before rendering begins:
public String validateEntityFound() {
try {
this.getInstance();
}
catch (EntityNotFoundException e) {
return "invalid";
}
return this.isManaged() ? "valid" : "invalid";
}
The isManaged() method tells us whether or not the entity was found in the database, as opposed to a
new, transient instance being created. Behind the scenes, the getInstance() method is using the
courseId value that is assigned to the courseHome component by the page parameter to lookup up the
corresponding entity instance in the database. Just as form element bindings are used to provide values for
actions that execute in the Invoke Application phase of a JSF postback, page parameters are used to provide
values for page actions.
Of course, if things don't go well, and the outcome value is "invalid", then we need to perform navigation.
Navigation rules are invoked after executing a page action just as they are when an action is invoked on a JSF
postback. Here, we redirect the user to the /CourseList.xhtml JSF view if the course cannot be successfully
loaded, letting them know with a warning message why they were redirected:
<page view-id="/Course.xhtml"
action="#{courseHome.validateEntityFound}">
<navigation from-action="#{courseHome.validateEntityFound}">
<rule if-outcome="invalid">
<redirect view-id="/CourseList.xhtml">
<message severity="WARN">
The course you requested does not exist.
</message>
</redirect>
</rule>
</navigation>
</page>
Your Course.xhtml page is now protected from bogus requests. You may be wondering about the
CourseEdit.xhtml page. You could apply the same logic for that page as well, by applying the same
configuration to the /CourseEdit.xhtml view ID. However, just to stretch the capabilities of the page node a bit,
perhaps to show off a bit, let's combine the two view IDs together and use a complex conditional to determine
when the validation should be applied. Here, all view IDs that begin with /Course are matched and the
validation is applied to the detail page for all requests and the editor page only if the courseId property is
set:
Licensed to Jaroslaw Gilewski <[email protected]>
<page view-id="/Course*">
<action execute="#{courseHome.validateEntityFound}"
if="#{view.viewId eq '/Course.xhtml' or
(view.viewId eq '/CourseEdit.xhtml' and
courseHome.courseId != null)}"/>
<navigation from-action="#{courseHome.validateEntityFound}">
<rule if-outcome="invalid">
<redirect view-id="/CourseList.xhtml">
<message severity="WARN">
The course you requested does not exist.
</message>
</redirect>
</rule>
</navigation>
</page>
Note that the navigation rules are consulted after each action is executed. If the outcome of a page action
matches a navigation rule, the remaining page actions will be short-circuited. So the only way to apply
multiple page actions is if all but the last one do not trigger navigation.
As you can see, it is possible to create fairly sophisticated rules about when to invoke page actions and to
what pages they apply. But you haven't seen anything yet. I'm going to blow you away with this next example.
As it turns out, the view IDs used in the page declarations don't actually have to resolve to JSF templates,
allowing us to do some fancy preprocessing of the URL before the page is rendered.
3.4.2 Search engine friendly URLs
What makes sense to the developer doesn't always make sense to the end user or a search engine. That's why
having a layer of abstraction between the URL requested in the browser and the template that is ultimately
rendered is sometimes desirable. The goal of this section is to turn URLs that look like
/CourseEdit.seam?courseId=15 into /course/edit/15. I will show you two ways that you can approach this
problem in Seam. The first is with page actions and the second is using a third-party rewrite filter.
Preprocessing view IDs
The view-id attribute specified on the <page> node in the page descriptor is not a done deal. Through the
use of page actions, it is possible to divert the request to a different view ID, as you saw in the last section.
What you may not realize, though, is that the view ID can be virtual, meaning it does not have to resolve to a
file in the web directory, or at least not right away. The view ID is more of an intent to render a view until the
page actions are done with it. Only then will it be used to load the view template. What that means for us is
that we can use a fuzzy matching pattern (e.g. /course/*) in the view-id attribute to trap requests and perform
preprocessing on the view ID.
The ability to map page nodes to arbitrary view IDs makes it possible to accept RESTful URLs that are
friendly to search engines and integrations with other applications and frameworks.
Why are search engine friendly URLs desirable?
To help people find information on your site, and to improve your page rank, you want to ensure that
search engines understand your website or application. One way to optimize a site for search engines
is by making the links that point to other pages of the site self describing. The URL should contain
words and characters that provide clues as to what resource will be displayed when the URL is
Licensed to Jaroslaw Gilewski <[email protected]>
requested. Search engines can then learn this pattern and provide search results that associate a
direct link to a resource that matches the search terms.
All the relevant information should also be in the URL path. Putting information in the query string
makes it difficult for the search engine to sort between essential and non-essential information and
may even cause important information about the resource to be truncated. Statistics engines are
notorious for wreaking such havoc on URLs. So having search engine friendly URLs also means having
statistics engine friendly URLs. It also helps generate more accurate statistics since you get the
granularity of showing the requests for individual resources rather than just the servlet path that
serves them.
Search engine friendly URLs are most desirable because they are simple and they are technology
agnostic. If you implement your application in Struts and spread around links that ended in .do, then if
you later switch to JSF, all of the existing links instantly become invalid, likely resulting in a 404 errors
for the user. You now have the job of broadcasting the new links that end in .jsf (or .seam). Instead,
what you want to do is make the URL about the resource, not about the framework that is serving it.
To implement search engine friendly URLs, the request parameters required to pull up the resource need to
be moved into the URL itself, away from the query string. An example of a search engine friendly URL that
shows the details of a golf course in the Open 18 application might appear as:
http://localhost:8080/open18/course/view/13.seam
The view ID that corresponds to this URL is /course/view/13.xhtml. Obviously, this request isn't going to
map directly to a JSF view because we do not have this template in the web directory. Instead, we are going to
use a fuzzy pattern in the view-id attribute of a page node to map this view ID, and ones like it, to a page
action where we can do some preprocessing:
<page view-id="/course/*">
...
</page>
Let's implement a page action method on courseHome that we can use to map to this page node. The
method needs to look for two things, an action and an entity id. The action is either view or edit. The entity id
will be used to lookup the record in the database to be viewed or edited. Listing 3.5 shows the
parseRestfulUrl() method on the courseHome component.
Listing 3.5 A page action that interprets a REST-style URL and sets the entity identifier accordingly
public String parseRestfulUrl() {
String viewId =
FacesContext.getCurrentInstance().getViewRoot().getViewId();
try {
String[] segments = viewId.split("/");
assert segments.length >= 2;
String action = segments[segments.length - 2];
String resourceId = segments[segments.length - 1];
resourceId =
resourceId.substring(0, resourceId.lastIndexOf('.'));
Long courseId = Long.parseLong(resourceId);
setCourseId(courseId);
Licensed to Jaroslaw Gilewski <[email protected]>
//1
//2
//3
//4
return validateEntityFound()
.equals("valid") ? action : "invalid";
} catch (Exception e) {
return "invalid";
}
//5
}
<Annotation #1> view ID matched in the <page> node
<Annotation #2> view or edit
<Annotation #3> course identifier
<Annotation #4> strip .xhtml suffix
<Annotation #5> validate it exists
The next step is to register this page action method with the <page> node and define navigation rules that
will reroute the request to the real view ID that will ultimately render the page. If you don't want the location
bar to change—you want it to show the RESTful URL—then you use a <render> tag in the navigation rather
than a <redirect> tag. Here is the complete page orchestration logic:
<page view-id="/course/*" action="#{courseHome.parseRestfulUrl}">
<navigation from-action="#{courseHome.parseRestfulUrl}">
<rule if-outcome="view">
<render view-id="/Course.xhtml"/>
</rule>
<rule if-outcome="edit">
<render view-id="/CourseEdit.xhtml"/>
</rule>
<rule if-outcome="invalid">
<redirect view-id="/CourseList.xhtml">
<message severity="WARN">
The course you requested does not exist.</message>
</redirect>
</rule>
</navigation>
</page>
There you have it, a search engine friendly, RESTful URL for accessing the course detail and editor pages.
The page action, in this case, is just directing traffic, very similar to how a Struts action might work.
INFO
You may notice that if you implement this logic in a seam-gen project, the stylesheet will not load properly.
In the master template, view/layout/template.xhtml, the stylesheet is being referenced using a relative path.
You will need to change the href attribute to include the fully qualified path for it to be resolved properly:
/#{facesContext.externalContext.requestContextPath}/stylesheet/theme.css
It's a good idea to make this change, regardless.
I would not be surprised if you shook your head a few times when looking at the parseRestfulUrl()
method and thought to yourself, "That is way too convoluted!". I will agree with you that having to put this
logic all over your code base would be messy. The point of showing you this logic was to give you an idea of
what is possible. We are now going to look at a simpler approach that does not involve Seam page actions.
Rewriting URLs
When creating friendly, RESTful URLs, it is best not to involve the application at all. The whole idea behind
having these so-called friendly URLs is that they form a layer of abstraction over the inner workings of the
Licensed to Jaroslaw Gilewski <[email protected]>
application. Fortunately, there is a very simple, yet powerful third-party servlet filter for handling this type of
work, aptly named UrlRewriteFilter. If you are familiar with Apache's mod_rewrite filter, the premise
behind this servlet filter is the same.
At the time of writing, Seam does not include a built-in filter for enabling and configuring the
UrlRewriteFilter. There was a licensing conflict (GPL) that prevented it from being included in Seam at
one point, though it is expected to be in a future Seam release. Until then, it is necessary to add it to the
web.xml descriptor. You likely will want to put this entry below the SeamFilter:
<filter>
<filter-name>UrlRewriteFilter</filter-name>
<filter-class>
org.tuckey.web.filters.urlrewrite.UrlRewriteFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>UrlRewriteFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
You will also need to modify the build to include the urlrewritefilter.jar file in the deployment archive.
Please see appendix A for details on how to add a library to the build.
The rewrite rules are defined in the WEB-INF/urlrewrite.xml descriptor. The rules are defined using either
Perl 5 regular expressions or wildcards. It is possible to capture match references and pass them on to the new
URL that is constructed. Listing 3.6 shows the equivalent configuration for friendly course URLs that was
performed using page actions.
Listing 3.6 URL rewrite configuration for friendly URLs
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE urlrewrite PUBLIC
"-//tuckey.org//DTD UrlRewrite 3.0//EN"
"http://tuckey.org/res/dtds/urlrewrite3.0.dtd">
<urlrewrite>
<rule>
<from>^/course/view/([0-9]+)$</from>
<to last="true">/Course.seam?courseId=$1</to>
</rule>
<rule>
<from>^/course/edit/([0-9]+)$</from>
<to last="true">/CourseEdit.seam?courseId=$1</to>
</rule>
<rule>
<from>^/$</from>
<to>/home.seam</to>
</rule>
</urlrewrite>
<Annotation #1> match example: /course/view/15
<Annotation #2> match example: /course/edit/15
<Annotation #3> matches the root URL
Licensed to Jaroslaw Gilewski <[email protected]>
//1
//2
//3
The UrlRewriteFilter is quite capable of slicing and dicing the URL in whatever creative ways you
can think of to write regular expressions. It is not only useful for creating friendly URLs, but can help when
migrating a site to a new structure or extension, serve custom resources based on the user-agent (browser), and
for trimming long or complicated URLs. In addition to the source code for this book, you can see examples of
the UrlRewriteFilter in use in the seambay and wiki example projects under the examples directory in
the Seam distribution.
The only limitation of the search engine friendly URLs is that the JSF and Seam UI component tags that
create links are not going to know about this format, so you will need to assemble the URLs in links manually.
But again, it could be an opportunity for a new Seam component tag!
You have seen first hand that page actions are quite powerful. Still, they do require some work. That is
why Seam includes some basic pre-render functionality that you are likely going to need in every application.
Let's take a look.
3.4.3 Built-in page actions
There are certain page-oriented features that every application needs, such as restricting unauthenticated or
unauthorized users from accessing protected pages. Rather than forcing you invest time in a solution for every
application, Seam offers a handful of built-in page actions for handling this work.
If you have ever tried to use a servlet filter-based security mechanism, such as Spring Security, to secure
JSF pages, you were likely frustrated by the fact that it doesn't do a very good job of securing JSF pages
because it is not granular enough. Securing JSF pages is very easy if done at the proper level, just prior to the
Render Response phase. Page actions are the perfect fit. To restrict access to users that are not authenticated,
you simply add the login-required attribute to the page definition:
<page view-id="/CourseEdit.xhtml" login-required="true"/>
You can also enforce custom security rules using the <restrict> element. If you are using a JAAS
principle, the configuration of which is covered in chapter 11, and you want to enforce role-based security
according to the return value of isUserInRole(), then you can do so using the built-in EL function named
s:hasRole in the <restrict> element. Let's assume that there is a role named "edit" that is used to grant
privileges to modify records. You can prevent anyone that is not assigned the edit role from modifying a
course with the following page declaration:
<page view-id="/CourseEdit.xhtml" login-required="true">
<restrict>#{s:hasRole('edit')}</restrict>
</page>
In a similar fashion to how you require the user to be authenticated, you can enforce that a conversation is
already in effect by adding the conversation-required boolean attribute to the page declaration. Both of
these tags can also be added to the root <pages> node if you want either of them to apply to all pages. If
either of these two conditions fail, Seam provides the login-view-id and no-conversation-view-id
attributes that indicate where to direct the user. We are going to look at security and conversations in much
greater depth later in the book. Just take note that these are features of Seam that you can configure using
pages.xml.
Now that you are well versed in Seam's pages.xml configuration, it's time to look at how Seam ties this
functionality into the JSF life cycle and what else it adds in the process.
Licensed to Jaroslaw Gilewski <[email protected]>
3.5 The JSF life cycle with Seam
The JSF life cycle under the direction of Seam is more well-balanced than its intrinsic counterpart. By that, I
mean that in Seam's version of the JSF life cycle, the initial request processing is just as full-featured as the
postback processing. You have seen many of the ways in which Seam expands the activity in the initial request
by applying page-oriented features. These new features include parameter mappings, front controllers, and
request routing. In this section, we are going to (quickly) step through the JSF life cycle again, this time
observing the points where Seam adds its enhancements.
3.5.1 Phase listeners versus servlet filters
Seam is able to work very closely with JSF thanks to the granularity offered by a phase listener. Few other
frameworks offer such a deep view into the execution processes. You will often see frameworks using servlet
filters to perform such tasks, such as Spring Security. The problem with filters is that they work at too high of
a level and lack the intimate knowledge of what is going on inside of the request. Without this context, it
becomes very difficult to make the correct decision. Under some circumstances, it is impossible for a filter to
influence the execution path. Seam's phase listener, SeamPhaseListener, can really get under the skin of
every JSF request by intercepting the life cycle and taking it on slight detours to offer functionality such as the
page-oriented features previously described, as well as other supplemental business. Let's examine when these
activities take place.
3.5.2 Stepping through the augmented life cycle
Before we get started with the revamped JSF life cycle, I will warn you that the amount of activity that
happens in the Seam life cycle is daunting. Trying to cover it all on a single walk through would be difficult to
manage. Therefore, I am going to give you a general idea of what happens, emphasizing key points. Some of
the features, such as conversations and page flows, I am only going to touch on because they warrant their own
chapters. Think of this run through as the highlights reel. It's going to give you a broad overview and plenty to
be excited about. Are you ready to roll the tape?
The initial request, Seam-style
Once again, we are going to examine the life cycle as it processes the initial request. I promise that the story is
far more interesting this time around. Table 3.2 walks through the tasks that Seam wraps around the JSF
phases on the initial request.
Table 3.2 A general overview of the tasks that Seam incorporates into the JSF life cycle on an initial request (JSF phases shown in
bold; double line signifies transition from Restore View to Render Response)
Step
Task
Description
1
Initial request
The life cycle begins with a GET request for a URL captured by the JSF servlet.
2
Start JTA transaction
If transaction management is enabled and JTA transactions are being used, the entire request
is wrapped in a JTA transaction.
3
Restore View
On an initial request the Restore View phase merely creates an empty component hierarchy.
4
Restore or initialize
conversation
Restore the long-running conversation if active. If there is a problem restoring it due to a
timeout or other condition, advance to the no conversation view. If a long-running
conversation does not exist, initialize a temporary conversation. Temporary conversations are
used to retain event-scoped data across navigation redirects.
5
Handle conversation
propagation
Determine from the request parameters if a long-running conversation is to begin, end, be
joined, or remain untouched. Conversations will be covered in more depth in a later chapter 7.
6
Validate page flow
If a stateful page flow is in use, it is validated to ensure the user is not attempting to request a
page out of sequence. If the request is not compliant, appropriate action is taken.
Licensed to Jaroslaw Gilewski <[email protected]>
7
Process page parameters
The parameters associated with this view ID are read from the request, converted, validated
and hung out to dry in the view root.
8
Enforce login, long-running
conversation, and
permissions
If a login is required to view this page, the user is redirected to the login page. Once
authenticated, the process of rendering this page is resumed. If a long-running conversation is
required to view this page and one does not exist, the user is forwarded to the no conversation
view. If the user does not have appropriate permissions to view this page, a security error is
raised.
9
Apply page parameters
If all checks pass, the page parameters are applied to the model via the value bindings.
Parameters without value bindings are stored in the page context.
10
Start non-JTA transaction
If transaction management is enabled and entity or hibernate transactions are being used, the
remaining response is wrapped in a resource local (non-JTA) transaction.
11
Emulate the Invoke
Application phase
The life cycle temporarily takes on the signature of the Invoke Application phase. Seam does
this so the JSF infrastructure will response appropriately to actions as if they were triggered by
a postback.
12
Enforce login, long-running
conversation, and
permissions
The restrictions are once again applied. This step occurs twice because there are some
execution paths that will skip steps 1 – 9 when rendering a given view ID after a navigation
event.
13
Select data model row
If s:link or s:button resided within a row generated by iterating over a UIData component
backed by a JSF DataModel, advance the pointer to the selected row in the model. Notice
that a postback is not required for this selection.
14
Execute page actions
Each page action associated with the view ID are executed. This includes actions that are
passed through an s:link or s:button tag. Navigation rules are applied after each execution. If
more than one navigation rule is applicable, the one with the highest priority will be used.
15
Commit transaction
If transaction management is enabled and a transaction is active, it is committed. A new
transaction is opened to prepare for the rendering of the response.
16
Migrate JSF messages
Migrate any Seam-managed FacesMessages stored in the conversation to the
FacesContext. The messages have survived any redirects at this point.
17
Prepare conversation
switching
Conversations can be selected through a UI component. This step remembers the description
and view ID for the current page. The stack must be prepared at this point in the event the life
cycle is short-circuited before the Render Response phase.
18
Store conversation
The conversation is stored in the session to prepare it for a subsequent request.
19
Render Response
The response is rendered by reading the JSF page template and encoding it to the generated
markup (typically XHTML). The component hierarchy is stored in either the client or the server
to be restored on a postback.
20
Commit transaction
If transaction management is enabled, commit the transaction that was active during the
rendering of the response.
21
Prepare conversation
switching
Conversations can be selected through a UI component. This step remembers the description
and view ID for the current page. Updates the stack prepared prior to the Render Response
phase.
22
Cleanup conversation
Either end the temporary conversation or update the last request time of the long-running
conversation.
You can breathe again, the process is over. This chart doesn't cover every last detail of the steps added by
Seam in the JSF life cycle, but it comes pretty close. There are two important differences between the initial
request in the Seam life cycle and the initial request in the plain JSF life cycle:
transactions are started and stopped automatically if they are enabled
page-oriented events are triggered and restrictions applied as configured in pages.xml
The global transaction that Seam wraps around each request is an important part of what makes Seam
applications so practical. You can safely perform persistence operations in your page actions and action
handlers without having to worry about beginning and committing a transaction. Seam takes care of it for you.
If you prefer to push your persistence logic to other layers, you are still able to take advantage of this global
Licensed to Jaroslaw Gilewski <[email protected]>
transaction as it is ever present. You won't see much of this feature, however, until you get to chapters 8 – 10,
which deal heavily with transactions and persistence.
Lazy associations in the view
One of the key benefits of Seam is its ability to allow lazy associations in JPA (or Hibernate) entities to
be traversed in the view without fear of getting a LazyInitializationException. In the past,
this feature has been enabled by using the Open Session in View pattern. Seam takes a smarter
approach by binding the JPA EntityManager (or Hibernate Session) to the conversation scope
and wrapping a transaction around the Render Response phase. Lazy associations are just one of the
many features in Seam that just work. You will learn more about the conversation-scoped persistence
manager and how it contrasts with the Open Session in View pattern in chapter 9.
After seeing close to two dozen steps on the initial request, you may be dreading the postback. Well, fear
not, because there isn't much to cover. Seam performs most of its work around the Restore View and Render
Response phases.
Less work required on postback
This section is going to be extremely short because not much different happens on a postback than what has
already been described in both Seam's initial request and the additional phases in the standard JSF postback.
Keep in mind that all the behavior from the initial request still applies on postback. When considering the
differences in how the Seam life cycle handles the extra phases added during postback as compared to the
plain JSF life cycle, the only major addition is that Seam wraps the Invoke Application phase in a transaction,
committing it once the phase is complete. The purpose of this transaction closely resembles the transaction that
surrounds the handling of the page actions. Once again, Seam takes the tedium of dealing with transactions out
of the picture until we are absolutely ready to start fine tuning them.
The Seam life cycle introduces quite a number of new features on top of what was already available with
the JSF life cycle by hooking into its phase listener architecture. The most blatant deficiencies in JSF are the
initial request handling and the navigation capabilities, both of which Seam corrects, making it possible to
create more sophisticated JSF applications. All too often, though, things go wrong. When they do, the
application should handle itself just as well as it does when things go as planned. In the next section, you learn
that Seam helps out in this area by tacking an exception handling facility onto JSF.
3.6 Icing on the life cycle
The ability to handle exceptions is just as important as all of the other cool and exciting things that a
framework, such as Seam, can do. Unfortunately, exceptions are typically left behind, as they are in JSF. Once
again, Seam taps into the JSF life cycle to trap and handle exceptions gracefully.
3.6.1 Exceptions previously overlooked
JSF does not handle exceptions. If an exception is thrown at any time during the JSF life cycle, your only hope
of catching it is to register an exception handler in the web.xml descriptor. An example of such an exception
clause might look like:
<error-page>
<exception-type>
org.jboss.seam.security.AuthorizationException
</exception-type>
<location>/error.seam</location>
</error-page>
Licensed to Jaroslaw Gilewski <[email protected]>
This solution is so crude that it makes me want to throw my golf club. First of all, the exception is all but
lost unless you know how to tap into the low-level request attributes in the Java Servlet API. Even then you
cannot add JSF messages or access other attributes in the Seam contexts lower than the session—the
conversation is not restored because the conversation token is missing. You are gasping for air trying to muster
an error page, even when the exception may not be a fatal one.
3.6.2 Failing gracefully or with intentional crudeness
Seam handles failures by catching any exception that occurs during the processing of the request and offers
you a chance to deal with it from within the same execution of the life cycle. By catching the exception as part
of its life cycle, Seam can retain page parameters, the temporary or long-running conversation, and any JSF
messages that may have been added before the exception occurred. Seam will also ensure that any transactions
are rolled back before delegating to the exception handler if deemed appropriate.
When dealing with an exception, you have three actions you can take:
redirect
render
send HTTP error code
In the process of handling an exception, you may also:
add a JSF message
end a conversation
add parameters to a redirect
To configure an exception handler, you again leverage the page descriptor (pages.xml). Using the stanza in
listing 3.7, we capture the same exception as above but in a far more graceful manner. Seam catches
exceptions that occur during the processing of the JSF life cycle and handles it according to the exception rules
defined in the page descriptor. These exception rules can add a message to the request and issue navigation to
an error page. The message is a standard JSF FacesMessage and is thus displayed in the user interface using
the <h:messages/> component tag.
NOTE
If you are using Facelets, you have to ensure development mode is disabled in order for the exception
handler to kick in. Facelets has a special debug page that is used for displaying exceptions when running in
development mode. The instructions for configuration Facelets development mode was covered in the hot
redeployment section (section 2.5.3) of chapter 2.
Listing 3.7 Configuration for capturing authorization exceptions
<exception class="org.jboss.seam.security.AuthorizationException">
<redirect view-id="/error.xhtml">
<message severity="WARN">
Sorry, you do not have access to the requested resource.
This message may explain why:
#{org.jboss.seam.handledException.message}
//1
or
#{org.jboss.seam.exception.message}
//2
</message>
</redirect>
</exception>
<Annotation #1> The inner exception
<Annotation #2> The outer most exception
Licensed to Jaroslaw Gilewski <[email protected]>
You can also configure exception handling through annotations by adding either an @HttpError
annotation or a @Redirect annotation to the exception class, but not both. You can supplement either of
these annotations with the @ApplicationException annotation.
The @Redirect annotation, summarized in table 3.3, is useful for maintaining the integrity of the
application. It allows you to render a pretty error page and kindly inform the user of what went wrong.
Table 3.3 The @Redirect annotation ties an exception to an HTTP redirect
Name:
Redirect
Purpose:
Indicates that when this exception is thrown, Seam should send redirect headers to the client.
Target:
TYPE (exception class)
Attribute
Type
Function
message
String
Optional message override. The default is to use the exception message.
viewId
String
The JSF view ID to use to generate an action URL to send to the client.
The @HttpError annotation, on other hand, is typically used to generate a fatal and ungraceful response
to the client. I think of it as screaming at an unwelcome guest. When an exception class annotated with
@HttpError is thrown, Seam will send the specified HTTP status code to the browser along with the
message in the exception. The @HttpError annotation is described in table 3.4.
Table 3.4 The @HttpError annotation ties an exception to an HTTP error code
Name:
HttpError
Purpose:
Indicates that when this exception is thrown, the server should issue an HTTP error to the client.
Target:
TYPE (exception class)
Attribute
Type
Function
message
String
Optional message override. The default is to use the exception message.
errorCode
integer
One of the HTTP error code constants in the Java Servlet API.
The @ApplicationException annotation, summarized in table 3.5, is used to end a long-running
conversation or immediately rollback the active transaction. When an exception class annotated with
@ApplicationException is thrown, Seam will determine from this annotation how to handle the
conversation and the transaction. Note, however, that unhandled exceptions always force a rollback. The
@ApplicationException just forces it to happen immediately.
Table 3.5 The @ApplicationException annotation configures rollback and conversation behavior for an exception
Name:
ApplicationException
Purpose:
Dictates how the long-running conversation and transaction will be handled when an exception marked with this
annotation is thrown. This class is equivalent to javax.ejb.ApplicationException for non-EJB environments.
Target:
TYPE (exception class)
Attribute
Type
Function
rollback
boolean
A flag indicating whether or not to rollback the active transaction immediately. If false, the
transaction will be rolled back at the end of the request.
end
boolean
A flag indicating whether or not to end the long-running conversation.
As an example, consider you have implemented an exception specific to your application.
Licensed to Jaroslaw Gilewski <[email protected]>
@Redirect(viewId = "/penaltyStrokeWarning.xhtml"
message="You're ball is out of play. That will cost you one stroke.")
public class OutOfBoundsException extends Exception {
}
There are two more supplemental features that I want to cover regarding Seam's page features. The first is
that you can enforce a page to be requested via an HTTPS request using the schema attribute on the page
declaration. In the following declaration I secure all pages in the secure directory:
<page view-id="/secure/*" scheme="https"/>
You can also load a message bundle into a page or set of pages. The keys will be added to the unified
message bundle that is prepared by Seam. You will learn how to configure and use the Seam message bundle
in section 14.6 of chapter 14. The following declaration loads the message bundle defined in the
admin.properties file for the administration section of the website:
<page view-id="/admin/*" bundle="admin"/>
Honestly, one could fill a book trying to cover every last detail of what Seam offers in terms of pageoriented functionality and beyond. Since this book is focused on teaching you how to use Seam and not to
serve as a reference manual, I'm afraid I am going to have to conclude this section. I encourage you to consult
the Seam reference documentation if you want to get play-by-play action of the features offered by Seam. In
fact, there are likely features that have been added to Seam that were not present at the time of writing.
3.7 Summary
In this chapter you learned how Seam's contextual container hooks into the Java Servlet and JSF life cycles to
make itself available to both JSF and non-JSF requests. You came to appreciate that this integration is multifaceted, relying on a servlet listener for the purpose of bootstrapping the container and listening for contextual
HTTP session events, a JSF phase listener to tap into the processing of the JSF request, a servlet to generate
and serve supporting resources, and a servlet filter to provide services beyond of the reach of the JSF servlet.
Thankfully, the servlet configuration performed in this chapter carries you through the remainder of the book.
You were then given a crash course in the JSF life cycle. The goal was to present the stark contrast
between an initial request and a postback and which JSF phases play a factor in the two scenarios. You were
made aware of the six phases in the JSF life cycle and learned that the pause between them is what allows JSF
to support extension through the use of registered phase listeners. You learned that it is through this
mechanism that Seam is able to become ingrained in the JSF life cycle to weave in valuable services, giving
rise to what I term the Seam life cycle.
You were presented with many of the ways in which Seam's enhanced JSF life cycle fills the gaps in the
JSF specification, pertaining most significantly in the area of page-oriented controls on the initial request. You
were encouraged to move to Seam's declarative navigation as defined in the page descriptor. The additional
controls give contextual awareness to page transitions. Committing to Seam's page descriptor gives you the
ability to accomplish tasks that JSF has been criticized for not being able to provide in the past, such as a frontcontroller and friendly URLs. You discovered that the entourage of page-oriented controls gives you
equivalent behavior to an action-based framework like Struts without having to ditch the other benefits of a
component-based model.
Licensed to Jaroslaw Gilewski <[email protected]>
Near the end of the chapter you were presented with the steps performed by Seam throughout the JSF life
cycle. This tabulation serves as a reference that you can return to after learning about conversations in chapter
7, Seam's managed transactions in chapter 9, and other services that were itemized that you have not yet
encountered.
Having gained the big picture of a Seam request, you are now ready to turn to the other essential aspects of
Seam, components and contexts. In the next chapter you learn what they are and then in chapter 5 you learn
how they can be configured in Seam's main switchboard, the Seam component descriptor. By then, you will
have a firm understanding of the role Seam plays in each and every request.
Licensed to Jaroslaw Gilewski <[email protected]>
4
Components and contexts
This chapter introduces the Seam container and the components it manages. If you are familiar with the Spring
Framework, then this aspect of Seam should seem very familiar. When you talk about the Seam container, you
only have to replace all uses of the word "bean" with the word "component". Like Spring, Seam acts as a
lightweight container. boasting similar capabilities to define, configure, and instantiate components. A
lightweight container does not force you to code to a container-specific interface, require you to adopt a
certain programming model, or mandate that you install the component into a container. Instead, components
are just plain old Java objects (POJOs). Upon instantiating one of these POJOs, the lightweight container
decorates the object with enterprise services transparently using aspect-oriented programming. The main
advantage that Seam has over other lightweight containers is that it treats the context of a component with
equal importance as the component itself. The focus of this chapter is not on components, but rather contextual
components.
In this chapter, you are going to continue to leverage seam-gen to add member registration to the Open 18
application. Only, now, you are going to be performing top-down development. You will use seam-gen to
create a new entity and then allow Hibernate to update the database with the new table when the application
starts using the information defined in the JPA annotations on the entity. Seam-gen will also be used to create
supporting views and action handler classes.
Chapter 2 gave you the opportunity to get an application up and running and observe Seam in Action. I am
sure those exercises spawned loads of questions about components. Hopefully, your questions will be
addressed in this chapter. We will start at Seam's very essence, the Seam container.
4.1 Seam's contextual naming container
At the core, Seam is a container that holds names, or rather, variable names. Seam doesn't just let all of these
variable names clump together at the bottom of the barrel. Instead, Seam divides the container into
compartments and disperses the variables into each one, accordingly. Each compartment represents a scope, or
in Seam terminology, a context. A context defines where a variable name can be found and for how long it
hangs around.
To be precise, the Seam container holds context variables. A context variable can hold a reference to any
type of object. But, as you will soon discover, the real divas of the Seam container are the components. When
the application interacts with a context variable holding a reference to a component instance, lots of exciting
things happen. From this point forward, when I use the term context variable, I mean a variable in the Seam
container that stores a value in a distinct context.
NOTE
In the text, I will often toggle between the terms scope and context. Don't let this confuse you. These two
terms are basically interchangeable. Technically, the context is the bucket and the scope is the marker that
indicates which bucket a particular variable belongs, but that is really splitting hairs.
Licensed to Jaroslaw Gilewski <[email protected]>
Before advancing with your lesson on Seam components and getting the low down on what they are, I first
need to briefly introduce you to Seam's contexts and show you how they differ from the traditional contexts in
the Java Servlet API. You will be able to understand components better if you know where they hang out.
4.1.1 Seam's context model
You have a set of contexts for storing variables for the same reason that you have multiple golf clubs in your
bag. In golf, you choose a club depending on how far you want the ball to go. In a web application, you choose
a context depending on how long you want a variable to stick around. You are certainly familiar with the three
scopes defined in the Java servlet API: application, session, and request. The problem with this abridged set of
scopes is that they are too few. Using this set is like trying to play a round of golf with a driver, a five-iron, and
a putter. You can make it work, but you are going to have to make each club do something it was not designed
to do at times.
The vast chasms that lie between the coarse-grained servlet scopes have claimed the lives of many
applications that have tried, unsuccessfully, to avoid them. The Seam developers recognized this fact and set
out to fill in the gaps between them by introducing a new set of contexts. In doing so, they addressed another
problem. Each servlet scope requires that you use a different API to access its variables. Seam brings this
access under one roof, the Seam container, and offers a single interface for accessing all variables, regardless
of the context in which they are stored.
4.1.2 Unifying the Java servlet contexts
Seam delivers a much needed technology update to the Java web environment by establishing a unified context
model. Seam takes all contexts under the wings of its container, allowing the existing contexts to fit naturally
with the new set of contexts that Seam contributes. Through this unified model, Seam provides one-stop
shopping for all of the contexts that it manages. By controlling the contexts, Seam is also able to add useful
enhancements to the existing servlet contexts, without having to dump them completely, potentially
jeopardizing integration with other applications.
The list of contexts Seam adds to the stack are the stateless context, the page context, the conversation
context, and the business process context. The complete set of contexts Seam supports are maintained in the
ScopeType Java 5 Enum, which you will see used in the Seam annotations later in the chapter. Table 4.1
shows these contexts, the associated Enum, and a brief description of the context's lifespan. The stateless and
unspecified scopes are not actual contexts since they do not store context variables. The stateless context
indicates that a context variable should be used for lookup only and not stored in a context. You typically make
a component stateless if it represents a resource that is managed by another container. The stateful component
is merely acting as a translator. The unspecified context is a marker that tells Seam to search across all scopes
when looking for a context variable.
Table 4.1 Seam's contexts, ordered from the shortest to the longest life span
Context name
Enum (org.jboss.seam.*)
Description
Stateless
ScopeType.STATELESS
A non-storing context. Use of this context forces a component to be
instantiated each time the associated context variable is resolved.
Event
ScopeType.EVENT
Analogous to the servlet request scope. Exists from the beginning of the
Restore View phase until the end of Render Response phase in the JSF
life cycle or until a redirect occurs.
Page
ScopeType.PAGE
Begins at the start of the JSF Invoke Application phase and lasts until the
end of the Invoke Application phase on the ensuing JSF postback.
Terminates prematurely if the ensuing request is not a JSF postback. This
context is used to store data in the JSF component tree.
Licensed to Jaroslaw Gilewski <[email protected]>
Conversation
ScopeType.CONVERSATION
Lasts from at least the Restore View phase until the end of the render
response phase, even in the event of a redirect. If converted to a longrunning conversation, it spans multiple requests for a single user until
terminated. Conversations are propagated by use of a special request
parameter for non-JSF postback requests and through the JSF
component tree on a JSF postback.
Session
ScopeType.SESSION
Analogous to the servlet session scope. Access to session-scoped
component instances are serialized.
Application
ScopeType.APPLICATION
Analogous to the servlet application scope.
Business Process
ScopeType.BUSINESS_PROCESS
Spans multiple conversations for multiple users as controlled
declaratively by start and end states in the business process definition
file.
Unspecified
ScopeType.UNSPECIFIED
Not a real context. Used when the scope should be implied. In many
cases, this value is used to indicate that a hierarchical search across all
scopes should be performed.
Let's briefly explore these contexts and the relevance of each, starting with Seam's new stateful contexts.
4.1.3 Seam's new stateful contexts
Seam makes a big deal about being stateful. This term means that the state—the data stored in the context
variables—associated with a user's interaction is retained for the duration of the use-case, rather than between
two arbitrary demarcations in the servlet life cycle. This stewardship is important because it improves the
user's experience, reduces load on the server, and reduces bugs caused as a result of stale data.
Seam introduces three new stateful contexts: page, conversation, and business process. The page context is
used to propagate data available in the Render Response phase of the JSF life cycle to the ensuing Restore
View through Invoke Application phase on a postback. The context variables in this scope are implanted in the
JSF UI component tree so that they survive across adjacent requests. You have used this scope in a less formal
way if you have ever included the <t:saveState> component tag from the MyFaces Tomahawk component
set 25 in your application. The benefit of using Seam's page context is that you do not couple the state logic with
the view.
The conversation and business scope processes are very different in nature. Their boundaries are
controlled declaratively using annotations or page descriptor tags. A conversation is a drop-in replacement for
most uses of the session scope in non-Seam applications. The business process is a variation on the
conversation scope, but can pass state between multiple users of the application. You will learn more about
conversations in chapter 7 and business processes in chapter 13.
4.1.4 Seam's enhanced servlet contexts
Seam doesn't turn its back on the traditional Java servlet contexts. It just fixes them. Seam even uses the Java
Servlet API as the underlying storage mechanism for these particular contexts. But, not blindly. By taking
control of these scopes, Seam is able to generalize their purpose and address flaws in how they are handled by
the native container.
For instance, the event context is wrapped around the servlet request scope. .This abstraction generalizes a
web request as an event so that the notion of a servlet request is not hard-coded into the Seam core. This
generalization opens to door for Seam to support the event construct as defined in alternate environments.
25
http://myfaces.apache.org/tomahawk/uiSaveState.html
Licensed to Jaroslaw Gilewski <[email protected]>
INFO
When operating with the bounds of the JSF life cycle, the event context encompasses the mutable
java.util.Map
that
holds
the
request
scope
attributes
as
returned
by
ExternalContext#getRequestMap() from the JSF API. An equivalent, but more involved,
wrapping is done with the servlet request map when operating outside of the JSF life cycle. The event
context and the request scope attribute map are one in the same in both instances.
Sometimes you need to make a distinction between a single servlet request and a logical request from the
standpoint of a user. For instance, there may be one or more redirects that occur between two page views. This
situation can arise from applying the Redirect-After-Post pattern 26. Developers that have attempted to apply
this pattern after a JSF action should be aware that it causes all request scoped data prepared in the action
listener to be dropped. Having a context that survives across the course of a redirect can be very useful for
tasks such as passing messages to the user. To handle this situation, Seam wraps each request in a temporary
conversation. Although the conversation is one of Seam's custom scopes, when used in the absence of an
active long-running conversation, it can be thought of as an extension of the event context, though it does
require that the context variable is scoped to the conversation.
Seam improves the session context as well by protecting session-scoped components from concurrent
access. Multiple requests scheduled to be handled by the same servlet (i.e. FacesServlet) may arrive at the
server at the same time. These requests are serviced by the same servlet instance running on different threads.
If the application logic that is executed during the request accesses a session-scoped variable, it may result in
the resource held by that variable to be altered in conflicting ways. This scenario is said to violate threadsafely. To avoid it, you would need to add the synchronized keyword to region of code accessing the
variable. Seam addresses this long standing pitfall in web-based applications by automatically synchronizing
session-scoped variables for you, and doing so with optimal efficiency. You can apply this synchronization
logic to components in other scoped by adding the @Synchronized annotation to the class definition. This
annotation can also be used to adjust the timeout period of the synchronization using the timeout attribute.
You learn the significance of applying Seam annotations to classes as you progress through this chapter.
[[QUESTION: should I put the @Synchronized annotation table here?]]
The important point to remember is that Seam maintains all context variables in its container, regardless of
whether they are the three servlet scopes or Seam's new contexts. Armed with this collection of contexts, we
are going to turn the focus of the discussion to the components that are associated with them.
4.2 Sorting out components
The term component has been used to mean many things. In my attempts to describe it to you, I found it
difficult to locate a universal definition, likely because one doesn't exist. In theory, a component is supposedly
a module that is plugged into an application in the same way that one Lego piece is attached to another Lego
piece as part of a larger structure. As a person who makes a living developing software, I am sure you will
agree that it is a little bit more complicated than that.
Definitions and intentions don't matter anyway. What matters is what the word means to you as a software
developer. Up until now, I have had you go on the assumption that a component is just like a JSF managed
bean. It's actually a bit more general. A component is a set of instructions, stored in a container, and used to
create objects whose life cycle is managed by the container. But, let's dive deeper into the meaning of this
abstract term. I promise that after this brief explanation, this "component" jargon will make sense. It's all in the
naming.
26
Redirect-after-post is a workaround that prevents double posts as a result of the user clicking refresh after submitting a form.
An explanation can be found in the Redirect After Post article on the ServerSide.com:
http://www.theserverside.com/tt/articles/article.tss?l=RedirectAfterPost
Licensed to Jaroslaw Gilewski <[email protected]>
4.2.1 Components vs component instances
A component is a set of instructions, a blueprint, for creating an object. It supplements the Java class
definition. Each component is assigned a name, which is used to address the component. Table 4.2 lists several
different containers and how the components that they manage are declared.
Table 4.2 A sampling of component containers and how classes become components in those containers.
Container
How classes become components
Seam
annotated with @Name or declared in components.xml
EJB 3
annotated with @Stateful, @Stateless, or @MessageDriven
JSF
declared as managed-beans in faces-config.xml
Spring
declared as Spring bean in applicationContext.xml
servlet container
servlets, filters and listeners declared in web.xml
When a class becomes a component, it gets access to whatever services the container has to provide. I will
cite several examples of services that the containers provide to their components to give you an idea what I
mean. Methods on session beans are automatically wrapped in transactions. Servlet components and JSF
managed-beans have access to web-tier resource injections. Spring beans are injected with other Spring beans
when instantiated. As you can see, being a component gives a class special privileges.
Let's now focus on Seam components. In Seam, a component holds:
z
metadata pertaining to the creation of an instance
z
life cycle methods
z
initial property values or references
Seam creates component instances from the component definition, as illustrated in figure 4.1.
Figure 4.1 Component instances are created from components by the Seam container.
Component vs component instance: making the distinction
One of the primary goal of this section is to make you aware of the distinction between a component
and a component instance. Just remember, the relationship between a component and a component
instance is the same as the relationship between a Java class and a Java object, respectively. Seam
even uses a Component type to describe a component definition in the same way that Java uses the
Class type to describe a class definition.
Once an instance of a component is created, it is stored as an attribute with the same name as the
component in the designated context, forming what is known as a context variable. An instance of a
component is just a Java object, with one exception. It is strung with interceptors that allow Seam to keep tabs
on it and manage its life cycle. Once in control, Seam is able to transparently weave behavior into the object
when it is invoked. You may recognize this technique as Aspect-Oriented Programming (AOP). The idea is to
handle cross cutting concerns that would otherwise appear as boilerplate code or tie the code to a particular
environment. With AOP at work, a method call isn't just a method call. More goes on around each invocation,
and that means you get more for less work.
Seam determines how to handle the object based on the instructions provided in annotations. The behavior
that Seam applies includes injecting dependencies, managing transactions, enforcing security constraints,
Licensed to Jaroslaw Gilewski <[email protected]>
invoking life cycle methods, and handling events triggered by the component, to mention a few. That should
sound very similar to how EJB 3 works as it inspired this design.
4.2.2 Seam manages components
There is another important characteristic of a component. A component is managed by the Seam container.
The container will hand out an instance of a component when the name assigned to the component is
requested, as diagrammed in figure 4.2. When this request comes in, Seam first tries to locate an existing
instance. If one cannot be found, Seam will create one. The instance is then returned to the requester.
Figure 4.2 Sequence diagram of a component instance being requested from the Seam container
What this means is that you no longer create instances by instantiating the Java class explicitly using the
Java new operator. That isn't to say that you cannot. But, to get all of the enhancements that Seam applies to
the object via AOP (which happens during the newInstance() routine in figure 4.3), you must allow Seam
to create the instance for you. In that regard, the Seam container is a factory for component instances and uses
the component definitions as the schematics for how to create those instances.
The translation from component to component instance happens more often in a Seam application than it
does in other lightweight containers, such as Spring. That's because context is so important in Seam. In Seam,
component instances come and go along with the life cycle of the contexts in which they are stored. As you
learned earlier, these contexts have varying lifespans (one with no lifespan at all). More times than not,
components are associated with stateful contexts, which means they do not hang around for the lifetime of the
application.
Spring, in contrast, primarily uses singleton beans, whose lifetime is tied to that of the container. Instance
creation happens in the Spring container just as it does in Seam, but you typically don't give it much thought.
What is so interesting about the Seam container is that it is perfectly natural for it to create an object and inject
dependencies at an arbitrary spot, rather than when the container starts.
What has not yet been addressed is how Seam components are defined and how they are assigned a
context variable name and a scope. To be more concise, how do the components get into the Seam container?
Read on to find out.
4.3 Defining components using annotations
In Seam, you can define components in one of two ways. You can either use annotations or XML. The goal of
Seam is to reduce as much XML-coding as possible. Therefore, annotations are the preferred mechanism for
defining a Seam component. The annotations that dictate how a component is defined are listed in table 4.3. As
Licensed to Jaroslaw Gilewski <[email protected]>
the chapter develops, you are introduced to each annotation in this table in turn, learning its purpose and how it
plays a role in defining a Seam component.
Table 4.3 Seam annotations that are used to create components and declare how they are instantiated
Annotation
What it does
@Name
Declares a class as a Seam component and associates it with a context variable. When the context
variable is requested, Seam will instantiate a new instance of this class and bind it to the context
variable.
@Scope
Specifies the default scope in which the context variable will be stored when an instance of the
component is instantiated.
@Role (@Roles)
Defines alternate name and scope pairings for the component that can be used to instantiate parallel
instances of the same component for different purposes. Multiple @Role declarations must be wrapped
in a @Roles annotation.
@Startup
Instructs Seam to instantiate the component automatically when the container starts, if it is an
application-scoped component, or when the Http Session starts, if it is a session-scoped component.
@Namespace
Binds a URI to a Java package that can be used to define custom XML namespaces in the component
descriptor.
@Install
Used to make installation of a component conditional. Such conditions include the presence of a class
on the classpath, presence of another component, or the debug mode setting.
@Autocreate
Tells Seam to create a new instance of the component when the context variable is first requested, even
if the calling code does not request for it to be created.
This section concentrates on @Name and @Scope, which together form a proper component definition.
The remaining annotations are auxiliary and effect how the component is processed or how it behaves at
runtime.
4.3.1 Giving a component a @Name
It all starts with a @Name. The most fundamental way of creating a Seam component is by adding the @Name
annotation to the class declaration. This annotation is summarized in table 4.4. Given that every Seam
component must be assigned a name, you must provide one using the value attribute of the @Name
annotation.
Table 4.4 The @Name annotation, which is used to define a Seam component
Name:
Name
Purpose:
Identifies a Java class as a Seam component using the context variable name provided.
Target:
TYPE (class)
Attribute
Type
Function
value
String
The name of the context variable under which this component will be stored. An Instance is
created when this context variable is requested.
You can place a @Name annotation on any class that you would like to dress up as a Seam component.
Keep in mind, though, that annotations are obviously only useful for classes that you can modify. See the
sidebar on the syntax of annotations if you are unfamiliar with how to use them.
The syntax of annotations
Annotations are markers. They are comprised of a Java type—a name prefixed with an at-sign (@)—and
a set of attributes associated with that type. Annotations can be placed on interfaces, class definitions
(types), methods, and fields. The acceptable locations are defined by the annotation.
Licensed to Jaroslaw Gilewski <[email protected]>
The attribute assignments for an annotation appear as a list of name-value pairs placed between a set
of parenthesis and separated by commas. But there is an exception to this syntax rule. If you are
defining exactly one attribute, and the name of that attribute is value, then the attribute name and the
equals sign (=) can be omitted. If the name of the attribute is not value, or you are defining multiple
attributes on a single annotation, then both the attribute name and value are required for every
attribute. If you are not declaring any attributes on the annotation, then the brackets can be omitted
all together.
Attribute values can be primitives, Java types, or arrays of the former. When defining an array value,
the list of values are placed between a set of curly braces and separated by commas. Again, there is
an exception to this syntax rule. If you are specifying exactly one value to a multi-valued attribute, the
braces can be omitted.
The coolest part of Seam is its ability to normalize the non-semantic differences between components'
native types. A component is but a component. The list of candidates for a Seam component includes:
z
JavaBean (POJO)
z
JPA entity class
z
EJB component
c
Stateless session bean
c
Stateful session bean
c
Message driven bean
z
Groovy class
z
Spring bean 27
Seam even decorates POJOs with functionality previously available only in EJB components, such as
container-managed transaction management and security, shielding the rest of the application from really
caring about the underlying type. What really sets components in Seam apart from those in other containers is
the attention to the context of a component instance—the scope of its existence.
4.3.2 Putting a component in @Scope
The @Name annotation is only half the story of a component. The component instance has to be put somewhere
once it is created. That is where the @Scope annotation comes in. The @Scope annotation dictates the
contextual scope in which an instance of the component will be stored after it is instantiated by the Seam
container. You can, of course, put the component instance anywhere you want using a manual assignment. The
@Scope annotation just determines the default scope where Seam puts the instance. Table 4.5 lists the scope
that is used for each type of component if one is not specified.
Table 4.5 Seam component classifications and default scopes
Component type
Default scope assigned
EJB stateful session bean
conversation
JPA entity class
conversation
EJB stateless session bean
stateless
EJB message driven bean
stateless
JavaBean (POJO)
event
27
Spring beans are classes that are managed by the Spring container. Seam can "borrow" a bean from the Spring container and
decorate it with Seam services. You learn about the Seam-Spring integration in chapter 15.
Licensed to Jaroslaw Gilewski <[email protected]>
You can override these default scope assignments by adding the @Scope annotation, summarized in table
4.6, to the class definition.
Table 4.6 The @Scope annotation sets the default scope of a component
Name:
Scope
Purpose:
Assigns the default context for a Seam component.
Target:
TYPE (class)
Attribute
Type
Function
value
ScopeType
The Seam context in which an instance of this component is stored. The instance is placed
into this context's map using the context variable name as the key. Table 4.5 lists the
default value according to component type.
There are other ways to define Seam components that don't involve the use of annotations. Two common
alternatives are the XML-based Seam component descriptor, covered in chapter 5, and Seam's pluggable
container mechanism—the integration that allows Spring beans to serve as Seam components—which is
explored in chapter 15. This chapter focuses solely on the use of annotations. Let's consider an example of how
to put the @Name and @Scope annotations together to use to develop a new module for the Open 18
application.
4.4 A comprehensive component example
You are going to make a JPA entity class your first Seam component. To add member registration to the Open
18 application, we first need to create an entity that holds a member's details. Since the members of the Open
18 application are golfers, we will name the corresponding entity Golfer.
4.4.1 Creating the entity components
To create the Golfer entity, navigate to the Seam distribution directory and run the seam new-entity
command using the following responses:
entity class name: Golfer
master page name: GolferList
detail page name: Golfer
The new-entity command generates the Golfer JPA entity class containing a handful of conventional
bean properties, a page to list the golfers (golferList.xhtml) and corresponding page controller (GolferList),
and a page to display the details of a golfer (golfer.xhtml) and corresponding page controller (GolferHome).
The page controllers that support the CRUD operations are covered in depth in chapter 10. For now, we are
focusing on using the Golfer entity class for the registration page.
The @Entity annotation added above the class declaration marks this class as a JPA entity and the
@Table annotation customizes the database table mapping. The next time the application is deployed,
Hibernate will incremental update the database schema to reflect the change in the model if the
hibernate.hbm2ddl.auto property in the resources/persistence-dev-war.xml descriptor is set to
update. The initial value established by seam-gen is validate. Make sure you have made this change
before continuing on.
I have decided to enhance the Golfer class, shown in listing 4.1, by making it a subclass of Member,
shown in 4.2. This added complexity isn't absolutely necessary for the purpose of the example, but sets the
stage for a more realistic application. The @PrimaryKeyJoinColumn is just a detail of this inheritance
Licensed to Jaroslaw Gilewski <[email protected]>
design strategy. For the purpose of this example, you don't really need to be concerned that this class is a JPA
entity because the primary focus here is to use it as a form "backing" bean in a JSF page. In order for that to
happen, it needs to be declared as a Seam component.
To make Golfer a Seam component, you can simply add the @Name and @Scope annotations alongside
the JPA annotations, shown in bold in listing 4.1. The context variable name newGolfer has been chosen
since the component will be called upon to instantiate a fresh instance of a golfer for use in the registration
form. The @Scope annotation is present to explicitly bind the component to the event scope, overriding the
default scope assignment for entity classes, which is the conversation scope. Several bean properties have been
added, which map to columns in the GOLFER table.
INFO
An alternative to adding @Name and @Scope to a JPA entity class is to declare the component in the Seam
component descriptor using XML. For now, appreciate that the tight coupling is helping to keep things
simple and minimizing configuration. Besides, annotations are merely class metadata. Unless they are
consulted at runtime using reflection, it's as though they aren't even there. The Seam libraries are only
required as a compile-time dependency. The class still remains a POJO.
Listing 4.1 The Golfer entity class, dually acting as a Seam component
package org.open18.model;
import
import
import
import
...;
org.jboss.seam.annotations.Name;
org.jboss.seam.annotations.Scope;
org.jboss.seam.ScopeType;
@Entity
@PrimaryKeyJoinColumn(name="MEMBER_ID")
@Table(name = "GOLFER")
@Name("newGolfer")
@Scope(ScopeType.EVENT)
public class Golfer extends Member {
private String name;
private Gender gender;
private Date dateJoined;
private Date dateOfBirth;
private String location;
private String specialty;
private String proStatus;
@Column(name = "last_name", nullable = false)
@NotNull @Length(max = 40)
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
@Column(name = "first_name", nullable = false)
@NotNull @Length(max = 40)
public String getFirstName() {
return firstName;
Licensed to Jaroslaw Gilewski <[email protected]>
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getName() {
return firstName + ' ' + lastName;
}
@Enumerated(EnumType.STRING)
public Gender getGender() {
return gender;
}
public void setGender(Gender gender) {
this.gender = gender;
}
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "joined", nullable = false)
@NotNull
public Date getDateJoined() {
return dateJoined;
}
public void setDateJoined(Date dateJoined) {
this.dateJoined = dateJoined;
}
@Temporal(TemporalType.DATE)
@Column(name = "dob")
public Date getDateOfBirth() {
return dateOfBirth;
}
public void setDateOfBirth(Date dateOfBirth) {
this.dateOfBirth = dateOfBirth;
}
public String getSpecialty() {
return specialty;
}
public void setSpecialty(String specialty) {
this.specialty = specialty;
}
@Column(name = "pro_status")
public String getProStatus() {
return proStatus;
}
public void setProStatus(String proStatus) {
this.proStatus = proStatus;
}
}
The Member is an abstract entity class that holds the username, passwordHash, and emailAddress
inherited by the Golfer entity. The Member entity, shown in listing 4.2, uses a joined-inheritance strategy.
This design makes it possible to have different types of members that are represented by separate tables,
Licensed to Jaroslaw Gilewski <[email protected]>
putting the burden of selecting the type of entity that is associated with a username on the ORM tool. Again,
don't get bogged down in this design if you are new to JPA. Appreciate that the goal here is to establish a
JavaBean that can be used to capture data from the registration form.
Listing 4.2 The Member entity class, a base class for entities requiring authentication
package org.open18.model;
import ...;
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@Table(name = "MEMBER", uniqueConstraints = {
@UniqueConstraint(columnNames = "username"),
@UniqueConstraint(columnNames = "email_address")
})
public abstract class Member implements Serializable {
private Long id;
private String username;
private String passwordHash;
private String emailAddress;
@Id @GeneratedValue
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Column(name = "username", nullable = false)
@NotNull @Length(min = 6)
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@Column(name = "password_hash", nullable = false)
@NotNull
public String getPasswordHash() {
return passwordHash;
}
public void setPasswordHash(String passwordHash) {
this.passwordHash = passwordHash;
}
@Column(name = "email_address", nullable = false)
@NotNull @Email
public String getEmailAddress() {
return emailAddress;
}
public void setEmailAddress(String emailAddress) {
Licensed to Jaroslaw Gilewski <[email protected]>
this.emailAddress = emailAddress;
}
}
The registration form needs to capture a plain-text password from the user as well as a password
confirmation. Corresponding properties are not found on either the Golfer or the Member entities since these
fields are not to be persisted. Rather than polluting the entity classes with transient fields, we will put these
fields on a reusable JavaBean, PasswordBean, defined in listing 4.3. The PasswordBean also contains a
business method for verifying that the two passwords entered are equivalent. This class is created under the
src/model directory of the seam-gen project along with the entity classes.
Listing 4.3 A Seam JavaBean component for capturing the password values from the registration form
package org.open18.auth;
import ...;
@Name("passwordBean")
public class PasswordBean {
private String password;
private String confirm;
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getConfirm() {
return confirm;
}
public void setConfirm(String confirm) {
this.confirm = confirm;
}
public boolean verify() {
return confirm != null && confirm.equals(password);
}
}
AUTHOR'S CHOICE
I prefer to use the @Name annotation on JSF action listeners, rather than entity classes. Entity classes are
the most frequently shared components, so conflicts can occur over how to define their annotations.
Besides, an entity class gets little benefit out of becoming a Seam component, other than convenience,
since Seam does not do much to enhance them. The primary use for a Seam entity component is to create
prototypes—new, transient (not yet persisted) instances. I chose the context variable name newMember for
this Seam component so that Seam will instantiate it and apply component properties to it when it is used as
a backing bean to capture form data. In the next chapter, the @Name annotation is removed from this class
and the component descriptor is used instead to define this component.
Licensed to Jaroslaw Gilewski <[email protected]>
To give you a true appreciation of how easy Seam is making your life, I want to now show you how
@Name and @Scope provide everything necessary to design a JSF form and action listener to process the form
submission. No XML is required.
4.4.2 Preparing an action listener component
Seam simplifies the creation of JSF action listener components. In fact, a Seam component used for this
purpose can completely replace the need for a JSF managed bean. I will demonstrate this point by showing you
how to create a component that serves as the action listener for processing the member registration form.
Return once again to the ${seam.runtime} directory. Execute the command seam new-action to create
the RegisterAction component using the following responses:
Seam component name: registerAction
local interface name: RegisterAction
bean class name: RegisterAction
action method name: register
page name: register
This command generates the RegisterAction JavaBean component, shown in listing 4.4. The
RegisterAction component contains a single method, register(), that will be used as the target of the
form on the register.xhtml page, which was also generated by the new-action command. The
RegisterAction class and register.xhtml page will be filled in as you move through this chapter.
Listing 4.4 The Seam component that handles the member registration
package org.open18.action;
import ...;
import org.jboss.seam.log.Log;
import org.jboss.seam.faces.FacesMessages;
@Name("registerAction")
public class RegisterAction {
@Logger private Log log;
@In private FacesMessages facesMessages;
public void register() {
log.info("registerAction.register() action called");
facesMessages.add("register");
}
}
The @Name annotation is added to the RegisterAction class to make it a Seam component. Since the
@Scope annotation is excluded, and this is a regular JavaBean (does not implement any container-specific
interfaces), its instances are bound to the event context. The Seam annotations @In and @Logger will be
covered later on. Although the register() method is just a stub, it will suffice for now. Before taking
another step forward down the development path, we need to safe guard ourselves by creating a test.
Fortunately, seam-gen has already done the legwork for us.
Licensed to Jaroslaw Gilewski <[email protected]>
4.4.3 Integration testing components
To practice good agile development techniques, you always want to create a test either before or while you are
developing a new component. Conveniently, the new-action command also generated an integration test
class, RegisterActionTest, in the src/test directory. The test class, shown in figure 4.5, has been renamed
to RegisterGolferIntegrationTest to better represent its purpose as an integration test.
Listing 4.5 A TestNG-based integration test for the RegisterAction component
package org.open18.test;
import ...;
import org.testng.annotations.Test;
public class RegisterGolferIntegrationTest extends SeamTest {
@Test
public void test() throws Exception {
new FacesRequest() {
@Override
protected void invokeApplication() {
invokeMethod("#{registerAction.register}");
}
}.run();
}
}
This test class extends SeamTest, which bootstraps the Embedded JBoss to allow testing in a Java EEcompliant environment. The FacesRequest anonymous inner-class is used to emulate the JSF life cycle,
shown here tapping into the Invoke Application JSF life cycle phase. As you can see from the @Test
annotation used in this class, seam-gen projects are configured to use the TestNG framework. A TestNG
configuration file, RegisterActionTest.xml, is created along with this class to configure the test runner:
<!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd">
<suite name="RegisterAction Tests" verbose="2" parallel="false">
<test name="RegisterAction Test">
<class name="org.open18.test.RegisterGolferIntegrationTest"/>
</classes>
</test>
</suite>
The Ant target test, from the project's build.xml file, looks for files ending in Test.xml and feeds them
into the TestNG test runner to execute the tests. You should be able to run ant test from the root of the
project to verify that the test passes, producing the output shown here:
test:
[testng] [Parser] Running:
[testng]
/home/twoputt/projects/open18/test-build/RegisterAction
Test.xml
[testng]
[testng] INFO [org.open18.action.RegisterAction] registerAction.re
gister() action called
[testng] INFO [org.open18.action.RegisterAction] Registering golfe
Licensed to Jaroslaw Gilewski <[email protected]>
r twoputt
[testng] Hibernate: insert into MEMBER (id, username, password_hash
, email_address) values (null, ?, ?, ?)
[testng] Hibernate: call identity()
[testng] Hibernate: insert into GOLFER (name, location, gender, joi
ned, dob, specialty, pro_status, MEMBER_ID) values (?, ?, ?, ?, ?, ?,
?, ?)
[testng] PASSED: registerValidGolfer
[testng]
[testng] ===============================================
[testng]
RegisterAction Test
[testng]
Tests run: 1, Failures: 0, Skips: 0
[testng] ===============================================
[testng]
[testng]
[testng] ===============================================
[testng] RegisterAction Tests
[testng] Total tests run: 1, Failures: 0, Skips: 0
[testng] ===============================================
[testng]
BUILD SUCCESSFUL
Total time: 25 seconds
You can adjust the log levels used when running the tests by editing the Log4j configuration
bootstrap/log4j.xml.
NOTE
The Embedded JBoss bundled with Seam 2.0.0.GA will only work with a Java 5 runtime (not a Java 6 or 7
runtime). Until a version of Seam is released with a Java 6-compatible Embedded JBoss container, you
must run tests using Java 5.
What about unit tests?
Seam favors the use of integration tests, not unit tests, for validating the behavior of action listener
components—components that respond to events, interact with the Seam container, and play an
active role in the JSF life cycle. This approach is a reflection of Seam's goal to eliminate unnecessary
layers. You can certainly use Seam to create a well-layered application and to create unit tests at the
boundaries of every layer. Seam just helps you get an application up and running quickly, and for that,
integration tests are what matters the most.
If you believe strongly in using unit tests for validating front-end components, it is possible to
bootstrap a mock Seam container which is used often to test the Seam framework itself. However,
doing so is tricker than it may appear at first. If you do venture down that path, I encourage you to
have EasyMock handy to mock out certain built-in Seam components that are particularly pesky to
swap out.
[[TODO: point to appendix for how to run integration tests in IDE (without ant)]]
The action listener component is just a stub at this point, but it's good enough to turn to the task of creating
the JSF template that renders the registration form. Using test-driven development (TDD) principles, we will
build out that component when we need to, not a minute sooner.
Licensed to Jaroslaw Gilewski <[email protected]>
4.4.4 Hooking components into JSF
Now we need to set up JSF so that it can access the Seam components. Guess what? There's nothing to do!
Believe it or not, all the components defined so far are accessible to JSF as is. The @Name annotation has the
same affect as defining the components in the faces-config.xml descriptor as managed beans, except that you
don't have to. While you may look back and see that there has been quite a bit of code written, you have not
had to write a single line of XML-hell. And there is no need to mess with glue code either. Golfer and
PasswordBean can serve as backing beans and RegisterAction can act as the action handler for the
registration page. All you need to do is write a JSF view to use them. But first, let's take a step back and review
how exactly JSF is able to reach into the Seam container to grab hold of Seam components.
Resolving Seam components from JSF
When the context variable name assigned using the @Name annotation is accessed from an EL expression,
Seam will locate or create a component instance and make it available to the variable resolver. Seam
establishes the bridge between JSF and its own container by registering a custom expression language (EL)
resolver, SeamELResolver, with the JSF 1.2—required to use Seam 2.0—application runtime:
<faces-config>
<application>
<el-resolver>org.jboss.seam.el.SeamELResolver</el-resolver>
</application>
</faces-config>
You do not have to register this custom EL resolver. It is defined in the faces-config.xml that is included
in the core Seam JAR file and processed at application startup by JSF. Seam's EL resolver intercepts requests
for base names in an EL expression and matches them against the names of Seam components. A base name is
the first segment of an expression. For instance, the EL resolver atomizes the expression
#{passwordBean.password} into the method getPassword() on a Seam component named
passwordBean. If the custom resolver cannot find a matching Seam component for the base name, it passes
the torch back to the default JSF variable resolver.
JSF views, Seam-style
Let's move on to the view layer. The register.xhtml page was created with a very basic JSF form when you ran
the new-action command, but we need to add the input fields to it. The augmented form is shown in listing
4.6. The action of the registration form is the register() method on the RegisterAction component, as
defined by the method-binding expression #{registerAction.register} on the JSF command button.
This expression is assembled by combining the context variable name of the RegisterAction component,
registerAction, with the name of the method that acts as the action handler, register. Seam also
prepares a new instance of the Golfer entity class and makes it available under the context variable name
newGolfer as well as a new instance of the PasswordBean JavaBean class under the context variable name
passwordBean.
Listing 4.6 The registration form used to populate the Golfer and PasswordBean components
<h:form id="registerActionForm" styleClass="edit">
<rich:panel>
<f:facet name="header">Open 18 Member Registration</f:facet>
<s:decorate id="firstNameField" template="layout/edit.xhtml">
<ui:define name="label">First Name</ui:define>
Licensed to Jaroslaw Gilewski <[email protected]>
<h:inputText id="firstName"
value="#{newGolfer.firstName}" required="true"/>
</s:decorate>
<s:decorate id="lastNameField" template="layout/edit.xhtml">
<ui:define name="label">Last Name</ui:define>
<h:inputText id="lastName"
value="#{newGolfer.lastName}" required="true"/>
</s:decorate>
<s:decorate id="emailField" template="layout/edit.xhtml">
<ui:define name="label">Email address</ui:define>
<h:inputText id="emailAddress"
value="#{newGolfer.emailAddress}" required="true"/>
</s:decorate>
<s:decorate id="usernameField" template="layout/edit.xhtml">
<ui:define name="label">Username</ui:define>
<h:inputText id="username"
value="#{newGolfer.username}" required="true"/>
</s:decorate>
<s:decorate id="passwordField" template="layout/edit.xhtml">
<ui:define name="label">Password</ui:define>
<h:inputSecret id="password"
value="#{passwordBean.password}"/>
</s:decorate>
<s:decorate id="confirmField" template="layout/edit.xhtml">
<ui:define name="label">Confirm password</ui:define>
<h:inputSecret id="confirm"
value="#{passwordBean.confirm}"/>
</s:decorate>
<s:decorate id="dateOfBirthField" template="layout/edit.xhtml">
<ui:define name="label">Date of birth</ui:define>
<rich:calendar id="dateOfBirth"
value="#{newGolfer.dateOfBirth}"/>
</s:decorate>
<s:decorate id="genderField" template="layout/edit.xhtml">
<ui:define name="label">Gender</ui:define>
<h:selectOneRadio id="gender" value="#{newGolfer.gender}">
<s:convertEnum/>
<s:enumItem enumValue="MALE" label="Male"/>
<s:enumItem enumValue="FEMALE" label="Female"/>
</h:selectOneRadio>
</s:decorate>
<div style="clear:both">
<span class="required">*</span> required fields
</div>
</rich:panel>
<div class="actionButtons">
<h:commandButton id="cancel" value="Cancel"
action="home" immediate="true"/>
<h:commandButton id="register" value="Sign Up Now!"
action="#{registerAction.register}">
<s:defaultAction/>
</h:commandButton>
</div>
</h:form>
Licensed to Jaroslaw Gilewski <[email protected]>
If you have used JSF, then the form and input components should look familiar. If not, all you need to
know is that the value binding expressions that take the form #{newGolfer.username} are a two-way
street. They are used to output a component property to the screen as well as capture a value to be assigned to
the property when the form is submitted. This form captures data from the user and populates the properties of
the Seam components bound to the newGolfer and passwordBean context variables.
This JSF template takes advantage of several Seam UI components. The <s:decorate> component tag,
covered in the last chapter, adds a label component to coincide with the input field and decorates the two
components with layout markup. Seam provides the <s:convertEnum/> and <s:enumItem> component
tags to enable input fields to support Java 5 Enum properties. Finally, take note of the <s:defaultAction>
component tag, another JSF refinement introduced by Seam. This tag sets the target action when the <Enter>
key is pressed on the user's keyboard. Unless directed otherwise, the browser will associate the first submit
button in the form with the <ENTER> key, which in this case would be the Cancel button. This tag allows you
to override this decision. For a complete list of component tags that Seam adds to JSF, please see the Seam
reference documentation.
As you can see, annotations and Seam JSF component tags drastically reduce the amount of work
necessary to pull together a JSF application. The @Name annotation is the only requirement to get JSF talking
to your classes. You are going to learn more about accessing Seam components using EL notation later.
You have learned how to spec out a component and how to use it in the context of a JSF application, but a
component lives a busy life outside of these moments in the limelight. In the next section, you get a glimpse
behind the scenes of the life of a component; how it is discovered, selected, groomed, and managed by the
Seam container.
4.5 A component's life
Before a component definition can be used to spawn component instances, the definition of the component
must be discovered by the Seam container. Even upon discovery, the definition may not be loaded into the
Seam container if its prerequisites cannot be satisfied. If loaded, Seam may create an instance immediately,
even before being requested by the application. Whenever instance creation occurs, additional callbacks may
be invoked before the instance is returned to the requester. Finally, when the component is destroyed or goes
out of scope, it has one last opportunity to perform work before being cast away. That is a component's life.
Let's start from the birth.
4.5.1 Loading component definitions
In order for the components to get into the container, Seam has to find them. This happens during the Seam
initialization process. The means by which Seam is bootstrapped was covered in the last chapter. During
initialization, a deployment scanner scours the classpath looking for classes that host the @Name annotation.
Seam accepts both compiled and non-compiled Groovy classes, in addition to Java classes. Seam also
considers classes that are identified as components in the XML-based component descriptor. The component
descriptor is covered in depth in the next chapter. A special component scanner inspects the hot redeployable
classpath whenever a class file managed by the hot redeployable classloader changes. The hot redeployable
component scanner does not consider components defined via XML when it runs.
For each class that is declared as a component using either an annotation or XML declaration, Seam
creates a component definition and stashes it away in the application context of the container. The name of the
attribute under which the component definition is stored is derived by appending .component to the context
variable name (e.g. registerAction.component). For your purposes, you will always address the
component by its context variable name (e.g. registerAction).
Licensed to Jaroslaw Gilewski <[email protected]>
The component scanner frees you from having to declare every Seam component in an XML descriptor.
Many XML configurations were devised because the Java language lacked a common syntax for adding
metadata at the code level. Since the scanner is capable of finding classes that use the @Name annotation, it
gives you the option of not using the component descriptor, giving you one less XML file to juggle and one
less layer of abstraction.
Ah, but there is a catch! Seam only considers qualified classpath entries (class directories and JAR files).
A classpath entry becomes qualified if it contains a seam.properties file at the root of classpath or the METAINF directory contains either a seam.properties file or a component descriptor (i.e. components.xml). Figure
4.3 shows the presence of the seam.properties file at the root of the classpath.
Figure 4.3 Shows the presence of a marker file, qualifying this classpath entry to be scanned for Seam components
These two files serve as markers that enable a JVM classloader optimization by paring down the number
of classpath entries that must be scanned for components. Though it may seem annoying to have to ensure one
of these files is present, this annoyance pays off in that it drastically reduces the load time of the Seam
container. Without this optimization, Seam would have to do a full scan of every library on the classpath, a
very expensive operation for projects with large code bases.
NOTE
If you decide to use seam.properties, you may start with it existing as an empty file. In the next chapter,
you discover that this file has another use, to initialize the properties of Seam components.
It is possible that even though a class is in a qualified classpath entry and has @Name annotation, or it is
declared as a component in the component descriptor, a component definition will still not be created for it.
The next section details how to define prerequisites on a class that make its installation conditional.
4.5.2 When to @Install a component
When the component scanner finds a class annotated with @Name, the default behavior is to make it
component. While the automatic discovery of components is a powerful mechanism, you lose a degree of
flexibility over which classes are turned into components. That is where the @Install annotation comes into
play. This @Install annotation, summarized in table 4.7, tells Seam what to do with a class that it comes
across bearing the @Name annotation.
Table 4.7 The @Install annotation allows conditional installation of a component
Name:
Install
Purpose:
Stipulates conditional installation of a component.
Target:
TYPE (class)
Licensed to Jaroslaw Gilewski <[email protected]>
Attribute
Type
Function
value
boolean
A flag indicating whether or not to install the component. Subsequent conditions may still
prevent the component from being installed. The default value is true.
dependencies
String[]
The context variable names of other components that must be installed for this
component to be installed.
classDependencies
String[]
Classes that must be available on the classpath for this component to be installed.
Classes are provided as strings to avoid unnecessary compilation requirements.
genericDependencies
Class[]
Classes which must be acting as components for this component to be installed.
precedence
int
A weighted value used to compare against other components assigned the same context
variable name. The component of higher precedence will be installed. The default value is
20.
debug
boolean
Indicates that this component should only be installed when operating in debug mode.
The default value is false.
You have a wide range of prerequisites for controlling the condition under which a component is installed.
The most clear-cut is the value attribute on the @Install annotation, which is a boolean that can be used to
switch the component on or off. You can further tune the control the component installation through the use of
the following prerequisites:
z
the presence of other component definitions, looked up by context variable name
z
the presence of other component definitions, looked up by class name
z
classes available on the classpath
z
a weighted precedence value (selects one definition over another for the same context variable name
and precedence combination)
z
the Seam debug mode setting
If the prerequisites are not satisfied, that doesn't mean that its future as a component is entirely bleak,
though. You can still place it back into the ranks of the other components by declaring it in the component
descriptor. Several built-in Seam components are declared using @Install(false) to conserve resources,
allowing you to enable them as needed. Such components include:
z
Seam managed persistence context
z
jBPM session factory
z
POJO cache
z
Asynchronous dispatcher (Quartz, EJB 3, Spring)
z
non-JTA transaction manager
z
JMS topic publisher
z
Spring context loader
z
GWT service integration
z
Meldware (embeddable mail server) configuration
Aside from limiting the set of component definitions, conditional installation can be useful for selecting
between alternate implementations of a component.
Alternate implementations
There are times when you need to perform different logic to support different implementations of the same
API, such as the JSF specification or application server environment. To keep your component clean, void of
conditional logic that checks for the presence of an implementation class, you may choose to separate the logic
Licensed to Jaroslaw Gilewski <[email protected]>
for each implementation into different components and have the appropriate component selected by Seam
according to the prerequisites defined in the @Install annotation.
To make use of the @Install annotation in this case, you create two implementation classes and one
interface. Then, you give the two implementations the same component name, and let the @Install
annotation handle which one will be configured based on the presence of a particular JSF implementation
class.
You may create a component for the Sun JSF implementation:
@Name("jsfAdapter")
@Install(classDependencies =
"com.sun.faces.context.FacesContextImpl")
public class SunJsfAdapter implements JsfAdapter {
...
}
and another for the MyFaces JSF implementation:
@Name("jsfAdapter")
@Install(classDependencies =
"org.apache.myfaces.context.servlet.ServletFacesContextImpl")
public class MyFacesJsfAdapter implements JsfAdapter {
...
}
You can then request the context variable named jsfAdapter from the Seam container and Seam will
choose the appropriate implementation for you depending on which FacesContext implementation class is
available on the classpath.
Let me ask, how many frameworks completely overlook this type of functionality, forcing you to devise
your own solution? Conditional installation is a fundamental part of defining components. Admittedly, you
could use dependency injection to handle this sort of situation, but the implementation cannot be controlled as
transparently as with the @Install annotation.
NOTE
At the time of writing, Seam does not support the use of EL value expressions in the value attribute of the
@Install annotation. However, you can use the installed attribute on the <component> element,
which serves the same purpose, to apply a replacement token (a name surrounded by @ signs) whose value
is defined in the components.properties file.
There is another facet to alternate implementations. Instead of having two production components to
handle the presence of different implementations, you may have components that should only be available
during development mode.
Debug mode components
Another way to control the installation of a component is to tie it to Seam's debug mode flag. You do so by
setting the debug attribute on the @Install annotation to true. Seam's debug mode is controlled by the
debug property on the org.jboss.seam.core.init component. You enable the debug mode flag by
adding the following declaration to the component descriptor:
<core:init debug="true"/>
Licensed to Jaroslaw Gilewski <[email protected]>
You will learn how to configure built-in Seam components in the next chapter. For now, focus on the
effect of this setting. When true, Seam activates components that are marked with
@Install(debug=true). You may use this flag to swap in components that return canned data or
otherwise stub out back-end logic. In debug mode, the debug component has higher priority. When it comes
time for deploying to an integrating test or production environment, the real component gets selected.
Speaking of priority, one component definition can be selected over another based on its precedence. A
precedence value is required any time you have two components assigned to the same context variable name.
Let's see how Seam handles the curve ball of conflicting component definitions.
Installation precedence
Precedence defines which component definition wins two components try to occupy the same space—they
have the same context variable name. A precedence is an integer value assigned to a component using the
precedence attribute on the @Install annotation. The higher the value, the more clout it has. All built-in
Seam components have a precedence of Install.BUILT_IN (0), so they can easily be overridden. If a
precedence is not defined, then it defaults to Install.APPLICATION (20). With precedence in the picture,
the rule becomes that two components cannot be configured with the same name and precedence value. If that
situation happens, it will cause an exception when the component scanner discovers it. This situation often
arises when you are defining or overriding component definitions using the component descriptor.
If all the prerequisites are satisfied, the component gets the gig. It has made it into the container. There is a
chance, though, that a single @Name annotation can spawn multiple component definitions. Instead of having
one component definition installed over another, you can have the same Java class associated with multiple
context variable names, which may even be configured to use different scopes. These alternate definitions are
known as component roles.
4.5.3 Giving a component multiple @Roles
As you know, a component must be assigned a context variable name. But, that doesn't mean it cannot be
assigned more than one context variable name. It is possible to assign alternate name and scope combinations
to a component using the @Role annotation, summarized in table 4.8. If you need to define multiple @Role
annotations for a single component, they must be nested within the @Roles annotation.
Table 4.8 The @Role annotation binds a component to additional context variables
Name:
Purpose:
Role
Associates the component with an alternate context variable name and scope. Multiple roles must be nested in the
@Roles annotation.
Target:
TYPE (class)
Attribute
Type
Function
value
String
An alternate context variable name under which this component will be stored. An Instance
of the component to which this annotation belongs is created when this alternate context
variable is requested.
scope
ScopeType
An alternate Seam context in which an instance of this component is stored for this role.
The roles feature is very subtle and it isn't always obvious when to use it at first. It tends to be more useful
as the size of your application grows, so bear with me if this feels a bit hypothetical right now.
The idea behind roles is to allow the same component to be instantiated and managed by Seam for
different purposes. Seam tries to isolate the decision of where to store the context variable from the code that
uses it. This means that if you reference the component using one name, it might be bound to the event scope,
Licensed to Jaroslaw Gilewski <[email protected]>
whereas if you use an alternative name—the role name—then it might be bound to the session scope. You will
often see this technique used in outjection, which is covered in chapter 6. Multiple roles also allow you to have
more than one instance of the same component being used simultaneously in the same scope.
Let's consider a simple example of when a role might allow us to work with multiple instances of the same
component class simultaneously. The registration form is using an instance of the Golfer component, named
newGolfer, for capturing member information in the registration form. Consider the case when we want to
use a difference instance of the Golfer component on the same page to capture search criteria locate the
member that referred the new golfer to the site.
To implement this feature, the Golfer component needs to be accessed under two different context
variable names, the later being golferExample. When the user clicks the lookup button, an auxiliary
Golfer instance is bound to the golferExample context variable name and is populated from search form
values and passed on to the back end to perform the lookup.
We now have two uses for the Golfer component, one to capture the new member registration
information and another to lookup existing members. Multiple context variables can be assigned to a
component using the @Role annotation. In fact, we will take it a step further and use the context variable
golferEntity as the default name of the Golfer component and then make both newGolfer and
golferExample role names, shown in listing 4.7. The differences are highlighted in bold.
Listing 4.7 The Golfer entity bound to three different context variables
package org.open18.model;
import ...;
import org.jboss.seam.annotations.Role;
import org.jboss.seam.annotations.Roles;
@Entity
@PrimaryKeyJoinColumn(name="MEMBER_ID")
@Table(name = "GOLFER")
@Name("golferEntity")
@Scope(ScopeType.EVENT)
@Roles({
@Role(name = "newGolfer", scope = ScopeType.EVENT),
@Role(name = "golferExample", scope = ScopeType.EVENT)
})
public class Golfer extends Member {
// class body remains unchanged
}
If you are having difficulty connecting with this example, just remember that roles give you more than one
way to refer to a Seam component and that those instances can live in different scopes. Roles are also good
way to correlate a context variable with a scope so that it is not necessary to specify the scope explicitly when
the context variable is set. You learn the importance of this feature when you study outjection, a means of
setting context variables declaratively using annotations.
Once the scan is complete and all of the component declarations and role assignments have been
addressed, the Seam container holds a bunch of component definitions. But, there aren't yet any instances.
Typically, a component definition has to wait until the context variable name associated with it is requested for
an instance to be created. There is one condition where the instance of the component is created when the
scope with it is associated is started. That is if the component is a startup component.
Licensed to Jaroslaw Gilewski <[email protected]>
4.5.4 Instantiating components at @Startup
The @Startup annotation, summarized in table 4.9, instructs Seam to create an instance of the component
eagerly when at the time when the component's scope is created. At the time of writing only application and
session-scoped components can be flagged as startup components, though other scopes may be added in the
future.
Table 4.9 The @Startup annotation flags a component to be instantiated automatically
Name:
Startup
Purpose:
Instructs the container to eagerly instantiate a component at system initialization for application-scoped components
or when the session starts for session-scoped components.
Target:
TYPE (class)
Attribute
Type
Function
depends
String[]
The context variable names of other components that should be started before this one, if
they are available. Dependencies should be in the same scope as the component itself.
If you add the @Startup annotation to the class definition of a component, and the component is scoped
to the application context, Seam will automatically create an instance of the component when the container
starts. This eager instantiation is consistent with the default behavior of singleton Spring beans. The instance is
available via the context variable for the lifetime of the container and thus does not have to be instantiated
when requested. The @Startup annotation is well suited for components that use the singleton design pattern.
WARNING
The problem with using the application scope to hold the primary business objects in your application is
that they cannot hold state. Due to the fact that they are shared amongst all threads, and synchronizing them
would be extremely expensive, putting client specific information in them is completely out of the question.
However, without state, their use is limited. Despite this general rule, the @Startup hook is useful for
resources such as a Hibernate SessionFactory or JPA EntityManager that do need to be in the
application scope, but are expensive to initialize.
If the component flagged with the @Startup annotation is scoped to the session context, then Seam will
automatically create an instance of the component when the HTTP session starts. This functionality is a unique
Seam feature. It enables you to have components that are automatically instantiated on a per-user basis.
The depends attribute on the @Startup annotation can be used to startup other components, even if
those other components are not defined as startup components. The dependent components are given as a list
of context variable names. The depends attribute also gives you some control over the order in which startup
components are instantiated, based on the position of the context variable name in the value of the depends
attribute.
Regardless of whether the component is instantiated eagerly by the container or it waits for the context
variable name to be requested by the application, Seam handles instance creation. While it is nice to have
Seam handle these details for you, there are times when you need to be there to help out with the creation of
the instance or to perform custom cleanup when it is being destroyed. Component life cycle callback methods
give you that chance to help out.
4.5.5 Component life cycle callbacks
At the beginning of the chapter, I mentioned that one of the benefits of using the Seam container to instantiate
your classes is that it manages the life cycle of the instance. You can then add code that participates in that life
cycle by registering two special life cycle methods. One method is invoked when the component instance is
Licensed to Jaroslaw Gilewski <[email protected]>
created and another when it is destroyed. These methods are identified, of course, through the use of
annotations. The method name is therefore irrelevant. Note that there can only be a single create method and a
single destroy method per component.
The method on a component annotated with @PostConstuct is called after an instance has been created
and initialized (meaning after the initial property values have been applied). The @PreDestroy method is
called before the component instance is removed from the context in which it resides. Both of these
annotations are part of the Java EE API. When working with a non-EJB component, you can use either the
standard Java EE annotations or the comparable set from the Seam API. The @Create annotation stands in for
the @PostConstruct annotation and the @Destroy annotation stands in for the @PreDestroy annotation,
as mapped in table 4.10.
Table 4.10 The component life cycle methods
When called
Java EE environment
Non-Java EE environment
After initialization of the component instance
@PostConstruct
@Create
Before component instance is removed from Seam context
@PreDestroy
@Destroy
To designate create and destroy methods for an EJB component, you use the standard Java EE life cycle
annotations and the EJB container handles the task of invoking these methods at the appropriate time. If you
are dealing with a JavaBean component, Seam takes on the role of calling the life cycle methods.
NOTE
Any of the life cycle methods called by Seam can be a no-argument method or accept the Seam component
definition—an instance of org.jboss.seam.Component—as the one and only parameter. The
getName() method on the component definition provides access to the context variable of the
component, which may be useful during an initialization routine.
Let's consider an example of using the create and destroy life cycle methods. In the code below, the
RegisterAction component records to the log file whenever it is created and destroyed. Since this
component is a JavaBean, Seam takes on the task of calling these two life cycle methods:
@Name("registerAction")
public class RegisterAction {
...
@PostConstruct
public void onCreate(Component self) {
log.debug("Created " + self.getName() + " component");
}
@PreDestroy
public void onDestroy() {
log.debug("Destroyed registerAction component");
}
}
#1
#2
<Annotation #1> @Create annotation also works here
<Annotation #2> @Destroy annotation also works here
You can also have collaborator components tap into the life cycle of a component by observing events
raised by the container when an instance is created and destroyed. Events are also raised when the instance is
bound to and unbound from a context variable. You learn about component events in the next chapter.
The create method is especially useful for performing post-initialization logic on the component, such as
validating its state or loading auxiliary resources that it needs. If you add the @Startup annotation to the
component, then the create method becomes a way to perform logic when the application initializes or when
Licensed to Jaroslaw Gilewski <[email protected]>
the user's session begins, depending on if the component is application- or session-scoped. Startup logic is
useful for performing tasks such as:
z
starting an in-memory database
z
applying a database upgrade script
z
loading seed data into the database
z
running an indexing service (i.e. Lucene)
z
starting up a third-party container or service (i.e. Spring, JMS)
z
initializing a third-party library or container
You can add the @Install(debug=true) to a startup component so that the initialization routine is
only performed when debug mode is enabled, which is useful for seeding a database in a test environment.
Another way to execute a startup routine is to have a method on a component observe the Seam container postinitialization event. You learn about events in the next chapter.
JavaBean components also support the Java EE standard @PrePassivate and @PostActivate
annotations. Methods on a JavaBean marked with these annotations are invoked by Seam when the HTTP
session is migrated between nodes in a cluster. If these annotations are used on an EJB component, then Seam
has no say in the matter and the EJB container takes on the task of invoking these methods.
Although components are the divas of the Seam container, even starts need help. Let's look at how
components get connected to one another.
4.5.6 Wiring components together
Being that Seam is a lightweight, dependency injection container, you may be eager to know how components
are wired together. I hate to disappointment you, but this is going to be a short section. Dependency injection is
one of Seam's biggest features and there is too much to get into right now. I will, however, give you a brief
introduction that gives you want you need to know for now.
The primary means of wiring Seam components together in Seam is a mechanism called bijection, which
is explained in detail in chapter 6. Bijection is controlled using annotations. The @In annotation placed on a
field or JavaBean property of a component tells Seam to assign, or inject, into that property the value of
component instance whose context variable name matches the name of the recipient property. The "bi" prefix
is used in the name because in addition to injecting components, the inverse is possible. The @Out annotation
placed on a field or JavaBean property of a component declares that the value of that property is to be bound to
a context variable of the same name.
Bijection is a new approach to inversion of control. Seam also supports the traditional dependency
injection mechanism—dubbed static dependency injection, which is controlled using the component
descriptor. You are going to learn about static dependency injection in the next chapter and bijection in chapter
6. There is one exception to the rule that annotations are used to perform dynamic dependency injection. The
@Logger annotation is used to inject a logger instance at component creation time.
4.5.7 Injecting a @Logger instance
Seam can automatically create an instance of a logger that is configured exclusively for the component into
which it is injected. This feature should be a welcomed relief for anyone who is tired of fiddling with mundane
logger declarations. But Seam also offers some other nice enhancements. For instance, you can use value
expressions inline in the log messages. These smart log messages reduce the tedium of providing contextual
information in the message (the name of the authenticated user, the account name, the order id, etc).
To have Seam inject a logger instance into a property of a component, simply place that @Logger
annotation above the property whose type is org.jboss.seam.log.Log.
Licensed to Jaroslaw Gilewski <[email protected]>
@Name("registerAction")
public class RegisterAction() {
@Logger Log log;
// ...
}
The logger instance is injected after the component is instantiated (but before the @PostConstruct
method is invoked). Table 4.11 shows a summary of the @Logger annotation.
Table 4.11 The @Logger annotation. Seam injects a logger instance into properties bearing the @Logger annotation.
Name:
Logger
Purpose:
Instructs Seam to create a logger instance and inject it into this property. The injection occurs when the component
instance is created, as part of static dependency injection.
Target:
FIELD (of type Log)
Attribute
Type
Function
category
String
A custom logging category for this instance. If a category is not provided, then the full
qualified class name is used instead.
FAQ
How do I configure the logging? There is nothing that you have to do differently if you use either Log4J or
the standard JDK logging. Seam uses Log4J if it available on the classpath, falling back to standard JDK
logging if it is not. Seam's Log implementation is merely a wrapper around the existing log frameworks,
adding the convenience of injecting the log instance via dependency injection.
An example of a log message would be as follows:
log.debug("Registering golfer #{newGolfer.username}");
The messages are contextual because they can access any Seam component that is "in context" at the time
the log message is reported.
4.5.8 Where all components go to die
Just like any other Java objects, component instances go to the garbage collector to die. An instance is
removed either when the context variable to which it is bound is assigned a null value, the context in which it
is held ends (i.e. goes out of scope), or it is explicitly removed using the Seam API. When the instance is
terminated, Seam calls the destroy method covered in section 4.5.5 before handing it over to the garbage
collector.
That covers the life of a JavaBean component and the instances that it spawns. Seam is also capable of
participating in the life of an EJB 3 session bean component. But the role that Seam plays is handled
differently in this case since it does not have the same degree of control over the component. Let's see what
these differences are.
4.6 Using EJB 3 session beans in Seam
After making much ado about how Seam stitches EJB 3 and JSF together, you may be wondering why I
avoided the use of an EJB 3 session bean as the JSF action handler in the earlier example. If you are waiting
Licensed to Jaroslaw Gilewski <[email protected]>
for the big unveiling, I am sorry to disappoint you. There just isn't much to show. The switch from a JavaBean
to an EJB session bean is just a matter of adding a couple of annotations and an interface and viola, you have
an EJB component.
NOTE
In order to use an EJB session bean, your project needs to be deployed in the EAR package format. The
EJB session beans are packaged in an EJB JAR and the web application is packaged separately in a WAR.
These two archives are then bundled together to form an EAR. To create an EAR project, you need to run
seam setup again, this time choosing the EAR project option. Unfortunately, by using the EAR format
you lose incremental hot-redeployment of JavaBean components.
Up to this point, I have presented many areas of Java EE that Seam replaces with its own solution. For
instance, the page descriptor replaces the declarative JSF navigation and adds page-oriented features, the
@Name annotation replaces the JSF managed bean facility, and the Seam container handles all of the variable
contexts. These improvements are the exception rather than the rule. Seam tries to use as much of the Java EE
standard as possible. This reuse is most apparent in the area of EJB 3.
In this section, you will learn that Seam can derive a component from an EJB 3 session bean—herein
referred to as a Seam session bean component. There is nothing proprietary about these components. The EJB
3 container does most of the work of managing them. Seam only steps in to bind the component to a context
variable and apply its own set of interceptors around method invocations. While we are on the topic of
ownership, let's consider who owns a Seam session bean component, the EJB 3 container or Seam?
4.6.1 Whose component is it, anyway?
At first, you might not give the question of who owns a Seam session bean component much thought. The
component scanner finds a class in an EJB JAR which has the @Name annotation and it creates a component
for it. But the class has already been picked up by the EJB 3 container on account of it having a @Stateless
or @Stateful annotation or declared as an EJB component in an XML descriptor. So, who really owns the
component, the EJB 3 container or Seam?
The answer is, both. Session beans that are designated as Seam components have a sort of dual
personality. They act as both Seam components and EJB 3 components, taking on the services of both
containers. The EJB 3 container manages the session bean, but Seam gets its hands on the life cycle of the
session bean using AOP interceptors and adopts it into the Seam container.
Seam session bean components differ from other Seam component types in one fundamental way. Seam
does not create instances of session bean components using the default constructor of the class. Instead, the
work of instantiating the class is delegated to the EJB 3 container. The EJB 3 container is responsible for
managing session bean components in the same way that Seam manages JavaBean components.
When the Seam container determines that an instance of the component needs to be created, Seam asks the
EJB 3 container for a reference to the session bean instance using a JNDI lookup. Once Seam has a reference
to the session bean instance, Seam adds additional services to it and then binds it to a context variable, just as
Seam would do with a JavaBean component instance. The two containers are working together to create and
initialize an instance of a session bean component.
What about message-driven beans? I am purposefully not discussing message-driven beans (MDB) in this
section. Although an MDB can act as a Seam component, the dynamics are very different. Message-driven
beans cannot be instantiated by the application, which means they are not given a context variable name
and are never associated with a context. Instead, they listen for messages on a JMS topic or queue and get
instantiated by the EJB 3 container to handle a message when it arrives.
Let's take a closer look at how a session bean becomes a Seam component and what it means for the
component's functionality.
Licensed to Jaroslaw Gilewski <[email protected]>
4.6.2 The making of a Seam session bean component
There are a handful of differences between a JavaBean component and a Seam session bean component that I
would like to point out. One set of differences pertain to the requirements for creating an EJB 3 component.
The other set of differences has to do with how Seam treats these components when compared to other Seam
components.
For a class to be a Seam session bean component, it must be declared as a Seam component—indicated by
the @Name annotation on the class—and be declared as an EJB 3 session bean component—indicated by a
@Stateful or @Stateless annotation on the class. As an alternative to using annotations, both containers
support the use of XML descriptors for declaring their respective components.
All session beans must also implement either a local or remote interface, as required by the EJB
specification. The interface must be annotated with either @Local or @Remote or must be declared as an EJB
3 interface in an XML descriptor. In order for a method on a session bean component to be accessible to the
client (e.g. a Seam application), it must be defined on the EJB 3 interface. It's not enough just to declare the
method as public on the implementing class. If it isn't on the interface, the method just doesn't exist in the eyes
of Seam, even if accessed from an EL expression, which does not enforce type-checking.
There is one final requirement that only pertains to stateful session bean components. All stateful session
beans acting as Seam components must define a no arguments method marked with the EJB 3 @Remove
annotation. The remove method is called when the context in which the session bean reference is stored is
destroyed (i.e. end of request, conversation, session, etc). The method can be used to clean up any state
associated with the use of the reference (i.e. state within the Seam application). If this method is invoked
directly, it will lead to an immediate removal of the session bean reference, with some special handling in the
case an exception is thrown.
INFO
Typically, @Remove annotation is added to the same method that is marked with the @PreDestroy life
cycle annotation, if present. There is a distinct difference between these annotations, though. The method
marked with the @Remove annotation is called when Seam removes the reference to the instance and the
method marked with the @PreDestroy annotation is called when the EJB 3 container destroys the
instance itself.
The JSF action handler for the registration page can be rewritten as a Seam stateful session bean
component. Begin by renaming the implementation class from RegisterAction to
RegisterActionBean:
package org.open18.action;
import ...;
import javax.ejb.Stateful;
@Stateful
@Name("registerAction")
@Scope(ScopeType.EVENT)
public class RegisterActionBean {
@Logger private Log log;
@In FacesMessages facesMessages;
public void register() {
log.info("registerAction.register() action called");
Licensed to Jaroslaw Gilewski <[email protected]>
facesMessages.add("register");
}
@Remove
public void destroy() {}
}
Notice that the @Logger and @In annotations are still present. Seam session bean components can use
functionality provided by Seam just like JavaBean components, which you will study in the next section.
The next, and final, step is to reserve the RegisterAction type as the EJB 3 interface and declare on it
any methods that need to be accessible to JSF. The method annotated with @Remove must also be defined on
the interface. The result is shown here:
package org.open18.action;
import ...;
import javax.ejb.Local;
@Local
public interface RegisterAction {
public void register();
public void destroy();
}
Unlike JavaBean components, the methods of an EJB component are automatically wrapped in a
transaction, unless specified otherwise. You haven't had to worry about transactions up to this point since
Seam is automatically wrapping each request in a set of JTA transactions. However, if you were to disable
Seam's transaction management, the transactional behavior of EJB 3 components would kick in.
Besides the difference in default scopes and transactional behavior, the session bean components operate
just like their JavaBean counterparts. Seam truly does make the use of EJBs a preference rather than a design
change.
4.6.3 The mechanics of the interaction
Let's take a closer look at how Seam obtains session bean references from the EJB 3 container and how it
participates in the life cycle of the server-side component. There are several references in this section to
features of Seam that are covered in chapter 6, such as bijection and interceptors. Feel free to come back to this
section once you have learned that material. You can safely skip this section if you are only interested in the
high level view of Seam EJB components right now.
Playing a part in the life of a session bean component
From the moment the Seam component scanner detects the session bean component, Seam begins to
participate in its life. Seam registers itself as an EJB interceptor on all session bean components. Acting as an
interceptor gives Seam the opportunity to tap into post-construct logic of the EJB component to register
additional server-side method interceptors that decorate the component with services such as bijection,
conversation controls, and event handling, all of which you will learn about in later chapters. The point is that
Seam session bean components get the same functionality as their JavaBean counterparts. Seam brings these
features to EJB session beans by asking to be a part of its life cycle since it is not the component's primary
owner.
Licensed to Jaroslaw Gilewski <[email protected]>
To register the Seam interceptor on all of the session beans in an EJB archive, you define the following
interceptor mapping in the EJB deployment descriptor, META-INF/ejb-jar.xml:
<ejb-jar xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd"
version="3.0">
<interceptors>
<interceptor>
<interceptor-class>
org.jboss.seam.ejb.SeamInterceptor
</interceptor-class>
</interceptor>
</interceptors>
<assembly-descriptor>
<interceptor-binding>
<ejb-name>*</ejb-name>
<interceptor-class>
org.jboss.seam.ejb.SeamInterceptor
</interceptor-class>
</interceptor-binding>
</assembly-descriptor>
</ejb-jar>
You can refine this configuration if you only want to apply the Seam interceptor to a subset of the EJB 3
components available in the archive by changing the pattern in the <ejb-name> element. You also have the
option of forgoing the XML descriptor and instead installing the interceptor on each session bean component
individually using the EJB 3 @Interceptors annotation, as follows:
@Stateful
@Name("registerAction")
@Interceptors(SeamInterceptor.class)
public class RegisterActionBean implements RegisterAction {
// ...
}
Intercepting session bean invocations is only part of the work Seam has to do to expose a session bean as a
Seam component. The next step happens when the Seam component scanner comes across the session bean
implementation class annotated with @Name (or declared in the component descriptor). The component
scanner simply mines the class definition for information, looking for Seam annotations as it normally would
for any other component, and then stores the component definition away in the Seam container. At this point,
no interaction occurs with the EJB 3 container.
Obtaining a session bean reference
The fusion with the EJB 3 container occurs when the Seam container receives a request for an unassigned
context variable that is associated with a session bean component. To resolve a value, Seam performs a JNDI
lookup to get a reference to the corresponding session bean in the EJB 3 container. This lookup is part of the
standard EJB mechanism. Seam only uses this lookup when "instantiating" the component, even though it
doesn't actually instantiate a class but rather delegates the work to the EJB 3 container.
Licensed to Jaroslaw Gilewski <[email protected]>
WARNING
You should not lookup the Seam session bean components in JNDI directly in application code. Doing so
will compromise the session bean's integrity as a Seam component. Once you decide to make a session
bean a Seam component, you should always access it through the Seam container by its context variable
name.
There are two ways to declare the JNDI name that Seam should use to lookup the session bean. You can
either specify a name explicitly at the component-level or you can define a template that Seam will use to
resolve the JNDI name for individual components. The first option is the most straightforward, yet also the
most tedious. Here we supply the JNDI name on the session bean component above using the @JndiName
annotation.
@Stateful
@Name("registerAction")
@JndiName("open18/registerAction/local")
public class RegisterActionBean implements RegisterAction {
// ...
}
A summary of the @JndiName annotation is provided in table 4.12.
Table 4.12 The @JndiName annotation specifies the JNDI lookup name for an EJB component
Name:
JndiName
Purpose:
The name used by Seam to locate the corresponding EJB component in the JNDI registry when allocating a new
instance of this component.
Target:
TYPE (class)
Tuning the JNDI pattern
It's possible to control how Seam resolves a JNDI name without having to use the @JndiName annotation on
every session bean component. It requires configuring a built-in component, which you are going to learn
about in the next chapter. You may want to come back to these instructions after reading that chapter.
JNDI names are about as variant as the weather from one Java EE application server to the next. To
protect you from the inclement weather, it is better to formulate a pattern that Seam can use to automatically
resolve the JNDI name. The jndiPattern property on the org.jboss.seam.core.init component is
used as a template for deriving the JNDI name from the context variable name. Assume that the following
template has been defined:
<core:init jndiPattern="open18/#{ejbName}/local"/>
NOTE
The jndiPattern property on <core:init> is special in that it is not treated as an EL value
expression, even though it uses the same syntax. The #{ejbName} is used as a replacement token to
resolve the JNDI name that Seam uses to lookup the session bean reference.
The JNDI name is formed by replacing the #{ejbName} token with the first non-empty value in the
following list:
Licensed to Jaroslaw Gilewski <[email protected]>
z
the value of the name attribute on the @Stateful or @Stateless annotation
z
the unqualified class name of the component
z
the value of the ejb-name node in the EJB deployment descriptor
Since the @Stateful annotation on the session bean above does not specify the name attribute, the
#{ejbName} resolves to registerAction in the example, so the entire pattern becomes
open18/registerAction/local.
Projects created by seam-gen use the property replacement token @[email protected] for specifying a value
for the jndiPattern property. The value for the token is defined in a components.properties file, which you
can customize for your application server:
jndiPattern=\#{ejbName}/local
This JNDI pattern shown here applies to JBoss AS. While the pattern is still application server specific, it
can be easily changed or overridden when deploying to another application server.
Everything that happens after the lookup of the session bean reference up until the reference to the newly
minted instance is returned to the caller is controlled by the EJB 3 container. Let's consider what happens after
that point.
Infusing session bean components with Seam functionality
Once Seam has obtained a client-side reference to the session bean, it wraps the proxy in additional client-side
interceptors, assigns it to the context variable name in the configured scope, and returns the instance to the
caller. Unless specified otherwise, the default scope for stateless session beans is the stateless context and for
stateful session beans is conversation context.
What makes Seam session bean components so unique is that they have a hybrid of functionality from the
EJB container and the Seam container. For instance, all methods are automatically wrapped in a containermanaged transaction, unless specified otherwise, courtesy of the Java EE container. This behavior is expected
for session beans. But the component can also take advantage of bijection, a service provided by the Seam
container that allows arbitrary objects to be injected into session beans, something that is not possible in a pure
EJB 3 components. Table 4.13 lists all of the services that are available to a hybrid Seam-EJB 3 component.
Table 4.13 A list of the core annotations available on a hybrid Seam session bean component.
Annotation
Provided by
When applied, properties
@Resource
Java EE (web-tier, EJB 3 tier)
post-construct, static
@EJB
Java EE (EJB 3 tier)
post-construct, static
@PersistenceContext
Java EE (web-tier, EJB 3 tier), proxied by Seam post-construct, static
@Interceptors
Java EE (EJB 3 tier)
around-invoke, stateless
@Interceptors (on @interface)
Seam
around-invoke, stateless or stateful
@AroundInvoke
Java EE (EJB tier)
around-invoke
@PreDestroy
Java EE (EJB tier)
pre-destroy
@PrePassivate
Java EE (EJB tier)
pre-passivate
@PostConstruct
Java EE (EJB tier), enhanced by Seam
post-construct
@In, @RequestParameter,
@DataModelSelection,
@DataModelSelectIndex
Seam
around-invoke, dynamic
@Out, @DataModel
Seam
around-invoke, dynamic
@Logger
Seam
post-construct, static
Licensed to Jaroslaw Gilewski <[email protected]>
The only catch is that by mixing services, taking advantage of what Seam has to offer, your session beans
are no longer portable to pure EJB 3 environments. Even then, you just need Seam on the classpath to
"upgrade" your container to support them. If you would rather keep your session bean components compliant
with a standard EJB 3 container, you should refrain from using any Seam APIs that affect the behavior of the
component, which includes bijection annotations, security annotations, non-Java EE life cycle annotations, and
perhaps even conversation annotations. For instance, to get a reference to another EJB component, you would
use the @EJB injection annotation. But, why? If you are employing Seam, you might as well take advantage of
what it has to offer. That means letting go of the purist flag and using Seam in combination with the Java EE
services on whatever application server you deploy.
You have now seen a day in the life of both JavaBean components and session bean components from
behind the scenes. It's time to learn how to get components to perform in your application. You are going to
learn all of the ways in which you can access a component and get a component instance with which your
application logic can interact.
4.7 Accessing components
Seam components are the key enabler for integrating technologies in a Seam application. Consequently,
Seam's forte is providing unified access to those components across the board. There are three ways to ask for
an instance of a Seam component from the Seam container. You can use:
z
the context variable name
z
EL notation (binding expression) that uses the context variable name
z
the Java class of the component
Table 4.14 gives a preview of where component instances can be accessed.
Table 4.14 Places where Seam components can be accessed
From where
How accessed
JSF view
EL notation
Seam annotation (e.g. @In, @Out)
context variable name or EL notation
Java code: Component.getInstance()
context variable name or component class
Java code: Expressions.instance()
EL notation
JPQL or HQL query
EL notation
JSF message (i.e. FacesMessage)
EL notation
Java properties (i18n bundle or component property)
EL notation
JavaScript using Seam Remoting
context variable name, component class or EL notation
Seam component descriptor (e.g. components.xml)
context variable name, component class or EL notation
Seam page descriptor (e.g. pages.xml)
EL notation
jPDL page flow descriptor
EL notation
jBPM business process descriptor
EL notation
Spring configuration file
context variable name, EL notation
In the last chapter you saw some examples of Seam components being used in page orchestration logic.
That's just the beginning. The EL is used in JSF views, page flows, business process definitions, annotations,
and Java code. You begin to learn about these options in this section. Let's begin by considering what happens
when a component instance is requested.
Licensed to Jaroslaw Gilewski <[email protected]>
4.7.1 Access modes
As it has probably been ingrained into you by now, a component is a recipe that describes how to create a Java
object that is managed by the Seam container (a component instance). When you request a component, you are
effectively asking the container to hand you back an instance of the component. This request operates in one of
two modes, both of which are depicted in figure 4.2:
z
Lookup only—In this mode, Seam will search for a component instance that is bound to the requested
context variable. It either looks for an instance in the specified scope, or, if a scope is not provided, it
uses a hierarchical search of all the contexts. If an instance cannot be found, a null value will be
returned. This mode is the default for the @In annotation, covered in chapter 6.
z
Lookup with option to create—In this mode, Seam performs the same search that is used in the lookup
only mode. The difference is that if an instance cannot be found, Seam instantiates an instance
according to the component definition and stores it in the context specified by that definition. Instance
creation is represented by the optional clause in figure 4.2. When a component is referenced via a
value expression, this mode is used to locate an instance.
There is one case when Seam will create a component instance when operating in lookup only mode. If the
context variable is correlated with an "auto create" component, a new instance of the component is created if
one does not exist regardless of what mode is being used to lookup the instance. An auto create component is
formed by placing the @AutoCreate annotation on the component class or setting the auto-create
attribute of the component definition declared in the component descriptor to true. You learn about declaring
components in the component descriptor in the next chapter. Factory components, which you learn about in
chapter 6, can also be made to be auto create components.
Auto create functionality eliminates the need to specify the create option on the context variable at the
point of access, shifting the responsibility to the component definition. The @AutoCreate annotation is
summarized in table 4.15.
Table 4.15 The @AutoCreate indicates that the component should be automatically instantiated
Name:
AutoCreate
Purpose:
When added to the class definition of a Seam component, it indicates that Seam should automatically instantiate the
component when its context variable is requested if an instance does not already exist.
Target:
TYPE (class)
Several methods of accessing a Seam component are summarized in table 4.16. The final three examples
show examples of the @In annotation, which allows the bijection mechanism to inject component instances
into the properties of a Seam component. For now, you can think of the @In annotation as a short-hand for
looking up the context variable explicitly using Component.getInstance() and then assigning it to a
property of the component class. This table also indicates the conditions under which the instance will be
created if it does not exist in the container.
Table 4.16 Ways to access a Seam component
Example usage
Create if does not exist?
Component.getInstance("contextVariableName")
yes
#{contextVariableName}
yes
Component.getInstance(ComponentClass.class)
yes
@In("contextVariableName")
no, unless component supports "auto create"
@In(value = "contextVariableName", create = true)
yes
@In("#{contextVariableName}")
yes
Licensed to Jaroslaw Gilewski <[email protected]>
That covers the conditions by which Seam will create component instances. Let's take a closer look at the
strategies you can use to access these component instances.
4.7.2 Access strategies
In a Seam application, you will find references to components in annotations, EL notation, and via the Seam
API. Throughout this chapter, I have made mention of interacting with the Seam container. This
communication occurs through the Seam API. Regardless of which access strategy you use, it always trickles
down to a Seam API call. Therefore, let's study this interaction.
Seam API
Seam offers several static getInstance() methods on the Component class that are capable of locating
and creating component instances. You generally want to avoid using this means of access in the middle of
your business logic because it prevents the code from being used outside of the Seam container—it's not
kosher POJO development. But, it's important to understand because it how instances are requested from the
Seam container.
You can access a component using the Seam API by passing either the context variable name (e.g.
passwordManager) or the Java class object (e.g. PasswordManager.class) to the
Component.getInstance() method. When you pass the Java class object, Seam will resolve the context
variable name automatically from the component definition and continue the lookup based on the context
variable. Here is an example of a lookup based on a context variable name:
PasswordManager passwordManager =
(PasswordManager) Component.getInstance("passwordMananger");
You can also access the context variable directly from the context where it is stored. To do so, you use the
Context.get*Context() to retrieve the context, where the * is a placeholder for the name of the context,
and then use the get() method to pull the component instance out of the context based on the context variable
name:
PasswordManager passwordManager =
(PasswordManager) Context.getEventContext().get("passwordManager");
The main difference between Component.getInstance() and accessing the instance directly from the
context in which it is stored is that Component.getInstance() will create an instance if one doesn't exist
(unless you pass false as the second argument), whereas accessing the instance using the Context API can
never create the instance. The context API merely gives you direct access to the storage location of context
variables.
You can search across all contexts using the Context.lookupInStatefulContexts() method. This
method is used by Seam to locate a component instance when no scope is provided at the point of access.
PasswordManager passwordManager = (PasswordManager)
Contexts.lookupInStatefulContexts("passwordManager");
Let's go back to the RegisterAction component. The implementation in listing 4.8 accesses the
dependent components using the Seam API rather than relying on the property values to be injected.
Licensed to Jaroslaw Gilewski <[email protected]>
Listing 4.8 The register action handler implemented using various component access strategies
package org.open18.action;
import ...;
@Name("registerAction")
public class RegisterAction {
@Logger private Log log;
public String register() {
log.debug("Registering user #{newMember.username}");
Context eventContext = Contexts.getEventContext();
PasswordBean passwordBean = (PasswordBean)
eventContext.get("passwordBean");
if (!passwordBean.verify()) {
return "failure";
}
Member newMember =
(Member) Contexts.lookupInStatefulContexts("newMember");
PasswordManager passwordManager = (PasswordManager)
Component.getInstance(PasswordManager.class);
newMember.setPasswordHash(
passwordManager.hash(passwordBean.getPassword()));
EntityManager entityManager = (EntityManager)
Component.getInstance("entityManager");
entityManager.persist(newMember);
return "success";
}
}
Once again, I will ease your thoughts by assuring you that accessing components through the Seam API is
mostly academic. It is much cleaner, and more commonly acceptable, to use bijection annotations to declare
the component wiring. However, there are two cases when it is extremely useful to use the Seam API:
singleton lookups and test cases.
Let's enhance the integration test from earlier that is used to validate the registration process. Listing 4.9
shows an implementation of that test that uses the Seam API to instantiate and populate the form backing
beans in the Update Model Values phase, emulating a form submission, and then invokes the register()
method in the Invoke Application phase.
Listing 4.9 The registration integration using the Seam API to set and retrieve component instances
package org.open18.action;
import ...;
public class RegisterActionIntegrationTest extends SeamTest {
@Test(groups = {"level.integration", "speed.slow"})
public void registerValidMember() throws Exception {
new FacesRequest("/register.xhtml") {
protected void updateModelValues() {
Licensed to Jaroslaw Gilewski <[email protected]>
Member member = (Member)
Component.getInstance("newMember");
member.setUsername("twoputt");
member.setEmailAddress("[email protected]");
PasswordBean passwordBean = (PasswordBean)
Component.getInstance("passwordBean");
passwordBean.setPassword("ilovegolf");
passwordBean.setConfirm("ilovegolf");
}
protected void invokeApplication() {
String outcome =
invokeMethod("#{registerAction.register}");
assert outcome != null && outcome.equals("success");
}
}.run();
}
}
The test should once again succeed since the passwords verify and the required fields on the member
entity are populated.
You often see the Seam API used to lookup component instances from within a static instance()
method on the component itself. What you get by doing this is a completely safe mechanism for creating
singletons, thanks to the protection provided by the Seam container. Without Seam, you might rely on static
fields to hold the instance, which risks thread safety or wrecks your chances of moving to a clustered
environment. Instead, you can allow the Seam container to intelligently manage the instance. There is nothing
special about the implementation of this method other than it taps into the Seam API. For instance, to
implement this technique on the PasswordManager component, the method would be declared as:
public static PasswordManager instance() {
return (PasswordManager)
Component.getInstance(PasswordManager.class);
}
You
would
then
obtain
an
instance
of
the
PasswordManager
by
calling
PasswordManager.instance(). This technique is useful for returning a implementation subclass of a
component or even a component of a completely different type. An example of this divergent return value is
seen on the Seam transaction component (org.jboss.seam.transaction.Transaction). The
Transaction.instance() actually returns an instance of the JTA UserTransaction interface, selecting the
implementation according to the transaction type configured in the component descriptor. You learn about
configuring transactions in Seam in chapter 9. If you are interested in how this lookup works, I encourage you
to dive into the Seam course code.
A far more ubiquitous and flexible means of component access is the use of EL notation. That is our next
stop. You have already seen several examples of this syntax in the examples from this chapter. Nevertheless,
there are still some important points to be made.
EL notation
Value and method binding expressions are the lingua franca of Seam. The reason EL notation is so appealing is
because it cleanly separates component access from component creation. EL notation is like having a sticker
Licensed to Jaroslaw Gilewski <[email protected]>
slapped on your code that says Insert Component Here. The rest is up to the EL resolver, and, in turn, Seam.
This means of access is really the key for being able to instantiate components from anywhere and makes the
references portable to any EL resolver implementation, not just the Seam EL resolver.
Value expressions are used both to resolve a component instance and to bind to it. The latter is used by
JSF to capture input values and apply those values to the bean properties referenced in the value expression.
Let's consider an example. The following snippet taken from the registration form binds the password and
confirm fields of PasswordManager to input fields:
<h:inputSecret id="password" value="#{passwordManager.password}"/>
<h:inputSecret id="confirm" value="#{passwordManager.confirm}"/>
When JSF encounters these fields, it will ask Seam to resolve the value binding expression. Seam will
automatically create an instance of the PasswordManager component (if it hasn't already done so) and bind
the password and confirm properties to the input values in the form. When the user submits the form, the
values entered will be assigned to these two properties through the value expression.
In addition to using value and method expressions in the usual places, you can also access them using the
Seam API. Let's look at how we could rewrite the portion of the register() method that works with the
PasswordBean to use value and method expressions instead of directly accessing the object. The following
snippet shows the relevant portions of this method.
Boolean valid = (Boolean)
Expressions.instance()
.createMethodExpression("#{passwordBean.verify}")
.invoke();
if (!valid) {
return false;
}
Member newMember = (Member) Component.getInstance("newMember");
PasswordManager passwordManager = (PasswordManager)
Component.getInstance(PasswordManager.class);
String password = (String)
Expressions.instance()
.createValueExpression("#{passwordBean.password}")
.getValue();
newMember.setPasswordHash(
passwordManager.hash(password));
You can also use a value expression to assign a value. For instance, let's say that you want to clear the
confirm password when it is wrong. You can once again use a value binding expression.
Expressions.instance()
.createValueExpression("#{passwordBean.confirm}")
.setValue(null);
We just covered good old-fashioned EL. But Seam offers so much more. Let's see what Seam does to make the
EL even more powerful as an integrator.
Licensed to Jaroslaw Gilewski <[email protected]>
Seam's enhanced EL
The EL is ideal for integrating technologies because it uses a simple, technology agnostic syntax. It
standardizes on the JavaBean property notation for value expressions and no-argument methods for method
expressions. Unfortunately, its simplicity is also its downfall. Being at the intersection of so many
technologies, you often find that if it supported that one additional feature, you would be able to take the
integration to the next level. This longing is especially strong when attempting to implement advanced layouts
in a JSF view, which you encounter in later examples in the book.
Fortunately, Seam supports several non-standard enhancements to the EL, provided in part by the JBoss
EL library and supplemented by the Seam EL resolver:
z
parameterized method binding expressions
z
parameterized value binding expressions
z
"magic" bean properties
z
projections
The first two enhancements allow you to use method arguments in the EL expression. The arguments are
surrounded in parenthesis and separated by commas, just like in the Java syntax. Each argument is interpreted
as a context variable unless it is quoted or is a number, in which case it is treated like a string or a number,
respectively.
You can use a parameterized method binding to pass data to an action handler. Using the example from
earlier in the chapter, you could change the register() action handler to pass the newGolfer and
passwordBean context variables as arguments:
<h:commandButton id="register" value="Sign Up Now!"
action="#{registerAction.register(newGolfer, passwordBean)}"/>
Be aware that the context variable is resolved when the action handler is invoked, not when the button to
which it is bound when it rendered. Since both newGolfer and passwordBean are instantiated and
populated during postback, the context of the components does not present a concern.
You can also pass parameters to value binding expressions. However, note that when doing so you have to
use the full method name of the property (i.e. getName()), not the shorthand syntax (name). Let's say that
you are creating a page in which you need to be able to pull up the tees for a particular hole on the golf course.
This example is taken out of context, but given that need, the following value expression gives you access to
data that otherwise would not be attainable with a basic EL value expression:
#{teeSet.getTeesByHoleNumber(10)}
The parameterized syntax provides access to methods that do not follow the JavaBean property syntax. For
instance, that pesky size() method on collections and equally evasive length() method on strings is not
reachable using a standard value expression. But, alas, you can use the parameterized method syntax to get
there:
#{course.holes.size()}
#{course.name.length()}
Recognizing that there are a handful of collection methods that are extremely useful, but happen to not fall
under the JavaBean property naming convention, Seam developers have weaved them into the Seam EL
resolver as "magic" methods. These properties are summarized in table 4.17, which map to equivalently named
no-argument methods on the type.
Licensed to Jaroslaw Gilewski <[email protected]>
Table 4.17 The magic bean properties on various collection types supported by the Seam EL resolver
java.util.Collection
java.util.Map
javax.faces.DataModel
size
entrySet
keySet
size
values
empty
size
The Seam EL resolver also recognizes the Seam contexts used as the root of the EL expression, providing
direct access to the map of variables in each context. The name of the map for each context is formed by
appending Context to the lowercase name of the context. For instance, eventContext is a map of
variables in the event context. The PasswordBean instance is accessible via EL notation as
#{eventContext.passwordBean}.
The parameterized syntax for value and method expressions is only available if you are using JSP >= 2.1
or Facelets (another compelling reason for using Facelets). The magic methods are available across the board.
Because of limitations in the JSP and Facelets compilers, the final enhancement, projections, is only available
in expressions created and resolved in Java code or in descriptors (XML files). Projections allow you to mine a
collection for values. For instance, let's say that you wanted to get all of the colors used for tee sets for a
course:
#{course.teeSets.{ts|ts.color}}
The ts acts as the iterator variable for the teeSets collection, and the pipe character (|) separates the
iterator variable from the nested expression. It is equivalent to the following pseudo-code:
List result = new ArrayList()
for (ts : course.teeSets) {
result.add(ts.color)
}
return result;
Projections work for any collection type (list or set), map (entries), or array. The resulting type is always a
java.util.List. Though not demonstrated in this example, the value expression used in the projection can
use the parameterized syntax described above.
Projections can also be nested. This allows you to descend into collections returned by the collections,
with each level of nesting using the same pattern of iterator variable and nested expression separated by the
pipe character. Let's say that you wanted to amass a collection of the distances of all the tees on the course:
#{course.teeSets.{ts|ts.tees.{t|t.distance}}}
To get access to all the tees, instead of the tee distances, you have to reference the iterator variable in the
expression segment, highlighted in bold:
#{course.teeSets.{ts|ts.tees.{t|t}}}
Projections are one of those features that you use if you are trying to save typing and are used to languages
like Ruby and Groovy that let you do one-liners. You likely won't need to use them that often. What's
interesting about them is that it shows that Seam is trying to stretch the limits of Java EE to offer nextgeneration efficiencies.
Licensed to Jaroslaw Gilewski <[email protected]>
The final place you will access Seam components is in annotations. But you are going to get a heavy dos
of this access strategy in chapter 6, so covering it now would be premature. You should now have an
appreciation for all the ways in which you can interact with the Seam container for the purpose of retrieving
component instances. If you still feel uneasy about how to use these means of access, trust that you are going
to get plenty of practice as you progress through the book. In fact, every chapter has something to do with
Seam components.
4.8 Summary
This chapter covered the two essential concepts in Seam, components and contexts. Components are
instructions for how to create managed objects and contexts are the buckets in which those instances are
stored.
The chapter opened by introducing Seam's rich contexts, which provide a centralized storage mechanism
for objects. You learned that objects stored in these buckets are known as context variables. You were briefed
on how Seam's context differ from those in the Java servlet API. With contexts that are better oriented to
support object life spans that align with application use cases, you should feel encouraged to track state in your
objects without fear of memory leaks, expensive replication, or concurrency problems.
The term component was given meaning in the context of Seam, since it has been recycled enough times
in the industry that its meaning is ambiguous. You learned that in Seam a component is a blueprint for creating
a managed object. A instance is created when the context variable name assigned to a component definition is
requested and an instance doesn't already exist. In this sense, the Seam container is a factory for creating
component instances. You now understand that Seam does more than just instantiate the component, but that it
also wraps it with interceptors that allow Seam to continue to weave functionality into the instance throughout
its life time. Many of the core interceptors will be covered in chapter 6, and additional ones pertaining to
conversations and persistence in chapter 7 and 9, respectively. You learned about various aspects of a
component's life and the callbacks that can be registered to hook custom code into that life cycle.
Throughout the chapter, you saw examples of how to access components. Since components are the
nucleus of Seam, what you have seen is merely a warm-up for what is to come. You will see components used
in every remaining chapter in this book. If by some chance you don't feel comfortable with how to access them
yet, you will soon enough.
Much of this chapter has been to focus on annotations. In the next chapter, you are going to learn about an
XML-based approach to defining components. You will discover that this alternative approach is about more
than just moving to angled brackets instead of @ signs. You will learn that it gives you a way to configure
components by assigning initial values. Component configuration, as this mechanism is termed, builds out the
basic component knowledge that you learned about in this chapter.
Licensed to Jaroslaw Gilewski <[email protected]>
5
The Seam component descriptor
Seam embraces annotations to keep you out of the XML weeds as much as possible. You are a Java (or
Groovy) developer, darn it, and that is the language you should be allowed to use to program your application.
Despite this noble statement, it would be misleading to say that Seam eliminates XML entirely. It doesn't.
If you are one of those good old-fashioned XML enthusiasts, you will be glad to hear that you can enjoy your
Seam meal with a side of angled brackets. Some use of XML in Seam is a personal preference. There are also
parts of Seam which warrant the use of XML as the primary means of configuration. The page descriptor that
you learned about in chapter 3 is one such example. It just doesn't make much sense to declare such dynamic
rules using annotations or inline Java. Given that compiled metadata isn't appropriate for this purpose, we
tolerate the XML.
Component definitions are another example where XML proves useful. You see, annotations such as
@Name are easy enough to add to classes under your control. However, if the class you are trying to declare as
a Seam component is sealed in a third-party JAR file or you need to configure Seam's built-in functionality,
annotations don't do you much good. You need another way to tell the Seam container how to deal with these
classes. That means resorting to the use of XML. The XML in Seam isn't all old-fashioned, though. Thanks to
XML namespaces, which are covered in this chapter, you may almost mistake the XML syntax for a real
language.
You gained an appreciation for the simplicity of using annotations to develop Seam components in the last
chapter. If you are content working with annotations, I encourage you to skip ahead to the next chapter which
introduces another set of annotations for wiring together components and binding values to context variables.
On the other hand, if you are interested in exploring how to define and configure components in XML, this
chapter teaches you how to use the Seam component descriptor for that purpose. The component descriptor is
external metadata and thus offers a way to keep the definition of the component separate from the class
definition. External metadata has another benefit. It allows you to set initial property values of component
instances, wire components together statically, adjust the settings of built-in Seam components, and control
built-in Seam functionality. By the end of the chapter, you will appreciate that XML is an important
supplement to any Seam diet.
5.1 Defining components using XML
When an audience is presented with a new XML configuration, two questions often come up: "Why would I
want to use XML?" and "Do I have to use it?" After the debacle that was EJB 2, a major theme of the EJB 3
rework was to do away with required XML descriptors. Well, you don't have much to worry about in the case
of Seam. You saw in the last chapter how you can author Seam components without having to touch an XML
descriptor. So, the answer to the second question is, "No, XML is not required." The component descriptor is
entirely optional. Seam will bootstrap properly in its absence.
But there are just some situations that annotations alone cannot handle. The component descriptor allows
you to perform the following tasks:
z
define an unmodifiable class as a Seam component that does not have a @Name annotation (the class
may still have other annotations that contribute to its component definition)
Licensed to Jaroslaw Gilewski <[email protected]>
z
install a class that is not installed by default (the class has an @Install annotation indicating that the
component should not be installed)
z
override a component definition setting, such as the scope or auto-create value, the value from the
descriptor always taking precedence over the annotation
z
configure bean properties of components to establish the initial state of the component or to externalize
deployment-specific information for a component
There is the possibility that you simply prefer to use XML over annotations as a general principle and you
want to use the component descriptor to define all of your Seam components. Seam affords you that flexibility.
Either way, Seam has a wealth of built-in functionality that is only an XML element away. Likewise, you can
use the component descriptor to dress up your own components once they are set in stone (i.e. compiled).
I am going to start with an overview of the component descriptor; what it is, where it lives, and how to use
it to define and configure your own components. Once you have studied its parts, I will show you how to
configure Seam's built-in components.
5.1.1 Choosing your descriptor strategy
Seam's XML-based configuration uses a distributed design. The component descriptor is actually a general
term for the combined sum of the configurations from all of the component descriptors on the classpath. Recall
that descriptor is a fancy term for XML file.
There are both general descriptors and fine-grained descriptors. The general descriptor can hold an
arbitrary number of component definitions, whereas the fine-grained descriptor is designed to govern the
components for a single class. The name of the general descriptor is components.xml, whereas fine-grained
descriptors are named using the file extension .component.xml.
The general descriptor is often placed in the WEB-INF directory of the web application, which is where
seam-gen stashes it. However, this file need not be confined to the WEB-INF directory. Seam supports
partitioning of this file into multiple instances on the classpath. This distributed design allows you to split up
your configuration in the way that is most suitable for you, rather than jamming every last component
definition into a single XML file. You can also go to the other extreme, where every component is defined in a
fine-grained descriptor matching the subject class. The choice is up to you.
NOTE
As you increase the number of component descriptors, you incur an increased container startup cost
because more XML files must be parsed. However, the number of descriptors does not affect the runtime
performance since the component definitions are only processed during container initialization (and hot
redeployment).
The rules regarding where the component descriptors can be placed are fairly loose. The locations that the
Seam component scanner visits are summarized in table 5.1, listed in the order that they are addressed by the
scanner. Regardless of how you decide to divide up your XML-based configuration, Seam will collect them,
combine them with the settings defined in annotations, and assemble a unified set of component definitions in
memory, as explained in the previous chapter.
Table 5.1 The resource locations where Seam will look for XML-based component descriptors
Resource
Location details
WEB-INF/components.xml
Located in a web application archive (WAR)
META-INF/components.xml
Located in any classpath entry (root of a JAR or WEB-INF/classes in a WAR)
components.xml or
*.component.xml
Located in any directory of a classpath entry that is scanned by the Seam component scanner (JAR
files that have a seam.properties marker file)
Licensed to Jaroslaw Gilewski <[email protected]>
TIP
The components.xml descriptor is not web-specific. Just because seam-gen puts this file in the WEB-INF
directory does not mean that it has to go there. I recommend that you store it in the META-INF directory at
the root of the application's classpath instead. That way, you can load the component definitions in a unit or
integration test environment without having to copy the descriptor to a special test build path.
That sums up where the component descriptor can be placed. Let's open up the file, have a look at its
structure, and learn how to use it to create component definitions.
5.1.2 The structure of the component descriptor
If you have worked with the Spring configuration file, the contents of the Seam component descriptor should
look surprisingly similar. The main difference is that instead of having a root <beans> element and child
<bean> elements, the components.xml file has a root <components> element and child <component>
elements. (In the case of the fine-grained configuration file, <component> is the root element). Seam's
descriptor also supports extension elements through the use of XML namespaces, just like Spring. Pretty
similar, wouldn't you say?
Listing 5.1 shows a basic component descriptor with two components defined. For the components shown,
the @Name and @Scope annotations have been removed from the respective class definitions. These classes
are declared as components using XML instead. If the annotations remain on the class, an exception will result
for reasons that are described in section 5.4 on component definitions versus component configuration.
Listing 5.1 The equivalent XML component definitions for the classes used in the registration example
<components xmlns="http://jboss.com/products/seam/components"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://jboss.com/products/seam/components
http://jboss.com/products/seam/components-2.0.xsd">
<component name="golferEntity"
class="org.open18.model.Golfer" scope="event"/>
<component name="passwordBean"
class="org.open18.auth.PasswordBean" scope="event"
auto-create="true"/>
</components>
If Spring hasn't made you sick of XML yet, then these definitions don't look so bad. The <component>
element defines a new Seam component for the class specified in the class attribute. The name attribute is
equivalent to the @Name annotation and the scope attribute is equivalent to the @Scope annotation. The
mapping of component definition annotations to the XML <component> element attributes are shown in
table 5.2.
Table 5.2 The mapping between the component definition annotations and attributes on <component>
Class-level annotation or annotation attribute
XML attribute on <component>
Java class name
class
@Name
name
@Scope
scope
Licensed to Jaroslaw Gilewski <[email protected]>
@Autocreate
auto-create
@Install(value)
installed
@Install(precedence)
precedence
@Startup
startup
@JndiName
a
jndi-namea
a. The @JndiName annotation and jndi-name attribute are only relevant for EJB session bean components
You may notice that the XML equivalent of the @Role annotation is missing from this list. Actually, it's
not. It is the <component> element. Since the component descriptor supports an arbitrary number of
component definitions, its possible to map the same class to multiple context variables. The only requirement
is that you assign a different context variable to each definition using the name attribute. In effect, the name
attribute represents the role name. A majority of the time, you will find yourself using the <component>
declaration to define roles rather than the @Role annotation, almost without being conscience of it.
NOTE
If you want to define multiple roles for a component in a fine-grained component descriptor, which is
covered in the next section, you have to use <components> as the root element so that the descriptor can
accommodate multiple declarations.
Here, the multiple roles that were established for the Golfer entity from the previous chapter are defined
using the <component> element rather than the @Roles annotation:
<component name="newGolfer"
class="org.open18.model.Golfer" scope="event"/>
<component name="golferExample"
class="org.open18.model.Golfer" scope="event"/>
From the standpoint of the application, there is no difference in how the component is constructed when it
is defined using annotations versus XML. Seam still builds the same internal representation of a component
definition and uses it to dish out component instances. The most significant enhancement that you get by using
XML is being able to assign initial values to bean properties. Component configuration will be covered in
section 5.3. Right now the component definitions appear terse because they are missing these property value
configurations.
WARNING
You cannot use XML-based component definitions for classes that reside in the src/action folder for WAR
projects created by seam-gen. The main component scanner that processes the components.xml descriptors
cannot "see" classes in the hot redeployable classloader. This shortcoming may be resolved in a future
release. Regardless, it defeats the purpose of using the hot redeployable classloader in the first place since
components defined in components.xml are not hot redeployable. Hot redeployable components can still
take advantage of configuration properties, which you will learn about in section 5.3.
While the component descriptor may seem simple enough with a single component definition, if you are
going to use XML to define all of your components, its size can quickly get out of hand. One recommendation
is to partition your component descriptors by module so that each descriptor only configures the classes within
a single artifact. You can narrow it even further by putting a general descriptor in each Java package. However,
you can take it a step further and use a fine-grained descriptor rather than the general descriptor. If you do, the
syntax and semantics change slightly.
Licensed to Jaroslaw Gilewski <[email protected]>
5.1.3 Fine-grained component descriptors
The trouble with component descriptors is that they tend to grow without bounds and quickly become a
melting pot of configurations. Narrowing in on a single configuration becomes as challenging as finding a
matching sock in a laundry pile. Fine-grained descriptors offer a solution to monolithic component descriptors
by segmenting the declarations by component class. The fine-grained descriptor is designed to make it more
intuitive for the developer to locate the configuration for a class and for those configurations to remain focused
on that class.
Fine-grained descriptors are identified by the .component.xml file extension and are used to configure a
neighboring Java or Groovy class as a Seam component. The name of the class to which the fine-grained
descriptor corresponds is derived by stripping the .component.xml extension from the descriptor's resource
path and converting slashes (/) to dots (.). For example, the fine-grained component descriptor whose resource
path
is
org/open18/auth/PasswordBean.component.xml
is
used
to
configure
the
org.open18.auth.PasswordBean class. The reverse logic is used to derive the resource path of the finegrained descriptor from the name of a class. Seam searches in both directions when preparing the component
definition.
You have seen this dispersed approach to XML-based configuration in chapter 3 which covers finegrained page descriptors. The only difference, in this case, is that the fine-grained component descriptor can
accommodate one or many component definitions. If you only intend on declaring a single component
definition, you use <component> as the root element. A fine-grained descriptor with a single component
definition need not declare the class attribute on that element, as the class name is derived from the logic
detailed above. Although the root element is <component> instead of <components>, it is still possible,
though not necessary, to include the XML schema declarations to get XML tag completion in the editor, as
shown here:
<component xmlns="http://jboss.com/products/seam/components"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://jboss.com/products/seam/components
http://jboss.com/products/seam/components-2.0.xsd"
name="passwordBean" scope="event"/>
If you intend on using multiple declarations in the fine-grained descriptor, you use <components> as the
root element. However, by doing so, you lose the benefit of the implied value of the class attribute, which is
once again required. What you do gain is the ability to use the full capacity of the component descriptor to
configure the class, including elements like <factory> and <event> which you learn about in the next
chapter.
NOTE
There is nothing stopping you from putting arbitrary component definitions—definitions not related to the
neighboring class—in a fine-grained descriptor that uses <components> as the root element. However, it
is generally discouraged to do so since it defeats the purpose of using the fine-grained descriptor in the first
place.
The fine-grained descriptor offers a nice alternative to using the Seam component annotations in the class
files, especially if you prefer to keep your code separate from your configuration—or shiver at the thought of
using so many annotations on your class. What's nice about the fine-grained descriptor is that it is easy to
locate, being adjacent to the component class it is used to configure, and the content of that descriptor is "taskoriented" since it revolves around a single class.
Licensed to Jaroslaw Gilewski <jgilewski[email protected]>
The downside to the fine-grained descriptor is that it is yet another XML file that you have to manage.
Piling XML schema declarations on top only adds to the noise. You also loose the type-safety that annotations
afford you. Fortunately, there is a compromise. Seam offers "type-safe" XML elements that leverage XML
namespaces, thus reducing the pain involved in working with XML.
5.2 XML namespaces in the component descriptor
You can hardly ignore the XML namespace declarations at the top of the component descriptor, first presented
in listing 5.1. This gratuitous metadata is used to import collections of XML elements and attributes defined in
XML Schema documents for creating component definitions.
5.2.1 The purpose of XML namespace declarations
The root tag that you see in listing 5.1 includes the base set of namespace declarations that you see in every
component descriptor. These declarations set up the descriptor to use the XML Schema format and import the
generic component vocabulary. This chapter focuses on the <component> element and makes mention of the
<import> element. There are two other elements in the generic component vocabulary, <factory> and
<event>, both of which will be covered in the next chapter.
With the base set of namespace declarations in place, you aren't seeing much benefit from using the XML
Schema over a simpler alternative like DTD. The benefit comes in the extensive set of component-specific
namespaces that Seam provides.
Each namespace that you import provides an XML vocabulary—attribute and element names—that maps
one-to-one with the Java classes and class properties from the Seam API. Because of this implied mapping, the
namespaces are referred to specifically as component XML namespaces.
As an example, the vocabulary associated with the http://jboss.com/products/seam/core
namespace includes a <core:init> element the represents the org.jboss.seam.core.Init class and a
set of attributes that correspond to its properties. One such property is debug, which is used to enable Seam
debug mode. By importing this namespace into the component descriptor, it is possible to use the
<core:init> element to set the debug property to true, as shown here:
<components xmlns="http://jboss.com/products/seam/components"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://jboss.com/products/seam/components
http://jboss.com/products/seam/components-2.0.xsd
http://jboss.com/products/seam/core
http://jboss.com/products/seam/core-2.0.xsd">
<core:init debug="true"/>
</components>
All built-in Seam components can be configured using custom namespaces, which will be covered in more
depth in section 5.5.
Although the namespace declarations themselves are quite verbose, they cut down on the number of
characters needed throughout the rest of the document. Instead of defining every component using the generic
<component> element, you can use elements that have recognizable names and attributes that pertain to the
properties of the component you are trying to define or configure.
NOTE
If you are not accustomed to seeing XML schema-based configuration, you may be turned off by its
verbosity. Although it makes the top of the XML document quite cluttered, it makes up for it by enabling
Licensed to Jaroslaw Gilewski <[email protected]>
the IDE to support XML tag completion, giving you a friendly development experience. That is the main
reason why Seam leverages XML schema-based namespaces in all its XML descriptors.
An XML namespace is a Uniform Resource Identifier (URI), which is a fancy way of saying a unique
name. A namespace looks like a URL, but it is not mandatory that it resolve to a public document. The
namespace is mapped to an alias, such as core in the example above. The name of the alias is arbitrary. It is
used as a prefix on element names, such as <core:init>, to associate those elements with a particular
namespace. The prefix is not required for elements in the default namespace, as set by the xmlns attribute.
Typically, component descriptors declare http://jboss.com/products/seam/components as the
default namespace, which represents the generic component vocabulary. As such, the <component> element
does not need a prefix.
Namespaces are similar to Java packages. In fact, Seam enforces a one-to-one relationship between
namespaces and Java packages in the component descriptor. You will soon learn that the elements are a
transformation of the class name and the attributes the class properties. The use of XML namespaces in the
component descriptor is the closest you can get to writing Java without actually using the Java syntax.
Let's implement our own XML namespace for the Open 18 application to avoid the use of the generic
<component> element and save ourselves some keystrokes. This lesson will be vital to understanding how
elements in a component XML namespace are interpreted and will help you to better understand how to read
the configurations of Seam's built-in components.
5.2.2 Defining an XML namespace for components in a package
XML namespaces used in the component descriptor are defined by the @Namespace annotation, summarized
in table 5.3. @Namespace is a package-level annotation, which means it is placed above the package
declaration in the package-info.java file. The package-info.java file was introduced in Java 5 and is used
strictly for package-level annotations and comments. When a namespace is established for a Java package,
Seam associates all of the classes defined in that package with that namespace.
Table 5.3 The @Namespace annotation is used to create an XML namespace for components
Name:
Namespace
Purpose:
Maps a custom URI to a Java package. The URI can be referenced as an XML namespace in the component descriptor
for defining and configuring components in the associated Java package.
Target:
PACKAGE
Attribute
Type
Function
value
String
A URI that serves as the XML namespace prefix.
prefix
String
Used to qualify a context variable name, similar to how a Java package qualifies a class
name. The local name of the XML element is appended to this prefix to derive the context
variable name. If this value is not specified or blank, the prefix is not used.
Let's create a namespace for the authentication package in the Open 18 application. The contents of the
org.open18.auth.package-info resource (the package-info.java file in the org.open18.auth
package) is shown below:
@Namespace(value="http://open18.org/components/auth", prefix="")
@AutoCreate
package org.open18.auth;
import org.jboss.seam.annotations.AutoCreate;
import org.jboss.seam.annotations.Namespace;
Licensed to Jaroslaw Gilewski <[email protected]>
Notice that in addition to the @Namespace declaration, other Seam component annotations can be added
to the package-info.java file to set defaults for any component in that package. In this case, all components in
the org.open18.auth package support the auto create functionality.
NOTE
The component scanner does not pick up namespace declarations located in the src/action directory of
seam-gen WAR projects. Instead, you must use the src/model directory.
You find similar declarations scattered throughout the Seam code-base, enabling the use of component
XML namespaces for configuring built-in Seam components. Seam's built-in namespaces are covered in depth
in section 5.4. Having created a component namespace, let's see how it enables the use of component-specific
markup in the component descriptor.
5.2.3 How XML namespaces are interpreted
The @Namespace declaration establishes a link between a Java package and the URI that identifies an XML
namespace. This bridge is the key to extensible XML authoring of components using the component
descriptor. In other words, you can define your components using custom XML elements just like with Seam's
built-in components.
Start by adding a namespace declaration to the component descriptor, using the URI defined in the
@Namespace annotation and assigning a namespace prefix for referencing the namespace in XML elements.
Listing 5.2 makes use of the http://open18.org/components/auth namespace by binding it to the
auth prefix. Recall that the name of the prefix is arbitrary.
Listing 5.2 The PasswordBean defined using a component namespace
<components xmlns="http://jboss.com/products/seam/components"
xmlns:auth="http://open18.org/components/auth"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://jboss.com/products/seam/components
http://jboss.com/products/seam/components-2.0.xsd">
<auth:password-bean scope="event"/>
</components>
There is one component definition in this descriptor, <auth:password-bean>, which is bound to the
auth namespace prefix. The <auth:password-bean> is a short-hand syntax for declaring the
PasswordBean class as a component. It is equivalent to the following generic component definition:
<component name="passwordBean"
class="org.open18.auth.PasswordBean" scope="event"/>
Let's explore how Seam interprets the short-hand syntax to derive the same set of information provided by
the generic component definition.
The translation from the <auth:password-bean> element to a fully qualified Java class is shown in
figure 5.1.
Licensed to Jaroslaw Gilewski <[email protected]>
Figure 5.1 Shows how Seam interprets an namespace component element. The URI of the prefix is matched against a @Namespace
declaration to determine the package, and the element name is translated into the class name.
When Seam encounters an XML element that uses the auth namespace prefix, it looks to see if there is a
@Namespace annotation with a value that matches the XML namespace URI. Indeed, the namespace maps to
a Java package as follows:
http://open18.org/components/auth -> org.open18.auth
At this point, the namespace URI has served its purpose and Seam ceases to do anything more with it. It
just puts Seam in the right playing field. Next, the simple name of the class—the class name without the Java
package—is derived from the local name of the XML element, in this case password-bean. This conversion
occurs by making the first letter, and any letter after a hyphen, uppercase and then dropping the hyphens:
password-bean -> PasswordBean
The package org.open18.auth and the simple class name PasswordBean are assembled to form the
fully qualified Java class. The complete translation is as follows:
auth:password-bean -> org.open18.auth.PasswordBean
NOTE
The benefit of using a custom XML element is that it eliminates the need to specify the class attribute on
the component definition. Instead, the class is derived from the local name of the XML element and the
information in the @Namespace annotation. If the XML namespace does not resolve to a component
namespace, an exception is thrown during deployment that prevents the application from loading.
That takes care of the component class, but as you have learned, every Seam component must be
associated with a context variable name and scope (if not provided, a scope is chosen automatically according
to table 4.5 in chapter 4).
There are three ways to specify a context variable name for a custom element in a component XML
namespace:
z
the name attribute on the custom XML element
z
the @Name annotation on the derived class
z
derived from the custom XML element name
Custom elements in a component namespace are treated as extensions to the <component> element. That
means they inherit all of the standard attributes used to define a component that were listed in table 5.2.
Because the standard attributes are inherited, the context variable name and scope can be specified as follows:
<auth:password-bean name="passwordBean" scope="event"/>
Licensed to Jaroslaw Gilewski <[email protected]>
However, declaring the context variable name on a custom element is not necessary. If the name attribute
is not provided, one is implied. Seam first checks to see if the class has a @Name annotation. If so, the context
variable name specified in the annotation is used. No more processing is done. The process is much more
involved if the class does not have a @Name annotation.
If the name attribute is not supplied and the class does not have a @Name annotation, the context variable
is derived in the same way that the Java class name is derived. Seam begins by lowercasing the first letter of
the simple name of the class:
PasswordBean -> passwordBean
This value is the unqualified context variable name. The unqualified context variable name is the same as
the fully-qualified context variable name if the prefix attribute on the @Namespace annotation is empty. In
this example, the prefix attribute is empty, so the context variable name remains passwordBean.
The prefix attribute on the @Namespace annotation is the means by which a context variable prefix is
specified. A context variable prefix serves the same purpose as a Java package. If the prefix attribute is nonempty, the fully-qualified context variable is constructed by combining the value of the prefix attribute with
the unqualified context variable name, separated by the dot (.) character. Assume for a moment that the
prefix attribute had been defined using a non-empty value:
@Namespace(value="http://open18.org/components/auth",
prefix="org.open18.auth")
In this case, the context variable name for the <auth:password-bean> declaration would resolve to
org.open18.auth.passwordBean. (Do not confuse the fully-qualified context variable name with the
fully-qualified class name).
Enabling IDE tag completion
Declaring the auth namespace prefix in your component descriptor is not enough to get XML tag completion
in the IDE. You still need to provide an XML schema file for each of the components that you want to declare.
The XML schema vocabulary for this namespace, auth-1.0.xsd, is not shown here, but is available in the book
source code for this chapter. Once you have authored that file, you add it to the xsi:schemaLocation
attribute
in
the
component
descriptor,
which
maps
the
namespace,
http://open18.org/components/auth, to the XSD schema file:
<components xmlns="http://jboss.com/products/seam/components"
xmlns:auth="http://open18.org/components/auth"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://open18.org/components/auth
http://open18.org/components/auth-1.0.xsd
http://jboss.com/products/seam/components
http://jboss.com/products/seam/components-2.0.xsd">
...
</components>
You then correlate the auth-1.0.xsd with the http://open18.org/components/auth namespace to
get both XML tag completion and validation in the XML editor.
Licensed to Jaroslaw Gilewski <[email protected]>
Creating component XML namespaces may appear challenging. Fortunately, you are more often the
consumer of a namespace than a creator. They are the means by which you configure all built-in components,
so you are going to see them often.
In the previous example, you were introduced to the concept of a fully-qualified context variable. While
useful for avoiding naming conflicts, the long context variable names can be cumbersome to type. It is possible
to import context variable prefixes just like you import Java packages so that you can use the unqualified
names in your application.
5.2.4 Importing context variable prefix
Just like packages are used in Java to avoid naming conflicts between classes, prefixes are used to avoid
naming conflicts between context variables. A context variable prefix even uses the same dot (.) notation that
you are familiar with from Java packages.
Having to type fully-qualified context variable names all the time would be a drag. To remedy this pain,
Seam offers a way to import a set of qualified context variable names for convenience of use, just like the
import statement in Java. The difference, of course, is that there is no static checking in the case of context
variables.
A context variable prefix is imported using the <import> element in the component descriptor so that
you can reference the context variable according to its last segment—it's unqualified name. Assuming for a
moment that we define the org.open18.auth prefix on the component namespace mapped to the auth
XML namespace, the context variable prefix could be imported using the following descriptor contents:
<components xmlns="http://jboss.com/products/seam/components"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
...
xsi:schemaLocation="
...
http://jboss.com/products/seam/components
http://jboss.com/products/seam/components-2.0.xsd">
<import>org.open18.auth</import>
</components>
This import statement applies globally across the application. It allows you to reference the context
variable
for
the
PasswordBean
component
as
passwordBean
rather
than
org.open18.auth.passwordBean.
It's not very likely that you will use fully-qualified context variable names in your own application, unless
you are creating reusable, shared libraries. However, you are very likely to encounter this naming convention
when using third-party libraries. In fact, all built-in Seam components use qualified context variable names to
be polite and avoid "stealing" context variable names you might like to use. But since many of the core Seam
components are so commonly used, many of them are imported automatically for you. The following is the list
context variable prefixes that Seam imports (at the time of writing):
z
org.jboss.seam.core
z
org.jboss.seam.transaction
z
org.jboss.seam.framework
z
org.jboss.seam.web
z
org.jboss.seam.faces
z
org.jboss.seam.international
Licensed to Jaroslaw Gilewski <[email protected]>
z
org.jboss.seam.theme
z
org.jboss.seam.pageflow
z
org.jboss.seam.bpm
z
org.jboss.seam.jms
z
org.jboss.seam.mail
z
org.jboss.seam.security
z
org.jboss.seam.captcha
This set of default imports are especially convenient for Seam components that you use in the UI or, as
you will learn in the next chapter, for dynamic injection. One common component to use in the UI is the
FacesMessages class, bound to the org.jboss.seam.faces.facesMessages context variable. You
can see that the namespace used by the context variable appears in the default list of imports, so you can
instead reference it using the abbreviated context variable name facesMessages. You may use this
component, for example, to iterate over the global messages only, without having to use <h:messages> with
the globalOnly flag:
<rich:dataList var="msg"
value="#{facesMessages.currentGlobalMessages}">
#{msg.summary}
</rich:dataLlist>
Context variables give you access to Seam components, as you see in this example. But they also provide
a means of configuring initial property values for a component, which are applied to an instance of the
component after it is created. In the next section, you will take the component definition a step further so that
the component instance is not only created by the Seam container, its state is also initialized.
5.3 Configuring component properties
So far, you have learned how a class enters into "component-hood" by way of annotations or an XML-based
declaration. But alone, these definitions are just a fancy way of creating a new instance of the class. In many
cases, a component is only useful once its properties are initialized—meaning they are assigned initial values.
This initialization comes in the form of simple property values, such as connection properties, or as references
to other components, effectively wiring those components together. If you have used the Spring configuration
file, this routine should be natural. Component configuration allows you to establish the initial state of the
component after it is instantiated by the Seam container, yet before it is bound to the context variable and put
to work.
NOTE
The @Create and @PostConstruct life cycle callback methods benefit from these initial property
values in that they are executed after the property values have been assigned to the component instance.
In this section, you are going to learn how to initialize a Seam component by adding initial property values
to the component definition that are applied to the properties of the component instance when it is created.
5.3.1 Component definitions as object prototypes
Where you really see the value of a component definition is in its ability to act as an object prototype. As part
of the component definition, you can store a set of property names and associated values. The properties are
Licensed to Jaroslaw Gilewski <[email protected]>
read by the Seam component scanner at startup. They are then transferred to the corresponding properties of
the class after the component instance is created to establish its initial state.
The property names are mapped to either JavaBean-style "setter" methods or field names on the target
object, herein referred to as the bean property. The property value is then injected into the method or field
using reflection. This mechanism is known as dependency injection (DI). When the dependency being injected
is a reference to another component, the mechanism is referred to as "component wiring" (to steal from the
comparable Spring term, "bean wiring").
NOTE
The access level of the method or field on the target object does not matter. Seam can assign a value to a
method or field of any access level, even if it is private—a privilege that is granted to reflection. If a
property name matches both a setter method and a field name, the setter method takes precedence.
Unlike other parts of the component definition, component properties must be defined in external
configuration, rather than in annotations. 28 Although Seam tries to avoid external configuration, namely XML,
as much as possible, configurable properties is one case where it makes sense to take advantage of the
decoupling. Declaring a property value outside of the Java source code allows you to achieve any of the
following:
z
adjust the runtime behavior of the application without having to recompile (e.g. change a timeout
period, enable debugging, or set the maximum number of results retrieved by an entity query)
z
define different property values for different component roles (i.e. same class, different context
variable name)
z
declare references to other component instances, known as component wiring
It's really up to you to decide when and where to use component properties in your application. The next
choice to make is where to define that initial property value.
5.3.2 Where component properties are defined
An initial property value can be added to a component definition by declaring it in one of three places, listed in
the order most likely selected:
z
component descriptor
z
seam.properties
z
servlet context parameter
Chances are, you will use the component descriptor a vast majority of the time, given that it is the most
flexible and convenient. Earlier you learned to use the component descriptor to define Seam components either
with a generic <component> element or a non-generic element bound to a component XML namespace. You
can use the same elements to assign property metadata, either to augment the component definition or to
configure an existing component. Section 5.4 clarifies this distinction.
As an alternative to using the component descriptor, you can configure component properties using the
standard Java properties syntax, herein referred to as external property settings. External property settings can
be defined in either the seam.properties file or in servlet context parameters. Seam employs a special naming
convention to determine how the key of the property setting is mapped to the bean property of a Seam
component.
The remainder of this section demonstrates how to configure components using these two methods by
drawing on example components from the Open 18 application. Given that you already have the component
descriptor open, the XML-based component configuration will be covered first.
28
There is one exception to this rule. The @Logger annotation is treated like a property initializer. It instructs Seam to inject a log
instance configured for the class when the component is instantiated.
Licensed to Jaroslaw Gilewski <[email protected]>
Defining properties in the component descriptor
Properties can be associated with any component definition declared in the component descriptor. If you use
the generic <component> element, then properties are configured using nested <property> elements. The
name of the property being configured is specified in the name attribute of the <property> element and the
value to be assigned is specified in the body of the element.
To associate the configuration properties with an existing component definition, you specify the
component's context variable name in the name attribute of the <component> element. If you want the
<component> declaration to also serve as a component definition, then you must also supply the class
attribute. To learn more about differentiating between the two, see section 5.4.
Let's assume that we want to configure the hashing algorithm and character set used in the
PasswordManager component, shown in listing 5.3. The digestAlgorithm property determines the type
of hash that will be calculated from the plain-text password and the charset property determines the
encoding scheme that will be applied to the password prior to hashing it. If this component were to be used
without configuring it, a NullPointerException would be thrown when the hash(String) method is
called. Its state needs to be initialized first.
Listing 5.3 A configurable component that is used to hash plain-text passwords
package org.open18.auth;
import java.security.MessageDigest;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.util.Hex;
@Name("passwordManager")
public class PasswordManager {
private String digestAlgorithm;
private String charset;
public void setDigestAlgorithm(String algorithm) {
this.digestAlgorithm = algorithm;
}
public void setCharset(String charset) {
this.charset = charset;
}
public String hash(String plainTextPassword) {
try {
MessageDigest digest =
MessageDigest.getInstance(digestAlgorithm);
digest.update(plainTextPassword.getBytes(charset));
byte[] rawHash = digest.digest();
return new String(Hex.encodeHex(rawHash));
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
}
Licensed to Jaroslaw Gilewski <[email protected]>
The following declaration configures the digestAlgorithm and charset values for a component
bound to the passwordManager context variable using nested <property> elements:
<component name="passwordManager">
<property name="digestAlgorithm">SHA-1</property>
<property name="charset">UTF-8</property>
</component>
This declaration assumes that there is a component bound to the passwordManager context variable.
Otherwise, the configuration serves no purpose. When the passwordManager context variable is requested,
Seam will instantiate a new instance of the corresponding class and then apply the property values using
reflection. To spell it out for you, this process is equivalent to the following Java code:
PasswordManager passwordManager = new PasswordManager();
passwordManager.setDigestAlgorithm("SHA-1");
passwordManager.setCharset("UTF-8");
Contexts.getEventContext().set("passwordManager", passwordManager);
Of course, this is a generalization of what really happens when a component is instantiated since there are
all kinds of interceptors that are wrapped around it and life cycle methods are executed, if they exist. I am just
giving you a general picture of what Seam is doing to set the property values.
To make for a more concise declaration, properties can be defined as attributes on the <component>
element. The component configuration for PasswordManager has been modified to take advantage of this
shorthand:
<component name="passwordManager"
digestAlgorithm="SHA-1" charset="UTF-8"/>
However, the attribute syntax has a huge disclaimer. You cannot use an attribute to configure a property
whose name is a reserved word in the XML vocabulary of the <component> element. The reserved words,
listed in table 5.4, are interpreted as part of a component definition declared using XML.
Table 5.4 Reserved attribute names on the <component> element
name
scope
startup
class
jndi-name
precedence
auto-create
xmlns
Memorize this list or keep it close at hand. Otherwise, you will become very confused as to why you get
an error (or a silent failure) trying to assign a value using an attribute of the <component> element to a bean
property that matches one of these names. If you do happen to have a property whose name matches one of
these words, you will need to use a nested <property> or nested namespace element to assign a value to it.
Your final option is to use a custom property element whose name is equivalent to the property that you
are configuring. Once again, the component configuration for PasswordManager has been rewritten to
reflect this syntax:
<component name="passwordManager">
<digestAlgorithm>SHA-1</digestAlgorithm>
<charset>UTF-8</charset>
</component>
Licensed to Jaroslaw Gilewski <[email protected]>
If you have been following along with your XML editor, then you already know that the XML schema
validator is not happy with these last two recommendations—declaring the property value using an attribute or
nested element to represent the name of the property. That is because the generic component vocabulary does
not declare any attributes or elements with the names digestAlgorithm or charset.
So what good is this syntax if it doesn't validate? As you learned, a custom element in a component XML
namespace extends from the generic <component> element. All you need to do is educate the XML
document with these additional attribute names and nested element names by adding them to the XSD schema
associated with the namespace. As long as the XML schema imported with the namespace includes attribute or
element names that correspond to the names of the component's properties, you can use the shorthand syntax
for configuring your components instead of using nested <property> elements.
Therefore, I do not recommend that you use the shorthand syntax unless used in conjunction with a
component XML namespace and supported by the associated XSD schema. All of the built-in Seam
components already have this setup, which is why you can define properties on those components using
custom attribute and element names.
Getting the schema validation for your own components requires that you create an XSD schema file for
your component XML namespace, as covered in section 5.2.3. Otherwise, the XML editor will complain
loudly that the attribute or element name used to configure the property value is invalid. You don't have to go
through the steps of creating an XSD schema file, though, since Seam does not enforce schema validation at
runtime. Use of XSD is merely a best practice to get the type-safety when developing.
Assuming that you have added the appropriate vocabulary, then the configuration for the
PasswordManager component can take advantage of the shorthand syntax, as shown in listing 5.4, and still
validate.
Listing 5.4 The component configuration for PasswordManager using a component XML namespace
<components xmlns="http://jboss.com/products/seam/components"
xmlns:auth="http://open18.org/components/auth"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://jboss.com/products/seam/components
http://jboss.com/products/seam/components-2.0.xsd
http://open18.org/components/auth
http://open18.org/components/auth-1.0.xsd">
<auth:password-manager>
<auth:digest-algorithm>SHA-1</auth:digest-algorithm>
<auth:charset>UTF-8</auth:charset>
</auth:password-manager>
</components>
I threw a curve-ball at you in that last excerpt. Can you tell what it is? If you look closely, I changed the
name of the digestAlgorithm element to digest-algorithm. Seam always converts hyphenated
element names, attribute names, and the value of the name attribute on the <property> element to their
camel-case equivalents. This conversion is consistent with what is done to derive the simple name of the class
from the element name, as you learned in 5.2.3.
NOTE
The XML namespaces on property elements, which appear in listing 5.4, do not play a role in the
interpretation of the assignment, as is done with the top-level element described in section 5.2.4. You could
Licensed to Jaroslaw Gilewski <[email protected]>
have easily named the property element <digest-algorithm> in this case, and aside from whining
from the XML editor, the assignment would work exactly the same way.
Going back to the generic syntax, you could have written the <property> element for the
digestAlgorithm property using the hyphenated form:
<property name="digest-algorithm">SHA-1</property>
or, if the namespace vocabulary supports it, written the attribute name as:
<auth:password-manager digest-algorithm="SHA-1"/>
To summarize, you can configure a component property using a nested <property> element, the
attribute with the same name as the property on the component element, or as a nested element with the same
name as the property. The important question to ask is, does it validate? In all cases, the nested <property>
element validates. The property name can be supplied using either the literal property name or its hyphenated
form. Beyond that, the component XML namespace vocabulary dictates the attributes and nested elements you
can use to configure the component's properties. Seam's built-in components mostly use attribute names in
hyphenated form for configuring component properties. See section 5.5 for several examples. If you are
defining your own XML schema, you may choose to adopt this style as your standard.
That covers the variety of syntaxes you can use to define component properties in XML. Let's move on to
property configuration using external property settings.
External property settings
The component descriptor allows you to both declare a component and configure a component's properties.
External property settings only allow you to configure the properties of existing Seam components. That
means either the class must have a @Name annotation (and be installed) or it must be declared as a component
in the component descriptor.
To define the property of a component using an external property, the property key is constructed by
joining the context variable name of the component with the name of the bean property on the component,
separated by a dot (.) character. The value associated with that key is used as the value to assign to the bean
property.
Let's return to our PasswordManager component. For the purpose of this example, let's assume that the
PasswordManager has been defined as a Seam component, but has not yet had property values set. To set
the digestAlgorithm and charset properties of the PasswordManager component using external
property settings, you add the following two lines to a seam.properties file at the root of a classpath entry or in
the META-INF directory:
passwordManager.digestAlgorithm=SHA-1
passwordManager.charset=UTF-8
When you define the property settings in the seam.properties file, you specify the value in the standard
java.util.Properties syntax, separating the name of the configuration property from the value with
either an equals sign (=) or a space. For more complicated values than what is shown here, see the JavaDoc for
java.util.Properties for a more complete explanation of the standard rules.
Notice that the camel-case naming convention is used to define the property name. Unlike with the
component descriptor, Seam does not perform the conversion from hyphenated names to camel-case names
Licensed to Jaroslaw Gilewski <[email protected]>
when processing component properties defined using external property settings, so you will need to use the
formal name of the property.
Instead of registering initial property values in a seam.properties file, you can set them up as servlet
context parameters in the web application's web.xml file. Note that these parameters are not defined within the
JSF servlet definition, but rather as context-wide initialization parameters, using top-level <contextparam> elements:
<context-param>
<param-name>passwordManager.digestAlgorithm</param-name>
<param-value>SHA-1</param-value>
</context-param>
<context-param>
<param-name>passwordManager.charset</param-name>
<param-value>UTF-8</param-value>
</context-param>
Using servlet context parameters is less flexible since it requires a servlet environment for the properties to
take affect. This limitation can make it difficult to create basic unit tests since it forces you to bootstrap the
servlet environment.
Keep in mind that the component name (the context variable name) is used as the first half of the property
key, not the class name of the component. Some of the names of the built-in Seam components happen to
closely resemble the classes that they represent, so don't confuse the two. In addition, do not confuse the dots
in the context variable name with the dot used to isolate the name of the bean property. The name of the bean
property is always assumed to be the part of the key that follows the final dot character.
Just to get a taste for a more advanced property key, let's consider how you might set the property on one
of Seam's built-in components. Figure 5.2 shows how to disable the transaction management in Seam by
setting the transactionManagementEnabled property to false. Transaction management will be covered
in chapter 9. For now we are just playing around with the setting for demonstration purposes.
Figure 5.2 Shows how Seam interprets an external property setting in order to assign the property value to the property on the target
component, identified by its context variable.
Defining component properties using external property settings works well if you need to externalize
property values, but would prefer to stay away from XML. In fact, if you define a value for a component
property using an external property setting, it will take precedence over the configuration of the same property
in the component descriptor. This override can be useful for adjusting property values for different deployment
environments.
That's the basics of configuring components, but your exposure has been limited to basic string properties.
Seam is also capable of configuring more complex property types. Let's explore these other options.
Licensed to Jaroslaw Gilewski <[email protected]>
5.3.3 Property value types
The values that are assigned to the properties of a component can be any of the following:
z
basic types (strings, numbers, booleans, characters, and class names)
z
EL values (value and method expressions)
z
collections (where each individual value can be a simple type or a value expression)
z
a replacement token (a name surrounded by @ symbols)
Let's start simple.
Basic value types
The basic types are straightforward. A value begins its life as a string when it is read from the configuration
file. Seam then determines the correct type for the value based on the target property's type and converts it
before attempting to perform the assignment.
In the case of the PasswordManager component, there is no need for conversion since the XML
element data is read in as a string and both properties, digestAlgorithm and charset, are string
properties. But Seam can handle most of the common conversations that you would expect to see supported.
Let's consider another example to demonstrate this feature.
Most golf courses have 18 holes (those that don't have 9 holes). Let's use component configuration to set a
sensible default value for the numHoles property on a transient Course instance:
<component name="newCourse" class="org.open18.model.Course">
<property name="numHoles">18</property>
</component>
The property numHoles is a primitive integer. Seam is doing a very basic conversation in this case using
Integer.valueOf(String). You can expect the same conversion done for all other basic types that are
represented by primitive wrapper classes (and thus have the valueOf(String) method). Two additional
types that Seam supports are java.lang.Class and java.lang.Enum types. A string is converted to a
class using Class.forName(String). An Enum is converted using the literal name of the Enum constant.
Let's assume that the facility type has been changed from a string to FacilityType Enum defined as
follows:
public enum FacilityType {
PUBLIC, PRIVATE, SEMI_PRIVATE, RESORT, MILITARY;
}
You could set the default type to PUBLIC on a transient facility instance by declaring the following
component configuration:
<component name="newFacility" class="org.open18.model.Facility">
<property name="type">PUBLIC</property>
</component>
HINT
The only hangup you may have is regarding the boolean type. Seam uses Boolean.valueOf(String
s) to perform the conversion. The value is considered true if it is equal, ignoring case, to the string
"true". All other values are false.
Licensed to Jaroslaw Gilewski <[email protected]>
Component configuration gets interesting when you inject references to other components rather than just
literal values. As it has been hammered into your brain by this point in the book, Seam makes liberal use of the
EL to unify technologies in a Seam application. If the Spring-Seam bridge configured, which is covered in
chapter 15, you can even inject a Spring bean into a Seam component using the EL. Let's consider the case
when the value provided in the property value is an EL value expression.
Expression language values
Rather than inventing yet another XML vocabulary for wiring Java objects together, Seam leverages the EL to
form references between components. To initialize a property to the value of another component instance, you
specify a value expression that references the other component. Using the EL to define property values is a
powerful concept for two reasons:
z
you can leverage existing knowledge of accessing components
z
any value that can be resolved using an EL expression can become a property value
When you declare a property value using a value expression, Seam does not evaluate the expression, but
rather stores it in the component definition in raw form. When the properties of a new component instance are
being initialized, Seam evaluates the value expression, performs any necessary conversion on the resolved
value, and then assigns the value to the property.
Think about the possibilities that this introduces. You can theoretically have a component whose purpose
is to provide a contextual value for a component property at the time the instance is being created. There's that
word contextual again. For example, when the member registration page is brought up, the transient Golfer
instance can be initialized with the current date and time assigned to the dateJoined property:
<component name="newGolfer" class="org.open18.model.Golfer" scope="event">
<property name="dateJoined">#{currentDatetime}</property>
</component>
To set the value of the dateJoined property, you can supply any EL expression that resolves to a
java.util.Date. Saving you a few keystrokes, Seam provides a built-in component, bound to the
currentDatatime context variable, that supplies the current date and time as produced by new
java.sql.Timestamp(). This component is one of several date-related components in the Seam
Application Framework, the others being currentDate and currentTime. You learn about the Seam
Application Framework in chapter 10.
Let's get a little fancier. Many golf facilities only have one golf course that bears the same name as the
facility. Let's set the name of the new course to the facility name when creating a transient Course instance:
<component name="newCourse" class="org.open18.model.Course" scope="event">
<property name="name">#{facilityHome.instance.name}</property>
</component>
There are several important points to be made about properties defined using value expressions:
z
the component instance never sees the value expression, only the resolved value
z
the value expression is resolved when the component instance is created
z
the component instance will not be notified if the underlying value of the value expression changes
after the component instance is created
If you have used the JSF managed-bean facility, you should recognize that this is exactly how JSF deals
with value expression injections. While the component configuration stores the expression, only the resolved
value is passed on to the property.
Licensed to Jaroslaw Gilewski <[email protected]>
There are two exceptions to the rule of how expression language values are handled. If the target
property's type is a ValueExpression or a MethodExpression, Seam will not evaluate the expression,
even when the properties of the component instance of being initialized. Instead, the expression string is
converted to an expression object (ValueExpression or MethodExpression) and assigned to the
property. The expression can then be evaluated at a later time by the application logic. You can see an example
of this scenario in the built-in Seam component org.jboss.seam.security.Identity. The
authenticateMethod property is of type MethodExpression. The method expression is evaluated in
the application logic to determine if the user's credentials are valid after the login form is submitted. Assuming
you have defined a Seam component named authenticator to handle authentication, you would wire the
authenticate method into the built-in component using the following declaration:
<security:identity
authenticate-method="#{authenticator.authenticate}"/>
Another exception to the rule is if EL notation is used inline in a property value (and not the first
character). In this case, Seam treats the value as a regular string and assigns it to the property as is. It is the
responsibility of the application logic to interpolate the string for any embedded EL expressions. For example,
you might want to define a contextual message string:
<framework:created-message>
You have successfully added #{course.name}.
<framework:created-message>
One of the main benefits of using the EL is that it is universal. You are going to learn in section 5.3.4 how
it enables components to be wired together. The point I want to make here is that you use the same syntax for
assigning any value, whether it be a simple type, a basic object (e.g. a date), or reference to another component
instance. In Spring, on the other hand, you have to choose the appropriate element depending on the type being
injected. For instance, to inject a reference to another bean you use <ref> or <bean>. To assign a null value,
you use <null>. You will see more elements compared in the next section.
With Seam, you can use an EL expression regardless of the type. The #{passwordManager} can inject
a reference to the PasswordManager component and #{null} can be used to assign a null value. No longer
do you have to worry about choosing the right element. In fact, you will find that the same is true for assigning
collection and map values. While Seam does introduce elements for building a collection of values, you can
alternatively use the EL to handle the assignment.
Collections and maps
There are two ways to assign a value to a property whose type is a collection, aside from using an EL value
expression. You can either use a flat string value, which Seam will automatically slice and dice to extract the
values, or you can specify each item explicitly using nested XML elements. Obviously, the second option only
works when using the component descriptor. XML is also the only way to assign values to maps. The value of
each item can be a basic value or an EL value expression.
IMPORTANT
In order to use component configuration on a collection property, the property must be a parameterized
collection or it must be an array. Seam relies on the generic type information in the parameterized
collection or the array type to convert the values.
Let's start by looking at collections. Seam converts flat values into collections by splitting on all of the
following characters:
Licensed to Jaroslaw Gilewski <[email protected]>
z
comma
z
space
z
end-line (any type) (\n, \f, \r)
z
tab (\t)
Seam uses the flat value converter if the property type is a collection (which includes both arrays and
java.util.Collection types) and the value is supplied using one of the following methods:
z
an external property settings
z
an attribute value on custom namespace element
z
a <property> or custom element without placing the property value inside a nested <value>
element
Let's try out an example. Assume that in the register form, we want to be able to allow the member to
indicate their pro status—amateur, pro, or semi-pro. These values will be stored on the RegisterAction
component and made available as JSF select items using the <s:selectItems> element.
@Name("registerAction")
public class RegisterAction {
// ...
private String[] proStatusTypes;
public String[] getProStatusTypes() { return this.proStatusTypes; }
public void setProStatusTypes(String[] types) {
this.proStatusTypes = types;
}
}
The types can be defined using XML using of the options previously discussed. The difference is that the
value will be split on the characters mentioned above before being assigned to the property. Here is an example
of defining the property in XML.
<component name="registerAction">
<property name="pro-status-types">amateur pro semi-pro</property>
</component>
The property can also be assigned using an external property setting.
registerAction.proStatusTypes=amateur pro semi-pro
This converter works great when the values don't contain any of the delimiter characters. But what
happens when the delimiter shows up in one of the values? Let's consider an example that demonstrates this
problem and learn how to work around it.
Assume that we also want to allow the member to indicate their specialty. The array property
specialtyTypes will be used to hold these options. If the values contain spaces, we will have a problem
with using the flat syntax. To work around this situation, you must use the child <value> element on the
<property> element or a custom property element. Therefore, the only option is to define it using XML.
<component name="registerAction"
class="org.open18.action.RegisterAction">
<property name="pro-status-types">amateur pro semi-pro</property>
<property name="specialtyTypes">
Licensed to Jaroslaw Gilewski <[email protected]>
<value>Driving</value>
<value>Chipping</value>
<value>Putting</value>
<value>Iron play</value>
<value>Lookin' good</value>
</property>
</component>
What I really like about how Seam handles multi-value types is that it does not force you to use different
elements for different types of collections. For instance, there are no special elements like <set> and
<list>.
Seam can also support associative types—or to use the more familiar term, maps. To make this work, you
have to use both a <key> element and a <value> element inside the property element. Once again, the only
option is to define this type of value using XML. Let's assume that we want to associate codes to each of the
specialties listed above. We first have to change the specialtyTypes property to a java.util.Map. Then
we can define the key-value pairs using the following stanza:
<component name="registerAction"
class="org.open18.action.RegisterAction">
<property name="pro-status-types">amateur pro semi-pro</property>
<property name="specialtyTypes">
<key>DRIVE</key> <value>Driving</value>
<key>CHIP</key> <value>Chipping</value>
<key>PUTT</key> <value>Putting</value>
<key>IRON</key> <value>Iron play</value>
<key>LOOKS</key> <value>Lookin' good</value>
</property>
</component>
Interestingly enough, you can also make specialtyTypes a java.util.Properties and the same
declaration would work. Once again, Seam does not force you to use different elements for different types of
associative types.
Replacement tokens
There is one more level of abstraction that you can use when supplying property values. Instead of using a
value or value expression in the property declaration, you can use a replacement token. Tokens are names that
are surrounded by @ symbols. The value of a token is read from the components.properties file at the root of
the classpath and applied to the component definition. Tokens make it easier to customize the values for
different environments without having to modify the descriptor itself. Note that the token has to represent the
whole property. It will not work if you try to put the token inline in a string property. You can, however, use
EL in the value of the token.
I am going to cite the example that you see most often used in a Seam application, toggling of debug
mode. Assuming that you have the following property set in the components.properties file.
debug=true
You can then use this key as a token value in the component descriptor:
<core:init debug="@[email protected]"/>
Licensed to Jaroslaw Gilewski <[email protected]>
The value of the token can be a value expression, which will be subject to further evaluation:
debug=#{facesContext.externalContext.request.serverName eq 'localhost'}
Although not as likely, the token could also represent a reference to another component. That brings us to
the topic of component wiring. I introduced the EL as a means of assigning a property value, but there are
points to consider about what happens when references to other components are assigned as property values.
5.3.4 Wiring components together
From within a component, you typically don't ask the container to resolve a context variable explicitly. By
doing so, you would be coupling your code to the Seam container or the EL resolver. You tend to want to
avoid doing this in your core components (it's acceptable, however, to do this in unit tests and helper classes
used exclusively by those tests).
In the true spirit of POJO (plain old Java object) development, you should declare that you want to receive
the value of a context variable via an injection. Ouch, needles! Don't fret, this does not involve any shots. I am
talking about dependency injection, or perhaps the more affable term, inversion of control. Much better. The
idea is that you don't have to worry about seeking out references to dependent components in the middle of a
business method. Instead, you declare the dependency on another object by exposing a bean property that can
be populated using component configuration. These dependencies can either be other components, JavaBeans,
or primitive values, all of the types that you studied in the last section. You then instruct the container to inject
the dependent value into a property of the component at runtime. The value is injected either when the
component instance is created or when a method on the component is executed, depending on whether you use
static or dynamic dependency injection.
Static vs dynamic dependencies
Seam has two types of dependency injection, static and dynamic. Both styles use reflection to assign values to
either the fields or properties—setter methods—of a bean. The difference between static and dynamic injection
is when they occur.
With static injection, Seam assigns a value to a field or property of a component instance when it is
created. You assign the value using component configuration. This style of injection is analogous to that used
by other lightweight containers, such as Spring and the JSF managed bean facility. The assignment happens
only once and uses whatever resources are available at the time the component is instantiated. After that point,
the values of the fields and properties of the component instance are not affected by this mechanism. What's
done is done. The properties will get their shot again when a new instance is created. When you use component
configuration, you are performing static dependency injection.
Seam also supports a style of dependency injection that is dynamic. This hook is activated by placing the
@In annotation above a field or JavaBean-style property "setter" method. These injections are resolved each
time a method is invoked on a component. This mechanism is the key to Seam's inversion of control, which
you will learn about in depth in the next chapter.
A component wiring example
Seam-gen applications include one use of static dependency injection for wiring the entity manager factory
into the entity manager component. You are going to learn about persistence unit configuration in chapter 9.
Right now you are focusing on the mechanics of the wiring. The component descriptor from the Open 18
application includes the following two stanzas from the persistence namespace:
Licensed to Jaroslaw Gilewski <[email protected]>
<persistence:entity-manager-factory name="open18EntityManagerFactory"
persistence-unit-name="open18"/>
<persistence:managed-persistence-context name="entityManager"
auto-create="true"
entity-manager-factory="#{open18EntityManagerFactory}"/>
The entity manager factory component is an application-scoped component that is bootstrapped on startup
(it has the @Startup annotation). The entity manager is a conversation-scoped component. Both components
are wrappers around the native JPA EntityManagerFactory and EntityManager respectively.
When the entityManager context variable is resolved during the course of a conversation, an instance
of the entity manager component is created and the entity manager factory component is wired into it. The
entity manager component uses the EntityManagerFactory it wraps to create and return a new JPA
EntityManager. That entity manager instance is bound to the conversation and is only destroyed (and
garbage collected) when the conversation ends.
The PasswordManager component is not very useful just sitting there all alone. Let's take it a step
further and wire the PasswordManager and entity manager components into the RegisterAction
component to complete the registration process. The PasswordManager component is fed into the
RegisterAction component so that the new member's password can be hashed in the register()
method. The EntityManager instance, prepared by the entity manager component, is wired into
RegisterAction so that the new golfer can be persisted. Listing 5.5 shows a basic implementation of the
RegisterAction component. Instead of littering the class with getters and setters, I have decided to save
space by using private fields for the dependent components.
Listing 5.5 The RegisterAction component augmented to include its dependencies
package org.open18.action;
import ...;
@Name("registerAction")
public class RegisterAction {
private EntityManager entityManager;
private PasswordManager passwordManager;
private Member newMember;
private PasswordBean passwordBean;
public String register() {
if (!passwordBean.verify()) {
return "failed";
}
newMember.setPasswordHash(
passwordManager.hash(passwordBean.getPassword()));
entityManager.persist(newMember);
return "success";
}
}
The next step is to wire the dependent objects to the properties of this component in the component
descriptor. All of the dependent components of RegisterAction are wired into it using component
configuration:
Licensed to Jaroslaw Gilewski <[email protected]>
<component name="registerAction">
<property name="entity-manager">#{entityManager}</property>
<property name="password-manager">#{passwordManager}</property>
<property name="new-member">#{newMember}</property>
<property name="password-bean">#{passwordBean}</property>
</component>
NOTE
Before you run off and start developing your application using this approach, I want to let you know that
you almost never wire component instance together using static injection. You particularly want to avoid
injecting components from short-term scopes into long-term scopes, as it can lead to scope impedance,
which you will learn about in the next chapter. The only reason I can safely do it in this example is because
the RegisterAction component is scoped to a lifetime that is equal to or shorter than all of its
dependencies. It is much cleaner, and safer, to define the dependencies using dynamic injection using @In,
which you will learn about in the next chapter.
With all of this great knowledge of how to configure the properties of a component, you are ready to go
off and configure component properties. But not before you are made aware of a potential hangups that you
can run into. You need to be cognizant of the fact that there is a difference between defining and configuring a
component when using the component descriptor.
5.4 Component definitions versus component configuration
The component descriptor can be used to define a new component, configure the properties of an existing
component, or define a new component and configure its properties simultaneously. You have to be aware of
what constitutes a component definition and when the <component> element is merely being used to assign
initial property values of a component declared using annotations. The difference is actually quite simple.
If the <component> element defines both a class attribute and a name attribute, it is considered to be a
complete component definition. If the class has already been declared as a component with the same context
variable name as defined by the @Name annotation, and exception is thrown at startup. If either the class
attrribute or name attribute is excluded from the <component> element, then Seam treats that declaration as
supplemental configuration. XML-based component definitions defined using custom XML elements are
handled with more leniency when it comes to conflicts.
An XML element using a component namespace (e.g. <auth:password-bean>) implies the class
attribute and possibly the context variable name, if the class is not annotated with @Name. Fortunately, Seam is
smart about how namespace component elements are to be handled, so as to avoid conflicts. If a component
definition already exists—meaning the class has a @Name annotation—Seam understands that the XML
declaration is being used to assign component properties, not to define a new component. That is why you are
able to configure built-in Seam components without danger of conflicting with the existing component
definition declared using annotations on the class. In cases where a component definition doesn't already exist,
the namespace element is considered to be a complete component definition.
If you are configuring a class that is not a Seam component (it does not have the @Name annotation or the
value of the @Install annotation is false), no worries. You can simply define the component using the
<component> element and you are ready to start adding property declarations. However, if the class that you
want to configure is already a Seam component (it has a @Name annotation and either doesn't have an
@Install annotation or the value of the @Install annotation is true), then you have to make sure you don't
collide with the existing definition. Let's take a closer look at how to sidestep this collision.
Licensed to Jaroslaw Gilewski <[email protected]>
5.4.1 Avoiding conflicts with an existing definition
To avoid conflicting with an existing component definition when declaring component property values, you
have to satisfy one of the following three conditions:
z
use a custom element from an XML namespace (i.e. <auth:password-manager>)
z
use <component>, supplying either a class or name attribute, but not both
z
use <component>, supplying a higher precedence using the precedence attribute
cueballs in numbered list and text
The XML namespace approach [#1] is your best choice because you never run into the conflict of trying to
configure a component rather than define it. If you use <component> and only supply the class or name
attribute [#2], you effectively augment an existing declaration, which must exist elsewhere. A new component
definition with a higher precedence value [#3] takes priority over any existing component definition.
If your component definition doesn't satisfy one of these three conditions in an attempt to configure an
existing Seam component, an exception will be thrown when Seam starts:
java.lang.IllegalStateException:
Two components with the same name and precedence
Let's consider some examples. The following XML stanza defines a Seam component for the
PasswordManager class:
<component name="passwordManager"
class="org.open18.auth.PasswordManager"/>
If you tried to start the application using both the XML definition and the
@Name("passwordManager") annotation on the PasswordManager class, Seam would throw an
exception stating that two components are defined with the same name and precedence. You could alter the
XML stanza so that it does not collide with the existing definition, and thus prepare it for property
configuration, by making one of four changes:
1. You can use a custom element name from the auth XML namespace:
<auth:password-manager/>
2. You can remove the class attribute:
<component name="passwordManager"/>
3. You can remove the name attribute:
<component class="org.open18.auth.PasswordManager"/>
4. You can set a higher precedence:
<component name="passwordManager"
class="org.open18.auth.PasswordManager"
precedence="25"/>
Licensed to Jaroslaw Gilewski <[email protected]>
The last variation trumps the @Name annotation. It is able to override the existing definition since its
precedence (25) is higher than the default precedence (20).
If you use @Name on a class and define a component for that class in the component descriptor using a
different context variable name, the result is two separate component definitions. Remember, component
definitions are keyed by class and context variable named.
5.4.2 Contributions from both parties
In the event that the component definition is overridden in XML, any attribute from table 5.2 that is not
defined in the XML declaration will be inherited from the equivalent annotation if defined on the class. A
component definition in an XML file can take contributions from the annotations on the class, just as long as
the name and precedence don't collide. For instance, the XML definition can use the scope defined in the
@Scope annotation on the class without having to define the scope in the XML. If the annotation
@Scope(ScopeType.APPLICATION) were defined on the class, then that scope value would be applied to
the component definition as if the scope attribute had been defined on the <component> element. Basically,
not all attributes need to be specified in the same place. If a setting is defined both in the annotation and in the
XML, then the value in the XML always wins. This hybrid approach is how you override the component
definition in classes that you don't control, such as Seam's built-in components.
Now that you have studied defining and configuring components in great detail and have been debriefed
on how to avoid conflicts in component definitions when using the component descriptor, you should feel very
comfortable configuring built-in Seam components.
5.5 Configuring and enabling built-in components
The component descriptor is your command center for configuring Seam. As much as I hate to say that the
component descriptor allows you to "program in XML", you can use the component descriptor to program in
XML. Seam provides a lot of glue code for solving common web application problems or for integrating
disparate technologies that you no longer need to write. However, the generic code can only go so far. You
need to step in and configure that code. The component descriptor gives you a means of affecting these areas
of Seam.
5.5.1 Using the component descriptor to control Seam
The component descriptor allows you to control Seam to accomplish the following goals:
z
Configure Seam runtime settings—Seam has a lot of switches and levers that you can use to control
how it functions. To cite a couple of examples, you can enable debug mode, disable transaction
management, define the authentication handler and authentication preferences, customize parameter
names used to manage conversations and the conversation timeout period, specify the names of the
resource bundles, or configure the available themes. These components act as the central switchboard
in Seam. Most do not manage a resource but rather reflect the configuration state of Seam itself.
z
Activate a feature that is disabled by default—Some of the built-in components are not useful to all
applications (or may depend on an environment that is not always available). Seam disables these
components by default. The component descriptor gives you an opportunity to enable them. For
instance, you can activate the asynchronous dispatcher, enable jBPM, hook into the e-mail service,
start the Spring container adapter, or define a JMS topic.
z
Customize a component template—Seam provides a number of component templates. These are
cookie-cutter components that you can customize for your own application domain. Examples include
the entity manager factory and managed persistence context (and the Hibernate equivalents), an
Licensed to Jaroslaw Gilewski <[email protected]>
application framework object (Query, Home and Controller), a JMS topic publisher or message sender,
or a JBoss Rule manager. These classes do not bear the @Name annotation and therefore are not
components until you strike them into action by configuring them in the component descriptor.
At times the distinction between these goals becomes blurry, since you may be activating and configuring
a component or service at the same time. The main point to take away is that Seam is highly configurable and
the component descriptor is your switchboard. If I tried to mention all of the built-in Seam components you
can configure, I would come up well short since new features are constantly being added. As you advance
through this book, you are going to keep coming back to these configurations, so take this as your introduction
to them. That said, I will give you a snapshot of the functional areas of Seam that you might like to configure.
5.5.2 A survey of Seam's built-in components
What makes configuring Seam components so easy to get started with is that there is a component XML
namespace and associated XML Schema vocabulary for Seam's entire configurable API. By adding one of the
namespaces in table 5.5 to the component descriptor, you can use tag completion support in the IDE to
research what you can configure. If you are the exploratory type, then I encourage you to add all of the
namespaces from this table to a complete descriptor and start poking away. It is an excellent way to explore the
hooks that Seam makes available to you.
Table 5.5 Built-in component XML namespaces
Namespace URI / schema location
Purpose
http://jboss.com/products/seam/components
http://jboss.com/products/seam/components-2.0.xsd
Generic component definitions, factories, event observers, and
context variable prefix imports
http://jboss.com/products/seam/async
http://jboss.com/products/seam/async-2.0.xsd
asynchronous dispatchers
http://jboss.com/products/seam/bpm
http://jboss.com/products/seam/bpm-2.0.xsd
jBPM integration
http://jboss.com/products/seam/core
http://jboss.com/products/seam/core-2.0.xsd
core Seam settings
http://jboss.com/products/seam/drools
http://jboss.com/products/seam/drools-2.0.xsd
Drools configuration and security rules
http://jboss.com/products/seam/framework
http://jboss.com/products/seam/framework-2.0.xsd
Seam application (CRUD) framework
http://jboss.com/products/seam/international
http://jboss.com/products/seam/international-2.0.xsd
locale and timezone selector components
http://jboss.com/products/seam/jms
http://jboss.com/products/seam/jms-2.0.xsd
JMS integration
http://jboss.com/products/seam/mail
http://jboss.com/products/seam/mail-2.0.xsd
e-mail integration and connection settings
http://jboss.com/products/seam/navigation
http://jboss.com/products/seam/navigation-2.0.xsd
global navigation rules and resource locations for global page
descriptors
http://jboss.com/products/seam/pdf
http://jboss.com/products/seam/pdf-2.0.xsd
PDF document storage and key-store configuration
http://jboss.com/products/seam/persistence
http://jboss.com/products/seam/persistence-2.0.xsd
persistence units and manager configurations
http://jboss.com/products/seam/remoting
http://jboss.com/products/seam/remoting-2.0.xsd
JavaScript remoting settings
http://jboss.com/products/seam/security
http://jboss.com/products/seam/security-2.0.xsd
identity (authentication and authorization) configuration
http://jboss.com/products/seam/spring
Spring integration
Licensed to Jaroslaw Gilewski <[email protected]>
http://jboss.com/products/seam/spring-2.0.xsd
http://jboss.com/products/seam/theme
http://jboss.com/products/seam/theme-2.0.xsd
UI theme selector and available themes
http://jboss.com/products/seam/transaction
http://jboss.com/products/seam/transaction-2.0.xsd
transaction providers
http://jboss.com/products/seam/web
http://jboss.com/products/seam/web-2.0.xsd
servlet filter configuration
The Seam project serves the XSD schema document for each of the namespaces in table 5.X at the URL
listed. To import one of these vocabularies into your component descriptor, choose a namespace URI and
declare it as an XML namespace. Then add both URI values in the xsi:schemaLocation attribute of the
root element. Here, an additional global page descriptor is added, perhaps for testing:
<components xmlns="http://jboss.com/products/seam/components"
xmlns:pages="http://jboss.com/products/seam/navigation"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://jboss.com/products/seam/navigation
http://jboss.com/products/seam/navigation-2.0.xsd
http://jboss.com/products/seam/components
http://jboss.com/products/seam/components-2.0.xsd">
<pages:pages>
<pages:resources>
<value>/WEB-INF/pages.xml</value>
<value>/META-INF/pages.xml</value>
</pages:resources>
</pages:pages>
</components>
Notice that I used nested <value> elements rather than the flat collection syntax (i.e. /WEBINF/pages.xml, /META-INF/pages.xml). The XML schema that accompanies Seam's component namespaces
typically enforce the use of the nested <value> element on collection property tags (i.e.
<pages:resources>).
Let's consider an example of using a built-in Seam component to configure Seam in your application.
5.5.3 Importing a resource bundle
One of the most notoriously tedious, yet disproportionately trivial features in a web application is resource
bundles. Resource bundles are typically used for displaying international messages (i18n), but they can also be
used for controlling environment-specific values. Resource bundles are a fancy way of saying property files
(i.e. java.util.Properties). The name of the properties file consists of the bundle name, followed by the
current locale prefixed with an underscore (_), followed by the .properties extension. If a file for the current
locale cannot be located, then a file with the name of the bundle and the .properties extension is used.
To use a play on words, Seam bundles all of these resource bundles together under a single component,
accessible through the context variable messages. The bundles it wraps are:
z
messages – the default message bundle
z
javax.faces.Messages – JSF message keys
z
ValidatorMessages – Hibernate validator messages
z
page-specific bundles defined in page descriptor
Licensed to Jaroslaw Gilewski <[email protected]>
The message bundles defined using the <message-bundle> element in the faces-config.xml descriptor
are not bundled. Instead, you define them in the component descriptor using component configuration. Let's
say that you had a resource bundle named application in one of the libraries used by the application and
you wanted to make those keys available to Seam. You would use the following component configuration to
add it to Seam's resource bundle:
<core:resource-loader bundle-names="messages application"/>
You could also declare each bundle name using the more verbose nested collection syntax:
<core:resource-loader>
<core:bundle-names>
<value>messages</value>
<value>application</value>
</core:bundle-names>
</core:resource-loader>
Notice that I included the bundle named messages. If you override the bundleNames property on the
resourceLoader component, as done here, you need to restore the default bundle name, which is
messages. At this point, the keys from the application bundles are merged into the collection of keys in the
Seam resource bundle, which you can access under the component name of messages from within your
application via EL. For instance, we can add i18n support to our specialty types:
<property name="specialtyTypes">
<key>DRIVE</key> <value>#{messages['specialty.drive']}</value>
<key>CHIP</key> <value>#{messages['specialty.chip']}</value>
<key>PUTT</key> <value>#{messages['specialty.putt']}</value>
<key>IRON</key> <value>#{messages['specialty.iron']}</value>
<key>LOOKS</key> <value>#{messages['specialty.looks']}</value>
</property>
</component>
These specialty keys would be defined in a properties file:
specialty.drive=Driving
specialty.chip=Chipping
specialty.putt=Putting
specialty.iron=Iron play
specialty.looks=Lookin' good
And with that, you can finally come up for air because you have mastered using the component descriptor
for defining and configuring components.
5.6 Summary
Seam minimizes the need for XML, but does not eliminate it. In this chapter, you learned that XML can be
useful, and in some cases necessary, for the purpose of defining and configuring Seam components. You were
pointed to the Seam component descriptor to accomplish these tasks. You were presented a mapping between
the attributes on the generic <component> element and the annotations that were covered in the last chapter.
Licensed to Jaroslaw Gilewski <[email protected]>
You came to appreciate that Seam's dedication to XML schema makes configuring built-in components
type-safe and gives you an opportunity to define your own component vocabulary. With component XML
namespaces, your declarations can be validated against an XML schema and allow tag completion in the IDE.
You saw many examples of these custom elements in use, both for defining components and configuring a
components properties.
You were trained to be able to distinguish between an XML declaration that serves as a component
definition and one that merely configures an existing component. If you are not aware of what these
differences are, you risk the error scenario in which two components with the context variable name and
precedence value combination are defined. You saw how it is possible to configure a component either using
XML or the Java properties syntax.
The many facets of component configuration were covered, showing you how to establish the initial state
of an object after it is instantiated by the Seam container. You learned that not only can property values be
supplied as primitive values, basic Java objects, and collections and maps, but also references to other
components, termed "component wiring". It was explained to you that component wiring is static dependency
injection, where the references are established at the time the component is instantiated. You learned that this
mechanism is consistent with the dependency injection used in Spring. In the next chapter, you are going to
learn how to use dynamic dependency injection. In dynamic dependency injection, the property value is
resolved and injected into the property each time the component is invoked. You will also learn about other
types of inversion of control such as on demand context variable creation, component events, and interceptors,
which round out the foundation of Seam's extensible inversion of control. Two XML elements left out of these
chapter that pertain to inversion of control, <factory> and <event> will be covered to complete your
education of the Seam component descriptor.
Licensed to Jaroslaw Gilewski <[email protected]>
6
Absolute inversion of control
Seam embraces the use of inversion of control not only to add behavior to components, but also to decouple
them from one another. This chapter focuses on two mechanisms that support dynamic interaction with one
another, bijection and events. Both are declared using annotations and are controlled through AOP
interceptors. It's possible to register custom interceptors using annotations as well.
In the last chapter you learned how a component instance establishes references to dependent objects
during initialization using static dependency injection. In this chapter you learn how that differs from dynamic
dependency injection, which is one aspect of bijection. Bijection is a radical new approach to component
assembly. It is not only used to wire components together through dependency injection—a key concept in
POJO-based development that helps keep components loosely coupled—but it also facilitates exporting the
value of properties on the component to context variables after a method on that component is invoked, a
mechanism known as outjection. Bijection is dynamic, which means that it happens on every public method
invocation 29, not just when the component is created. Events go further towards decoupling components by
allowing execution logic to jump from notifier to observer even though there is no direct reference between the
two components.
You begin the chapter by learning about the four steps of bijection—injection, method invocation,
outjection, and disinjection—that occur each time a public component method is invoked. You learn how
bijection is configured and how it encourages loose coupling between components. You then explore several
domain-specific bijection directives. One such variant supports "clickable lists" using a JSF DataModel in a
manner that is completely transparent to the business component.
Next, you learn about events and how they can be used to add features to the application without
disrupting well-tested code or to allow stateful components to receive a signal that prompts them to refresh
their state. Both bijection and events are instrumented using interceptors. You learn a variety of ways that you
can add your own interceptor logic to components. Shifting back to subject of context variables, you discover
how to use factory and manager components to generate context variables dynamically that are either not
bound to a Seam component or that require more sophisticated instantiation, respectively.
The theme of this chapter is about Seam manages the interaction between components, allowing you to
focus your attention on the application logic rather than boring low-level stuff. Seam achieves this
transparency by relying on AOP. Frankly, AOP can get exceedingly complex when you start getting into
pointcuts and advice. Seam takes the most useful parts of AOP, simplifies it, and makes using it as easy as
applying an annotation to a class, method, or field. Let's begin by exploring what makes bijection so unique
and how it can help simplify your application.
6.1 Bijection: dependency injection evolved
Dependency injection, arguably the most popular adaptation of the Inversion of Control (IoC) principle, made
POJO-based development possible. But, it has its limitations. The first is that dependency injection is onesided, handling only the case where one object depends on another. The relationship between the two objects is
29
Bijection can be disabled by applying the @BypassInterceptors annotation to the method or owning class. There
are also cases when it is skipped automatically. This topic is covered in section 6.4.
Licensed to Jaroslaw Gilewski <[email protected]>
established when the container "injects" the dependent object into the a property on the receiving object.
Through dependency injection alone, there is no way to have a property value sent back into the great wide
open the same way it came in. To cite another drawback, dependency injection is often implemented as a static
mechanism, meaning it is applied only once, when the object is created. As long as that object exists, it is stuck
with the dependencies that it is given at creation time. For as much as dependency injection has been studied,
discussed, and presented, it hasn't changed all that much since its emergence.
6.1.1 Introducing bijection
Seam changes all that. In Seam, the wiring of components can be done dynamically. By dynamic, I mean that
it happens continuously, throughout the lifetime of a component instance, not just when the instance is created.
In addition to injecting values, Seam dually supports outjecting values. Outject? Yeah, put away your
dictionary, it's a new term. Outjecting is the act of exporting the value of a property back onto a particular
scope. The value can then be picked up by another component, used, for instance, by a UI component tag in a
JSF view, in the page descriptor, or even in a jBPM business process definition.
The combination of injection and outjection is known as bijection. Bijection wraps each call to a method
on a component instance using an AOP method interceptor. Injection happens prior to the method being
invoked and outjection happens when the invocation is complete. The context variables that participate in this
interaction are exchanged with the Seam container. Injection takes variables from the Seam container and
assigns them to properties of the target component instance. Outjection defines or updates variables held in the
Seam container using the values of the corresponding properties on the component as they exist after the
method call completes. Figure 6.1 provides a high-level conceptual diagram of the bijection mechanism.
Figure 6.1 Bijection wraps a method call, performing injection before the call and outjection afterwards.
Just when you think it's safe to put that dictionary away, I'll mention that Seam also disinjects values from
a component. Disinjection is Seam's way of tying up lose ends after the bijection process is complete. This step
is indicative of the dynamic nature of bijection. The bijection process cleans up after itself by clearing the
values of the properties that received injections. The next time bijection is triggered on the component
instance, it starts with uninitialized property values.
Although bijection may inherit some traits of traditional dependency injection, the resemblances are
merely a subset of Seam's dependency management solution. You will discover how the bijection process
works, how it affects the relationship between your components, and how to put it to use as you read through
this section.
6.1.2 Bijection on the golf course
I am going to step away from the technicalities of bijection to give you a more general idea of what bijection
accomplishes by drawing on an example from golf. When you are playing in a golf tournament, you want all of
Licensed to Jaroslaw Gilewski <[email protected]>
your focus on your game. To help avoid distractions, you are supplied with a caddy who acts as your assistant.
Let's see what the caddy does for you between each stroke of the ball and how it relates to bijection.
As you approach your golf ball, in whatever context it might be lying—the green, the fairway, the sand
trap, or the woods—you aren't holding the golf club that you will need to take your stroke. The golf club is
your dependency. To satisfy this dependency, your caddy injects the golf club that is appropriate for the
context—a wood, iron, wedge, or putter—into your hands. You are now ready to swing. Striking the ball is the
action, or, in the case of bijection, the method invocation. After taking your stroke, the ball is outjected to
another part of the course, landing in another context—hopefully avoiding bodies of water and sand traps.
Outjection occurs as the result of a method call. Once the shot is taken, the caddy takes the club from you,
reclaiming the dependency that was previously injected into your hands and stores it in your bag for later use.
You walk away from the spot of the ball the way you arrived, empty handed.
Bijection is about inverting control, delegating routine work, and ultimately preventing tight coupling
between your components and the framework infrastructure. The reason that you use a caddy during a golf
tournament is so that you can focus your energy on your golf game rather than on the routine of carrying the
clubs, cleaning them, and remembering not to leave one behind. In this example, you are the component, the
club is the dependent object, and the caddy is the container. In the same way, bijection allows you to focus on
the business logic in your methods rather than rounding up dependent objects needed to perform the logic and
distributing results afterwards. As an added bonus, the components do not have hard dependencies on
environment-specific lookup mechanisms, so they can be easily tested in isolation or reused.
With this general understanding of how bijection works, let's now dig into the technical details of how it
works to prepare you for using it in your components.
6.1.3 The mechanics of bijection
When Seam instantiates a component, it registers a handful of AOP method interceptors that wrap the instance.
One of these interceptors handles bijection. The bijection interceptor is triggered each time a method is called
on an instance of a Seam component. The interceptor traps the method call, performing injections before the
method proceeds and then performing both outjections and disinjections after the method completes, but before
returning to the caller. The bijection interceptor's involvement in a method call is illustrated as the sequence
diagram in figure 6.2.
Figure 6.2 Illustrates how the bijection interceptor traps method calls to a component instance and performs the four steps of
bijection—injection, method invocation, outjection, and disinjection.
Licensed to Jaroslaw Gilewski <[email protected]>
The properties involved in bijection are designated using annotations. Prior to proceeding with the method
call, Seam locates the bean properties on the component that are marked with an @In annotation (or an
injection
variant,
such
as
@RequestParameter,
@DataModelSelection
and
@DataModelSelectionIndex) and helps those properties find the values for which they are searching. If
all @In annotations can be satisfied, or the ones that aren't are marked as optional 30, Seam then allows the
method to proceed. The code within that method can access any of the properties that Seam has prepared for it
via injection. As far as the logic in the method is concerned, it's as if the property values had been there all
along. If all goes well (meaning no exceptions are thrown), the bijection interceptor post-processes the method.
At that time, Seam iterates over the bean properties marked with an @Out annotation (or an outjection variant,
such as @DataModel) and helps those properties find their way back to the Seam container as context
variables in the scope where they would like to end up. Finally, Seam clears all the properties that received
injections, wiping the slate clean for the next invocation. Table 6.1 details what happens in each of the four
steps of bijection.
Table 6.1 An overview of the four steps of bijection.
Step
Description
1. Inject values
Seam iterates over properties marked with the @In annotation.
The context variable name (or value expression) supplied in each @In annotation is used to lookup
the context variable in the Seam container.
If the context variable cannot be found, it may be initialized.
The value held by the context variable is assigned to the property marked with @In.
2. Invoke method
The method invocation proceeds if all @In annotations can be satisfied.
The code executed by the method can access the values assigned via injection.
If the method completes without exception, bijection continues.
3. Outject (export) values
Seam iterates over the properties marked with the @Out annotation.
The value of each property is used to create or update the context variable whose name and scope
are provided in the @Out annotation.
4. Disinject (clear) values
Seam, once again, iterates over properties marked with the @In annotation.
The value of each property is cleared.
That sums up technical aspects of bijection. While you could probably explain bijection to your boss, you
may be wondering how it is useful to you. To put a spin on a popular phrase, no component is an island. They
need to be "wired" together. Let's explore how injection, the first step in bijection, can be used to bring
components together dynamically.
6.2 Dynamic dependency @In-jection
To implement the business logic, you are going to have to delegate work to other components. One such
delegate that you see in nearly every database-oriented application is the persistence manager (e.g. JPA
EntityManager or Hibernate Session), used to persist entities or read their state from the database. It's
possible to use component configuration to inject dependent objects. However, component configuration is
static, just like traditional dependency injection. That's fine for short-lived components or when injecting
singletons into stateful components, but it becomes a problem as soon as you start using stateful components
that interact with other stateful components. The state of these dependencies change from request to request
and the references become stale if not updated. The recommend way of hooking components together in a
30
Seam protects against the NullPointerException by enforcing @In to provide a value unless overridden by the
required flag.
Licensed to Jaroslaw Gilewski <[email protected]>
Seam application is to use bijection. It's the ideal choice because it is dynamic and can adapt to the application
as its state changes.
Annotations come into play when configuring bijection as they did when defining Seam components. The
most common dynamic injection annotation is @In, though there are also domain-specific variants. The @In
annotation, summarized in table 6.2, is placed above a bean property of a component—either a field or a
JavaBean-style property "setter" method. Seam will use reflection to assign, or inject, the corresponding value
that it searches out into this property during the first phase of bijection.
The value attribute on the @In annotation can be the name of a context variable, an EL value expression,
or it can be omitted so that the value is implied. In the case that the value attribute is not specified, Seam will
look for a context variable with a name equivalent to the property (according to JavaBean naming
conventions). If the value attribute is a non-EL string, it will be used to locate a context variable with that
name, thus allowing the name of the context variable and the name of the property into which it is injected to
be different. If the value attribute uses EL notation, it will be resolved and the result injected into the
property.
Table 6.2 The @In annotation defines a dependency injection
Name:
In
Purpose:
Indicates a dependency on a secondary component, which should be satisfied via injection.
Target:
METHOD, FIELD
Attribute
Type
Function
value
String
The context variable name or value expression associated with the dependent component.
If an instance is found, it will be injected into the marked property by the Seam container.
If no value is provided, the name of the bean property marked with this annotation will be
used as the context variable name.
create
boolean
Indicates that the component should be instantiated if an instance cannot be found. The
default value is false. However, if a value expression is used in the value attribute, the
create flag is treated as true. (e.g. #{courseHome})
required
boolean
A flag that indicates whether or not to enforce that the value to be injected is non-null. The
default value is true.
scope
ScopeType
The explicit Seam context in which to look for the component instance. Note that the
create flag cannot be used if a scope is specified. The scope is disregarded if the value
attribute is specified as a value expression, though the scope can be indicated in the
expression (e.g. #{eventContext.courseHome})
The most frequent use cases for using the @In annotation are to inject a persistence manager, a JSF form
backing bean, or a built-in JSF component. All three of these types are needed by the RegisterAction
component, defined in the last chapter. Listing 6.1 shows the RegisterAction component using dynamic
injection to supply all of the dependent components used to perform registration. The @Logger and @In
annotations are placed inline to conserve space.
Listing 6.1 RegisterAction refactored to use dynamic injection
package org.open18.action;
import
import
import
import
import
import
org.jboss.seam.annotations.*;
org.jboss.seam.faces.FacesMessages;
org.jboss.seam.log.Log;
org.open18.auth.*;
org.open18.model.Golfer;
javax.persistence.EntityManager;
Licensed to Jaroslaw Gilewski <[email protected]>
@Name("registerAction")
public class RegisterAction {
@Logger private Log log;
//1
@In protected FacesMessages facesMessages;
//2
@In protected EntityManager entityManager;
//2
@In protected PasswordManager passwordManager;
//2
@In protected Golfer newGolfer;
//2
@In protected PasswordBean passwordBean;
//2
// ...
public String register() {
log.info("registerAction.register() action called");
log.info("Registering golfer #{newGolfer.username}");
if (!passwordBean.verify()) {
facesMessages
.addToControl("confirm",
"Confirmation password does not match");
return null;
}
newGolfer.setPasswordHash(passwordManager
.hash(passwordBean.getPassword()));
entityManager.persist(newGolfer);
facesMessages
.add("Welcome to the community, #{newGolfer.name}!");
return "/home.xhtml";
}
}
<Annotation #1> Not part of bijection
<Annotation #2> Dynamic injections
With these changes in place, you should be able to run the RegisterGolferIntegrationTest and
verify that it passes just as before. The setup for the test is the same because Seam is reading the context
variables that we already put in place, just in a different way.
There are a number of permutations for how a context variable is resolved when processing the @In
annotation. Let's walk through the decision process that occurs for each @In property in the first phase of
bijection.
6.2.1 The injection process
The decision process used to perform an injection is illustrated in figure 6.3. You can see that there are two
main branches of this process, one where the value attribute of the @In annotation is expressed in EL
notation and one where the value attribute is absent or a context variable name. Use this diagram to follow
along with the discussion.
Licensed to Jaroslaw Gilewski <[email protected]>
Figure 6.3 The injection process.
Let's start by considering the case when the value attribute of the @In annotation is specified as a context
variable name. There are a few subtle differences between this case and when the value attribute of the
annotation uses EL notation.
Seam first looks to see if a scope is specified in the annotation. If so, Seam looks for the context variable
name in this scope. If a scope is not provided, Seam will perform a hierarchical search of the stateful contexts,
starting from the narrowest scope, ScopeType.EVENT, and continuing all the way to the broadest scope,
ScopeType.APPLICATION, as illustrated in figure 6.4. The search stops at the first non-null value that it
finds and assigns that value to the property of the component. Keep in mind that a context variable can hold
any value, not just a component instance.
Figure 6.4 The order in which the contexts are searched when Seam tries to locate a context variable.
Licensed to Jaroslaw Gilewski <[email protected]>
If the hierarchical context search fails to resolve a non-null value for a context variable, Seam will attempt
to initialize a value. But a value can only be initialized if the following two conditions are met:
z
the context variable name is bound to a component definition
z
the create flag is set on the @In annotation or the component supports auto create (e.g. uses the
@AutoCreate annotation)
If this binding between component and context variable name exists, then Seam will instantiate a new
instance of the component and store the instance in the context variable. The newly initialized value is then
injected into the property marked with the @In annotation. If there is no component bound to the context
variable name, then there is nothing for Seam to create, and the lookup value remains null. If the value is nonnull, it is assigned to the property.
Before finishing its work, Seam validates against the required flag. If the required flag is true, and Seam
was not able to locate a non-null value, then a runtime exception, RequiredException, is thrown. There
one exception to this rule. Required flags are never enforced on life cycle methods (in the case of injection and
outjection). Bijection still takes place for these invocations, but Seam assumes that the component is not yet
"in context" and therefore may not have all of its dependencies available. In the case that the method is a life
cycle method, or the required flag is otherwise false, null is assigned to the property if the context variable
does not exist or it is null.
ADVICE
If you find yourself making heavy use of the required flag to disable required injections (and later on
outjections), it is an indication that you are trying to make the component do too much. Refactor your
monolithic component into smaller, more well-defined components and use bijection or static injection to
wire them together.
When the value attribute is specified using EL notation, the semantics are much simpler. The work of
locating a value is delegated to the EL variable resolver. The same validation against the required flag is
performed as in the non-EL case.
If all such injections for a component succeed, then the method invocation proceeds. Before you check the
@In annotation from your list of Seam concepts mastered, let's consider how the dynamic nature of the
injection allows you to mix scopes and properly handle non-serializable data.
6.2.2 Mixing scopes and serializability
Recall that every time a method on a component is invoked, Seam performs the lookup to resolve a value
based on the information provided in the @In annotation (described in section 6.1.2). This dynamic lookup
allows you to inject a component instance stored in a narrow scope into a component instance stored in a
wider scope. Figure 6.5 shows a narrow-scoped component instance being injected into a wider-scoped
component instance at each invocation. If injection were to only happen when the wider-scoped component
instance is created, then the injected instance would be retained beyond the lifetime of its scope.
Figure 6.5 A narrower-scoped component being injected into a wider-scoped component.
Licensed to Jaroslaw Gilewski <[email protected]>
Let's pose this scenario as a question, plugging in actual scope names. What would happen if a requestscoped variable were to be injected into a session-scoped object via traditional dependency injection? Recall
that traditional dependency injection only occurs once, when the object is created. In this situation, even if the
request-scoped variable were to change between page requests, the session-scoped object would still hold the
original value of the request-scoped variable. The property on the session-scoped object that received the
injected value never gets updated. This describes the behavior of most other IoC containers in existence today.
By using Seam's dynamic dependency injection, the same property on a session-scoped component would
always reflect the most recent value of the injected request-scoped variable because the injections get reapplied
each time the component is invoked. Additionally, the value is not retained beyond the end of the method call
as Seam disinjects the value once the call is complete.
Let's consider another difficult scenario that is cleared up thanks to the dynamic nature of Seam's
dependency injection: mixing non-serializable objects with serializable objects—classes that implement
java.io.Serializable. If a non-serializable object is injected into the property of a serializable sessionscoped object and that property is not cleared prior to session passivation, the session storage mechanism will
get tripped up by the non-serializable data. You could, of course, mark the field as transient so that the value is
automatically cleared, hence allowing the session to properly passivate. However, the real problem is that once
the session is restored, the transient field remains null, acting as a land mine in the form of a
NullPointerException that may be triggered by an unsuspecting block of code.
Applying the @In annotation to the field solves both parts of the aforementioned problem. To begin with,
the value assigned through the @In annotation is cleared in the last phase of bijection, after the method on the
component is invoked. Seam terms this phase disinjection. Right out of the box, injections are transient
without you having to add the transient keyword on all of the fields marked with @In. But the real power
of injection is that the injected values are restored prior to the next method call. This continuous injection
mechanism allows objects coming out of passivation to be reinflated. Thus, disinjection paired with subsequent
injections should alleviate a very common, yet trite, pain point that arises when working with both session
replication and the reactivation of a session after a server restart.
But the @In annotation isn't the only type of injection that Seam supports. There are two variants to
consider that are more domain specific.
6.2.3 Injection variants
Seam includes two additional annotations capable of performing dynamic injection, @RequestParameter
and @PersistenceContext. The @RequestParameter annotation retrieves the value of a query string or
post variable. Like the @In annotation, you can either specify a parameter name explicitly using the value
attribute or have Seam imply the parameter name from the name of the property. It differs from @In in that a
value is never required.
Injecting a @RequestParameter
The @RequestParameter annotation is useful for creating RESTful pages that pass lookup values through
the URL query string without having to use page parameters. For instance, we want to create a page which
displays a golfer's profile. The ProfileAction component will be used to respond to a request for a golfer's
profile. The id of the golfer to display is passed through the URL as the request parameter golferId:
http://localhost:8080/open18/profile.seam?golferId=1
You can have the golferId request parameter injected into your Seam component by placing the
@RequestParameter annotation over a property with the same name. This injection happens whenever a
Licensed to Jaroslaw Gilewski <[email protected]>
method on the ProfileAction component is invoked. One such method is loadProfile(), which uses
the golferId to lookup the corresponding Golfer entity using the JPA EntityManager:
@Name("profileAction")
public class ProfileAction {
@In protected EntityManager entityManager;
@RequestParameter protected String golferId;
protected Golfer selectedGolfer;
public void loadProfile() {
if (golferId != null && golferId > 0) {
selectedGolfer = (Golfer)
entityManager.find(Golfer.class, golferId);
}
if (selectedGolfer == null) {
throw new ProfileNotFoundException(golferId);
}
}
}
To have the loadProfile() method invoked when the /profile.xhtml page is request, it is registered as
a page action for this view:
<page view-id="/profile.xhtml">
<action execute="#{profileAction.loadProfile}"/>
</page>
That is as far as we are going to go right now with this example. You still need to learn about how to make
the selected golfer available to the view before you can render the profile view, which is covered in the next
section.
Notice that a runtime exception, ProfileNotFoundException, has been introduced. This exception is
raised when the golferId request parameter fails to produce a golfer's profile. By throwing this exception,
we can prevent the profile page from being displayed when this happens. Instead, the user is presented with a
404 error, as defined by the @HttpError annotation on the exception class:
@HttpError(errorCode = HttpServletResponse.SC_NOT_FOUND)
public class ProfileNotFoundException extends RuntimeException {
public ProfileNotFoundException(Long id) {
super(id == null ? "No profile was requested" :
"The requested profile does not exist: " + id);
}
}
The Java EE container has its own way of injecting resources. While Seam mostly stays out of the way
and allows the container do its work, Seam does plays a role in the persistence context resource injection,
@PersistenceContext, by wrapping the JPA entity manager before it is injected.
Licensed to Jaroslaw Gilewski <[email protected]>
Augmenting the @PersistenceContext
Seam also interacts with the @PersistenceContext annotation, though in a slightly different manner. The
@PersistenceContext is a Java EE annotation that injects a container-managed EntityManager into a
Java EE component. As such, this annotation is only relevant for Java EE managed components (e.g. JSF
managed bean, EJB session bean, etc). Seam isn't totally hands off here, though. Seam intercepts the injection
and wraps the EntityManager using a proxy object before allowing the injection to proceed. This proxy
object is used to add value expression support to JPQL queries. You will see this annotation a lot more in
chapter 9, which covers managed persistence in Seam.
Dependency injection is as mainstream as MySpace and the iPod. While dependency injection may not be
a topic for discussion at a family gathering, you have probably read about it on more blogs than you care to
say. By now, you may be thinking, "I get it already!" But hold on a minute. Seam has a new act. Seam hasn't
just settled on the status quo by providing yet another dependency injection solution. Instead, Seam has proven
that dependency injection still has a couple tricks up its sleeve. The first trick was to make the injection
dynamic, as you just learned. But the show stopper that Seam put in the limelight is outjection, the flip-side of
injection.
6.3 @Out-jecting context variables
Outjection is a way of pushing state held by a component out to the Seam container. You can think of a
component as the parents and its properties the children. Outjection is like sending the child off (perhaps
kicking them out) to live in the world on their own. The parent (the component) exports the child (the
property) to a new address (the context variable). Other people (components) can visit that child at the new
address without consulting the parent. The property value is now associated with its own context variable that
puts it on equal footing with other components in the Seam container.
Outjection is designated by applying the @Out annotation, summarized in table 6.3, to a bean property—a
field or JavaBean-style "getter" method. After a component method is invoked, the value of the property is
used to create a context variable, or bind to one that already exists. If a name for the context variable is not
explicitly specified using the value attribute, then the name of the bean property hosting the annotation is
used. Like the @In annotation, value attribute on @Out allows the name of the context variable and the
property from which it is exported to be different. Note that the @Out annotation does not support EL notation
like @In.
Table 6.3 The @Out annotation. Annotating a property with @Out will outject its value to a seam context.
Name:
Out
Purpose:
Provides a mechanism to export the value of a property as context variable in the given scope.
Target:
METHOD, FIELD
Attribute
Type
Function
value
String
The name of the context variable into which the value of the property should be stored.
required
boolean
A flag that indicates whether or not to enforce that the value to be outjected is non-null.
The default is true.
scope
ScopeType
The explicit Seam context in which to place the component instance. The default is the
scope of the enclosing type.
Although not as complex as the injection process, the outjection process still has several decisions to make
before the context variable can be assigned.
Licensed to Jaroslaw Gilewski <[email protected]>
6.3.1 The outjection process
During outjection, the value of the property is assigned to the context variable with the same name or the
override in the value attribute. That much is straightforward. But determining which scope to use is a hair more
complex. Figure 6.6 shows the decision process that takes place during outjection.
Figure 6.6 The outjection process
If a scope is not specified, Seam attempts to locate a component that is bound to the candidate context
variable. If one exists, it ensures that the value's type is equivalent to the type of that component. It then uses
the scope of that component. If a component is not bound to the context variable, then the scope is inherited
from the owning component. However, there is one exception to this rule. If the scope of the owning
component is stateless, then the variable is exported to the event scope, since it is impossible to bind a context
variable to the stateless context. For that same reason, the stateless context can not be explicitly specified. In
the final step, the required flag is checked. If the required flag is true (the default) and the value being
outjected is null, a RequiredException is thrown. Recall that required flags are not enforced on life cycle
methods.
Let's consider when you might use outjection.
Licensed to Jaroslaw Gilewski <[email protected]>
6.3.2 Outjection use cases
Outjection is useful for accomplishing two goals. You can use it to expose the model to the view as a result of
executing an action listener method or you can use it to push data into a longer-term scope so it is available on
subsequent requests. Let's look at the two cases in turn.
Preparing the model for the view
The view isn't very useful without data to display. Preparing this data is typically the responsibility of the page
action or action listener. In Struts, you typically pass the model to the view by assigning values to
HttpServletRequest attributes in the action. Managing variables this way can be quite cumbersome and
couples your code tightly to the servlet API.
Before abandoning the servlet API, let's find a compromise that loosens this coupling and makes the code
less cumbersome. In chapter 4, you learned that Seam normalizes the servlet contexts under a single API. You
can inject one of these contexts directly into a Seam component using bijection. A context behaves like a map,
where the keys are the context variable names. You can add a context variables to the event scope with the
following setup:
@In Context eventContext;
public void actionListenerMethod() {
eventContext.set("message", "Hello World!");
}
Although this approach works, there is a cleaner, more declarative way of accomplishing the same task.
You simply mark properties with the @Out annotation that you want to have exported and your job is done.
Here is the same code using a declarative approach instead:
@Out(scope = ScopeType.EVENT) protected String message;
public void actionListenerMethod() {
message = "Hello World!";
}
Here you see that outjection decouples the context variable assignment from the business logic in the
action. (The scope attribute on the @Out annotation is only required if the target scope is different than the
scope of the component). As far as the view is concerned, it doesn't care how the context variable was
prepared.
Let's complete the golfer profile example started earlier by making the selected golfer available to the
/profile.xhtml JSF view. The goal is to create the profile page shown in figure 6.7.
Figure 6.7 A golfer's profile page. The golfer data is supplied by the outjected selectedGolfer context variable.
Licensed to Jaroslaw Gilewski <[email protected]>
To extract the value of the selectedGolfer field from the ProfileAction component when the loadProfile()
is invoked, we add the @Out annotation above the field. The value of the field is then assigned to the
equivalently named context variable in the event context:
@Name("profileAction")
public class ProfileAction {
// ...
@Out protected Golfer selectedGolfer;
public void loadProfile() {
// ...
}
}
The selectedGolfer context variable can then be referenced in value expressions in the /profile.xhtml
page as shown here:
<h1>#{selectedGolfer.name}</h1>
<rich:panel>
<f:facet name="header">Profile</f:facet>
<s:decorate template="layout/display.xhtml">
<ui:define name="label">Gender</ui:define>
#{selectedGolfer.gender}
</s:decorate>
<s:decorate template="layout/display.xhtml">
<ui:define name="label">Birthday</ui:define>
<h:outputText value="#{selectedGolfer.dateOfBirth}">
<s:convertDateTime pattern="MMMM dd, yyyy"/>
</h:outputText>
</s:decorate>
...
</rich:panel>
The other use case for outjection is to propagate state.
Keeping data in scope
Just because a field is outjected does not mean it has to be used in the view. It's quite possible that you outject
a value just so that it can be injected by a action listener method executing on a subsequent request. Say
goodbye to hidden form fields and the mentality of having to do the heavy lifting of propagating variables
from one request to the next. You can simply using outjection to put a context variable on a shelf (a long-term
scope such as the page, conversation, or session scope) and pull it down when you need it again. Obviously,
you want to be careful about overuse of the session. The point here is on the decoupling of preserving state and
the business logic. This technique is especially useful for developing conversations, which are covered in
chapter 7.
That's just about sums up what you need to know to use bijection. I will admit that if you forget the basic
mechanics of bijection, its easy to lose track of what is going on. To make sense of it all, what you want to
keep in mind is that injections happen before a method is invoked on a component and outjections happen after
the method is invoked, as was illustrated in figure 6.1 Finally, the injections are cleared when the process is
Licensed to Jaroslaw Gilewski <[email protected]>
complete. Repeat it to yourself. That way, when you see the error message "@In attribute requires non-null
value" or "@Out attribute requires non-null value," you will know what Seam is trying to tell you.
6.3.3 Built-in @DataModel support
In JSF, UIData components, such as <h:dataTable>, are backed by a special collection wrapper called a
data model. A data model is a way for JSF to adapt various types of collections to the UI component model in
a consistent way and to support capturing a row selection in the UI. The collection wrappers extend from the
abstract class javax.faces.DataModel and support the major collection types. Seam builds on this set by
adding one of its own wrappers to support the Query component from the Seam Application Framework. The
Query component manages the result of an JPQL/HQL query, which you will learn about in chapter 10. The
mapping between the collection types and the corresponding wrappers are enumerated in table 6.4.
Table 6.4 The corresponding JSF data model wrapper for each type of collection.
Native Collection
JSF DataModel Wrapper (javax.faces.model.*)
java.util.List
ListDataModel
Array
ArrayDataModel
java.util.Map
MapDataModel
java.util.Set
SetDataModel
org.jboss.seam.framework.Query
ListDataModel
So what do these wrappers have to do with bijection? To properly prepare collection data to be used in a
UIData component, you should wrap it a JSF data model. Why should you have to deal with this drudgery in
your business logic? This task sure sounds like something the framework should handle. Seam developers
agree. For this purpose, they created the @DataModel annotation, summarized in table 6.5. The @DataModel
is used in place of the @Out annotation for outjecting collections. The functionality that the @DataModel
annotation adds is that before the value of the @Out property is assigned to the context variable, it is first
wrapped in the correct JSF DataModel implementation. The @DataModel annotation supports all of the
collection types listed in table 6.4.
Table 6.5 The @DataModel annotation outjects a collection as a JSF DataModel
Name:
DataModel
Purpose:
Wraps a collection in a JSF DataModel and outjects the resulting value.
Target:
METHOD, FIELD (must represent a collection or array)
Attribute
Type
Function
value
String
The name of the context variable into which the value of the wrapped collection should be
stored.
scope
ScopeType
The explicit Seam context in which to place the wrapped collection. If a scope is not given,
the scope of the owning component is used.
Let's use Seam's data model support to display a list of newly registered golfers on the home page. The
names will eventually be made into links so that a user can click on a name to bring up the golfer's profile. The
goal of this section is to produce the output shown in figure 6.8.
Licensed to Jaroslaw Gilewski <[email protected]>
Figure 6.8 The list of new golfers prepared as a JSF data model as a result of outjection.
The business logic is responsible for looking up the list of new golfers, but we will let Seam handle the
details of wrapping that collection in a JSF DataModel and exposing it to the view. In preparation for that to
happen, the @DataModel annotation, a variant of the @Out annotation, is declared on the newGolfers field:
@Name("profileAction")
public void ProfileAction {
@DataModel protected List<Golfer> newGolfers;
// ...
}
Of course, that's not enough for the newGolfers field to be outjected. Heck, it's not even populated.
First, we need a method that populates this field. Then, we need to figure out how to get outjection to occur.
Let's reuse the ProfileAction component. We add the method findNewGolfers() to load the list of
new golfers from the database. To keep things interesting, we perform a little quick and dirty randomization on
the list:
@Name("profileAction")
public class ProfileAction {
private int newGolferPoolSize = 25;
private int newGolferDisplaySize = 5;
@Out(required = false) protected Golfer selectedGolfer;
@DataModel protected List<Golfer> newGolfers;
// ...
public void findNewGolfers() {
newGolfers = entityManager
.createQuery(
"select g from Golfer g order by dateJoined desc")
.setMaxResults(newGolferPoolSize)
.getResultList();
Random rnd = new Random(System.currentTimeMillis());
while (newGolfers.size() > newGolferDisplaySize) {
newGolfers.remove(rnd.nextInt(newGolfers.size()));
}
}
}
Licensed to Jaroslaw Gilewski <[email protected]>
As you have learned, when a method on a Seam component is executed, such as findNewGolfers(),
bijection takes place. That means the once the invocation of this method is complete, value of the
newGolfers field is going to be wrapped in a JSF DataModel and outjected to the equivalently named
context variable the event scope. The event scope is chosen since that is the scope of the component and the
scope is not explicitly specified.
Notice that required attribute on the @Out annotation above the selectedGolfer property is now set
to false. This directive is necessary since the findNewGolfers() does not assign a value to
selectedGolfer and bijection would otherwise complain that it is not set. This situation often arises when
you use the same component for more than one purpose. If you find yourself making excessive use of the
required directive, it is an indication that the component is trying to do too much and that you should divide it
into smaller, more focused components.
Once the findNewGolfers() method is invoked, the JSF view will have access the collection of new
golfers using the value expression #{newGolfers}. The UIData component that renders the list of golfers
(in this case <rich:dataList>) sees a ListDataModel rather than the java.util.List:
<rich:panel>
<f:facet name="header">Cool New Golfers</f:facet>
<rich:dataList var="_golfer" value="#{newGolfers}">
#{_golfer.name}
</rich:dataList>
</rich:panel>
TIP
You may notice in this code snippet that I prefix the iteration variable (the golfer) with an underscore (_). I
recommend this coding style to make it clear that the variable is local to the iteration loop and to avoid
conflicts with context variables.
The only question remaining is, when is the findNewGolfers() method invoked? We want to execute
this method eagerly when the home page is requested, not as a result of a user-initiated action. Once again, we
use a page action:
<page view-id="/home.xhtml"
action="#{profileAction.findNewGolfers}"/>
Now, whenever the home page is requested, the findNewGolfers() method prepares the newGolfers
context variable for the view. With the @DataModel annotation, you never have to think about the JSF
DataModel interface. It is completely transparent. Seam even takes care of reinitializing the context variable
whenever a change is detected in the underlying collection. Your component is free to use properties that are
native Java collection types. Your UI component sees the exported data as the appropriate DataModel
wrapper class. Where this pays off in capturing a row selection from the DataModel, again without having to
tie your component to the JSF API.
Making a @DataModelSelection
Presented with a list of golfers, the user of the application will want to interact with that list. One common use
case is "clickable lists". To continue with the example, the user clicks on the name of one of the golfers in the
table and the application brings up the golfer's profile. This mechanism is a contrast to using a RESTful URL,
though the two can coexist.
Licensed to Jaroslaw Gilewski <[email protected]>
In a clickable list, the user is performing an action on the data associated with the row that receives the
event. On such action is "drilling down", which is demonstrated here. Other actions include deleting and
editing. The only limitation is that this mechanism can only be applied to a single row at a time.
How do you know which row was clicked? Seam works in conjunction with JSF to take care of these
details. One of the reasons for using a DataModel in a UIData component is so that JSF can correlate an
event triggered from a row in the table with the data that is used to back that row. Seam can take the data from
the selected row and inject it into the component receiving the action using one of two @In-style annotations.
Clickable lists are effortless in Seam thanks to the @DataModelSelection annotation. The
@DataModelSelection annotation injects the value of the selected row in a collection previously exported
by the @DataModel annotation and assigns it to the corresponding component property. Seam also supports
capturing the index of the selected row using the @DataModelSelectionIndex annotation. You place
either of these two annotations, summarized in table 6.6, on a separate property of the same component in
which the @DataModel annotation is defined. Of course, the property annotated with
@DataModelSelection must have the same type as the items held in the collection, while the property
annotated with @DataModelSelectionIndex must be a numeric type.
Seam will put two and two together and assign the value of the selected row in the UI to the property
annotated with @DataModelSelection, or the index of the selected row to the property annotated with
@DataModelSelectionIndex. This selection occurs by triggering an action on a row of a UIData
component such as <h:dataTable>.
Table 6.6 The @DataModelSelection and @DataModelSelectionIndex annotation prepare the UIData selection
Name:
DataModelSelection / DataModelSelectionIndex
Purpose:
Captures the selected row (or row index) of the corresponding JSF DataModel
Target:
METHOD (setter), FIELD
Attribute
Type
Function
value
String
The context variable name of the DataModel. Defaults to the name of the
@DataModel property on the same component, if there is exactly one.
Let's build on the example of the list of new golfers by first making the names clickable. First, we enhance
the ProfileAction component to include a method named view() that will be used to select a golfer from
the new golfer list. This method will eventually be bound to a UI command component to enable the selection:
<h:commandLink value="#{_golfer.name}"
action="#{profileAction.view}"/>
The implementation of the view() method is shown in listing 6.2, along with several changes to the class
that are highlighted in bold to support this new use case. The existing RESTful logic presented earlier is left
intact so that both use cases can be supported.
Listing 6.2 Exposing the collection of new golfers as a DataModel and capturing the selected golfer.
package org.open18.action;
import
import
import
import
import
org.jboss.seam.ScopeType;
org.jboss.seam.annotations.*;
org.jboss.seam.annotations.web.RequestParameter;
org.jboss.seam.annotations.datamodel.*;
org.open18.ProfileNotFoundException;
Licensed to Jaroslaw Gilewski <[email protected]>
import org.open18.model.Golfer;
import javax.persistence.EntityManager;
import java.util.*;
@Name("profileAction")
public class ProfileAction {
private newGolferPoolSize = 25;
private newGolferDisplaySize = 5;
@DataModelSelection
@Out(required=false, scope = ScopeType.CONVERSATION)
protected Golfer selectedGolfer;
//2
//3
@DataModel(scope = ScopeType.PAGE)
protected List<Golfer> newGolfers;
//1
public String view() {
assert selectedGolfer != null &&
selectedGolfer.getId() != null;
return "/profile.xhtml";
}
public void loadProfile() {
if (selectedGolfer != null &&
selectedGolfer.getId() != null) {
return;
}
//5
//4
if (golferId != null && golferId > 0) {
selectedGolfer = (Golfer)
entityManager.find(Golfer.class, golferId);
}
if (selectedGolfer == null) {
throw new ProfileNotFoundException(golferId);
}
}
public String findNewGolfers() {
newGolfers = ...;
}
}
<Annotation #1> Collection stored in UI component tree
<Annotation #2> Populated when golfer name is clicked
<Annotation #3> Conversation scope used to survive redirect
<Annotation #4> RESTful logic acknowledges selection
<Annotation #5> Dispatch directly to view ID, rather than using navigation rule
An important change had to be made to the ProfileAction component to support clickable lists. The
@DataModel property is now scoped to the page context (i.e. ScopeType.PAGE) rather than the event
context. In order for @DataModelSelection to capture the selected row, the original @DataModel
collection must be available on postback. If the collection changes, it may result in the wrong row being
selected. This happens because the row index stored with the event no longer points to the correct row in the
collection on postback. If the collection disappears altogether, the result will be a ghost click. The later
happens because the portion of the JSF component tree that held that event is discarded and thus the event does
Licensed to Jaroslaw Gilewski <[email protected]>
not fire. Regardless of how hard Seam searches, it will not be able to locate the data model selection. In both
cases, the @DataModelSelection and @DataModelSelectionIndex annotations serve little purpose.
UIData components rely on the underlying data to be stable during subsequent requests. To guarantee that the
collection remains stable, it can be stored in the UI component tree by scoping @DataModel to the page
context, as done in this example. Keep this point in mind when you deal with data models and UIData
components. You have to get your DataModel to make the leap from render to postback. In the next chapter
you will learn how conversations are even better suited to deal with this problem since they can carry data
across an arbitrary number of postbacks and even non-JSF requests.
Ghost clicks
One utterly frustrating problem of JSF that Seam alleviates is the "ghost click". Ghost clicks happen
when the portion of the UI component tree that queued a UI event is dropped.
Upon hearing this definition, you might be thinking, isn't that a bug? Sadly, it isn't. There are certain
branches of the UI component tree that are processed dynamically. If the structure of the tree is not
reproducible, there is a chance that behavior will be lost. An example of where this can happen is in
any branch controlled by a component in the UIData family. The UIData components are unique
because they are data-driven. Let's consider how UIData components are handled and how they can
lead to portions of the tree to be dropped.
JSF walks the UI component tree during rendering. When it encounters a UIData component, it
iterates over the collection in the DataModel that is bound to the component to render each row.
The component tree is then saved in the session or serialized and sent along with the response.
Although the data is a critical part of how UIData components operate, JSF only stores UIData
components themselves, not the underlying data.
When the component tree is restored on postback, JSF walks the UI component tree again, looking for
events (amongst other things). Events triggered on a UIData component are associated with the
index of the activated row. When JSF arrives at the UIData component, it again iterates over the data
model to process each row, lining up events with the row index. If the data model changes prior to the
restore stage, then the event may be correlated with the wrong row. Worse, if the data disappears,
then any event associated with that portion of the tree is silently discarded. The result is a ghost click.
The lesson to learn is that JSF expects the collection wrapped by the DataModel to remain stable
between render and postback. The easiest way to guarantee the stability of the collection is to store it
in the UI component tree. In Seam, you store a context variable in the UI component tree by scoping it
to the page context. This approach is similar to using the <t:saveState> tag from the Tomahawk
project, except that the state is designated in the Java code rather than in the JSF view. Conversations
offer another option.
To appreciate how maddening a ghost click can be, you have to be the victim of it at least once. The
profile example in this chapter gives you an opportunity to research the problem in a lab environment
when your job—or contract—isn't on the line.
Finally, we add an <h:commandLink> around the name of each golfer, which will invoke the view()
method on ProfileAction when clicked:
<h:form>
<rich:panel>
Licensed to Jaroslaw Gilewski <[email protected]>
<f:facet name="header">Cool New Golfers</f:facet>
<rich:dataList var="_golfer" value="#{newGolfers}">
<h:commandLink value="#{_golfer.name}"
action="#{profileAction.view}"/>
</rich:dataList>
</rich:panel>
</h:form>
When the view() action handler method is invoked, Seam takes the selected row from the @DataModel
collection, newGolfers, and injects it into the corresponding @DataModelSelection property,
selectedGolfer. Seam makes this association automatically since both properties reside on the same
component. If there were more than one @DataModel property on the component, it would be necessary to
specify the context variable name used by the @DataModel property in the value attribute of the
@DataModelSelection annotation in order for the correlation to be made. Failure to make this distinction
results in a runtime exception. The @DataModel property and the @DataModelSelection property must
always reside on the same component.
In JSF, you have to use a command link or button to perform a data model selection using the pattern just
described. That means a POST form submission occurs. What's worse is that JSF submits the form using
JavaScript. If you don't care how the job gets done, then the combination of a page-scoped @DataModel,
@DataModelSelection, and a JSF command component is a grand slam. However, if you need to allow the
user to open the data model selection link in a new tab or window, then you may need to take the solution a
step further. Currently, if the user right-clicks on the golfer name a tries to open the profile in a new tab or
window, the home page will load rather than the profile page. To allow them to achieve the desired result, the
data model selection needs to be passed along with the URL. JSF does not support this feature, but Seam does.
Data model selection with Seam link components
The link components in the Seam UI component library, <s:link> and <s:button> support data model
selections, despite the fact that they do not submit a form or restore the JSF component tree like the JSF
command components. In previous chapters, you learned that it is possible to use the Seam link components to
execute actions and perform navigation. They can also emulate a data model selection by using two special
URL parameters, dataModelSelection and actionMethod, which are processed by the Seam phase
listener and used to inject the data from the selected row into a @DataModelSelection or
@DataModelSelectionIndex property. An example is provided further down.
The Seam link tags aren't quite a drop in replacement for the JSF command components. As I just
mentioned, the Seam link components do not restore the JSF component tree. That means that page-scoped
data will not be propagated with the request, since page-scoped variables are stored in the JSF UI component
tree. Thus, in the example, the newGolfers data model is be left behind. To fix the problem, you need to
store your data model in a longer lived scope.
There are two viable scopes that you can use as an alternative to the page scope, session and conversation.
Since we haven't explored conversations—and by that I mean long-running conversations—we will go with
the session scope for now. You need to place the newGolfers property into the session scope to allow
formless data model selection using either of the two Seam UI command components, <s:link> or
<s:button>. You have the option of either changing the scope of the @DataModel annotation to
ScopeType.SESSION or removing the scope attribute from the @DataModel and adding
@Scope(ScopeType.SESSION) to the ProfileAction class. In the later case, the scope of the
@DataModel is inherited from the containing component. To maximize readability, the scope attribute on
the @DataModel is changed, highlighted in bold:
Licensed to Jaroslaw Gilewski <[email protected]>
@Name("profileAction")
public class ProfileAction {
@DataModel(scope = ScopeType.SESSION)
protected List<Golfer> newGolfers;
// ...
}
You can follow this change with a switch from the <h:commandLink> tag to <s:link>:
<s:link value="#{_golfer.name}" action="#{profileAction.view}"/>
An example of a URL generated by this component tag is as follows:
/open18/home.seam?dataModelSelection=_golfer:newGolfers[0]
[CA]&actionMethod=home.xhtml:profileAction.view
The URL indicates that the first entry in the newGolfers collection is to be used as the data model
selection and that the method view() on the component named profileAction is to be executed. The user
is then directed to the view ID returned by the view() method. If the view() were to return a outcome value
rather than a view ID, then the navigation rules would be consulted to determine the next view.
These extra steps to support the Seam UI command components may seem like too much work, in which
case you may just want to stick with the JSF command components. You may want to consider ways to select
a row in a @DataModel context variable without the use of @DataModelSelection.
Other approaches to data model selection
As an alternative to using the @DataModelSelection annotation, you can pass the context variable of the
current row to the action handler using a parameterized method expression from the JBoss EL. Note that in
these examples assume the use of a page-scoped @DataModel. In the example, you can pass the _golfer
context variable as an argument to the view() action handler:
<h:commandLink value="#{_golfer.name}"
action="#{profileAction.view(_golfer)}"/>
The signature of the action handler method would change accordingly:
public String view(Golfer golfer) {
this.selectedGolfer = golfer;
return "/profile.xhtml";
}
The benefit of using the parameterized EL is that it allows you to pass a property of the row data rather
than the row data itself. For instance, you could pass the golfer's username instead:
<h:commandLink value="#{_golfer.name}"
action="#{profileAction.view(_golfer.username)}"/>
Licensed to Jaroslaw Gilewski <[email protected]>
To go in a completely different direction, you could use a RESTful URL to select the golfer. In this case,
you are using the @DataModel just for rendering purposes and instead passing the information needed to
retrieve the golfer through the URL.
<s:link value="#{_golfer.name}" view="/profile.xhtml">
<f:param name="golferId" value="#{_golfer.id}"/>
</s:link>
In this case, you don't even need to place the @DataModel in the page scope because you are done with it
after the list is rendered. If your goal is to produce RESTful URLs that can be bookmarked, it is best to steer
clear of the @DataModelSelection entirely. If your application uses a logical page flow that cannot be
broken, then you really cannot beat the page-scoped @DataModel, @DataModelSelection, and JSF
command component combination.
Bijection can be very powerful and convenient, but if applied at the wrong time, it can really throw a
wrench in things. In the next section, you will learn how to exert some control over when bijection is used and
when Seam applies this reservation automatically.
6.4 Bypassing bijection
Bijection is one of the most powerful and compelling features of Seam. But, with great power often comes
great confusion. In this section, we look at situations in which bijection is not used and also how you are able
to disable it when it is getting in your way. It's just as valuable to know when bijection is not applied as to
know when it is. We start by considering when Seam does not wrap bijection around a method call and then
look at how to disable bijection outright.
6.4.1 Internal method calls
As established earlier, bijection is implemented as an AOP method interceptor. Method interceptors are
applied around method calls that are invoked on proxy objects. When you ask the Seam container for a
component instance—perhaps through an EL expression or an injection, what you actually get back is a proxy
of the instance. Therefore, any method call invoked on that proxy is going to pass through the method
interceptors and, in turn, trigger bijection.
However, method interceptors are blind to what goes on within the component method. To the
interceptors, the raw method call (i.e. invocation.proceed()) is a black box. Once inside of the
intercepted method, you are dealing with the raw instance of the component when you refer to the implicit
variable this. What that means is that same class method calls are not going to be observed by the method
interceptors and, therefore, bijection is not wrapped around them.
If you have a strong grasp of how AOP method interceptors work, then this fact should come as no
surprise to you. However, for those with less exposure to method interceptors, it may be hard to grasp when
bijection is occurring given its mystical nature. In simpler terms, just remember that when a component is
asked to do work, by way of a method call, bijection is going to occur before and after that call. However, any
method that the component invokes on itself—an internal method call—is not going to involve the bijection
process. This distinction is also true of any interceptor-provided functionality, transactions being another
example.
Let's return to the RegisterAction component to see an example of an internal method call. We want
to verify that the username chosen by the new golfer is available. The method
isUsernameAvailable(String) on RegisterAction performs the check and emits an error message
if the username is already in use. The modified RegisterAction component is shown in listing 6.3 in
Licensed to Jaroslaw Gilewski <[email protected]>
abbreviated form. Note that the internal call to isUsernameAvailable(String) method does not trigger
bijection.
Listing 6.3 The RegisterAction component verifying that the username is available
@Name("registerAction")
public class RegisterAction {
@In protected EntityManager entityManager;
@In protected FacesMessages facesMessages;
@In protected Golfer newGolfer;
// ...
public String register() {
// ...
if (!isUsernameAvailable(newGolfer.getUsername())) {
facesMessages.addToControl("username",
Username is already taken");
}
//1
//2
// ...
}
public boolean isUsernameAvailable(String username) {
return entityManager.createQuery(
"from Golfer where username = :username")
.setParameter("username", username)
.getResultList().size() == 0;
}
}
<Annotation #1> Internal method call, does not trigger bijection
<Annotation #2> Message appears next to username field
You may wonder why it matters that bijection is skipped in these cases. The reason is that you want the
state of component instance to remain stable and consistent while the method call is proceeding. Reapplying
bijection could really fowl things up. You can make internal method calls to your heart's content without
worrying about bijection stomping on the state of the component instance.
That takes care of internal method calls, but there is another case when the state of the component instance
is sensitive and needs to be preserved (meaning bijection must be suppressed). While the method on the
principal component instance is proceeding, it may delegate work to other objects. If one of those objects gets
a reference to a proxy of the principal component instance and asks it to do work, by way of a method call, you
have effectively reentered the principal component instance. You don't want bijection to be applied during
reentry. You might see this situation if you are implementing a visitor or double dispatch pattern. Let's look at
how Seam detects and deals with these reentrant method calls.
6.4.2 Reentrant method calls
Reentry into a component is a situation that you don't often see in examples, yet it is common enough that you
will likely come across it soon enough. A reentrant method is a sibling to the target method, which gets
invoked through a proxy of the principal component instance by a delegate object while the target method is
still proceeding. The difference between a reentrant method call and an internal method call is that the
reentrant call is made by a collaborator component that obtained a reference to a proxy of the principal
Licensed to Jaroslaw Gilewski <[email protected]>
component instance. If you remember the simple rule of thumb provided earlier, the call to the reentrant
method is passing through a proxy and is thus subject to method interception.
When a method on a component instance is invoked from the outside, Seam sets a flag to indicate that a
method call is in progress. If a component instance is reentered while that call is still in progress, the bijection
interceptor consults the state of this flag and recognizes that bijection has already been applied and is now
being suppressed. It forgoes applying bijection for the reentrant method call. When the outermost method call
returns, outjection and disinjection are applied and the reentrant flag is reset. Method invocations on the proxy
are once again candidates for triggering bijection.
To modify the statement made earlier explaining bijection, we must now say that bijection occurs before
and after every method call on a Seam component instance, but only around the outermost invocation of the
component instance.
The motivation for skipping bijection on reentrant method calls is twofold. On the one hand, it reduces
overhead since there is no reason for applying injections again if they have already happened. But, there is a
bigger problem at hand with allowing bijection to proceed. Once the reentrant method call is complete,
disinjection wipes away the injections that were applied. Disinjection drastically affects the state of the
component instance and almost always proves disruptive to the outer method call.
Let's consider an example from the registration component. Each time a golfer registers, there are a
handful of validations that need to take place. To cleanup the register method, let's introduce a
GolferValidator component to which these validations can be delegated. The GolferValidator does
not host any business logic, it is just coordinator. The validation checks still reside on the business component,
RegisterAction. That means we will see some reentrant method calls during a call to the register()
method. Listing 6.4 shows both the refactored RegisterAction component and the new
GolferValidator validation component.
Listing 6.4 The RegisterAction and GolferValidator components demonstrating a reentrant method call
package org.open18.action;
import
import
import
import
import
import
org.jboss.seam.annotations.*;
org.jboss.seam.faces.FacesMessages;
org.open18.auth.*;
org.open18.model.Golfer;
org.open18.validation.GolferValidator;
javax.persistence.EntityManager;
@Name("registerAction")
public class RegisterAction {
@In protected FacesMessages facesMessages;
@In protected EntityManager entityManager;
@In protected PasswordManager passwordManager;
@In protected Golfer newGolfer;
@In protected PasswordBean passwordBean;
@In protected GolferValidator golferValidator;
public String register() {
Licensed to Jaroslaw Gilewski <[email protected]>
if (!golferValidator.validate(newGolfer, passwordBean)) {
log.info("Invalid registration request");
facesMessages.addToControls(
golferValidator.getInvalidValues());
return null;
}
//1
//5
newGolfer.setPasswordHash(passwordManager
.hash(passwordBean.getPassword()));
entityManager.persist(newGolfer);
facesMessages
.add("Welcome to the community, #{newGolfer.name}!");
return "/home.xhtml";
//4
}
public boolean isUsernameAvailable(String username) {
return entityManager.createQuery(
"from Golfer where username = :username")
.setParameter("username", username)
.getResultList().size() == 0;
}
//2
public boolean isEmailRegistered(String email) {
return entityManager.createQuery(
"from Golfer where emailAddress = :email")
.setParameter("email", email)
.getResultList().size() > 0;
}
//2
// proStatusTypes and specialtyTypes properties
}
package org.open18.validation;
import
import
import
import
import
import
org.open18.action.*;
java.util.*;
org.hibernate.validator.InvalidValue;
org.jboss.seam.annotations.*;
org.open18.auth.PasswordBean;
org.open18.model.Golfer;
@Name("golferValidator")
@AutoCreate
public class GolferValidator {
@In private RegisterAction registerAction;
List<InvalidValue> invalidValues = new ArrayList<InvalidValue>();
public boolean validate(Golfer newGolfer,
PasswordBean passwordBean) {
if (!passwordBean.verify()) {
Licensed to Jaroslaw Gilewski <[email protected]>
addInvalidValue("confirm", PasswordBean.class,
"Confirmation password does not match");
}
if (!registerAction.isUsernameAvailable(
newGolfer.getUsername())) {
addInvalidValue("username", Golfer.class,
"Username is already taken");
}
//3
if (registerAction.isEmailRegistered(
newGolfer.getEmailAddress())) {
addInvalidValue("emailAddress", Golfer.class,
"E-mail address is already registered");
}
//3
return !hasInvalidValues();
}
public InvalidValue[] getInvalidValues() {
return invalidValues.toArray(
new InvalidValue[invalidValues.size()]);
}
public boolean hasInvalidValues() {
return invalidValues.size() > 0;
}
public void reset() {
invalidValues = new ArrayList<InvalidValue>();
}
protected void addInvalidValue(
String property, Class beanClass, String message) {
invalidValues.add(new InvalidValue(
message, beanClass, property, null, null));
}
}
Cueballs in code and text
The RegisterAction component delegates the validation to GolferValidator at [#1]. If either the
isUsernameAvailable(String) or isEmailRegistered(String) methods [#2] on the
RegisterAction triggered bijection when called by the GolferValidator [#3], it would lead to a
NullPointerException when the entityManager is called [#4] to persist the newGolfer since
disinjection would have reset the entityManager property to null. Fortunately, the bijection interceptor
prevents this situation from happening by detecting the reentrant method call. The Hibernate Validator class
InvalidValue is used to accumulate the validation errors, which are then bound to the appropriate form
fields using the FacesMessages component [#5].
Figure 6.9 diagrams the sequence of events that occurs when the register() method on
RegisterAction is called.
Licensed to Jaroslaw Gilewski <[email protected]>
Figure 6.9 An example of two reentrant method calls on the RegisterAction component made by the GolferValidator collaborator
component.
The log messages in the trace level verify that Seam acknowledges the reentrant call:
TRACE [org.jboss.seam.intercept.RootInterceptor]
golferValidator.validate
TRACE [org.jboss.seam.intercept.RootInterceptor]
registerAction.isUsernameAvailable
TRACE [org.jboss.seam.core.BijectionInterceptor]
to component: registerAction
TRACE [org.jboss.seam.intercept.RootInterceptor]
registerAction.isEmailRegistered
TRACE [org.jboss.seam.core.BijectionInterceptor]
to component: registerAction
intercepted:
intercepted:
reentrant call
intercepted:
reentrant call
This discussion of reentrant method calls is technical, but it is important to understand why Seam forgos
the use of bijection even this case where a method is being invoked on a proxy of the component instance.
With the exception of JPA entity classes, all Seam components are subject to bijection. However, there are
certain isolated components that have no use for bijection. In these cases, you likely want to forgo bijection
outright. You can tell Seam not to intercept method calls on a component using a special annotation.
6.4.3 Disabling bijection by disabling interceptors
Since bijection is provided by an AOP method interceptor, you can avert bijection by disabling the interceptors
on a single method or on the entire component. You instruct Seam not to apply interceptors by adding the
@BypassInterceptors annotation, summarized in table 6.7, at the class or method level. The method-level
annotation is a good way to optimize one area of the code without cutting out the benefits of interceptors from
the component as a whole.
Table 6.7 The @BypassInterceptors annotation. The @BypassInterceptors annotation disables the AOP interceptors, including
bijection, for either a component or a single method.
Name:
BypassInterceptors
Purpose:
If applied to a method, disables all the interceptors that are wrapped around a single method call. If applied to a
component, has the same effect as applying it to every method on the component.
Target:
TYPE (class), METHOD
Licensed to Jaroslaw Gilewski <[email protected]>
Unfortunately, if you disable interceptors, you end up stripping away all the other functionality provided
by interceptors, not only bijection. Such features include declarative conversation controls, transaction
management, and event handling, to name a few. Just be aware that by disabling interceptors, you may get
more than you bargained for.
NOTE
Adding @BypassInterceptors to a component does not disable life cycle methods. However, these
methods are not privy to interceptor-based functionality such as bijection. Life cycle methods are those
annotated with @Create, @PostConstruct, @Destroy or @PreDestroy and are covered in section
4.5.5 of chapter 4.
What's the motivation for disabling interceptors? By no means are interceptors punishing to the runtime
execution speed of your code, but they do consume extra cycles. If you know that you do not need to
functionality provided by interceptors, then you might as well disable them.
For instance, the PasswordBean component has no use for interceptors as it merely holds data and
validates its internal state. Thus, it is safe to disable interceptors for that component:
@Name("passwordBean")
@BypassInterceptors
public class PasswordBean {
// ...
}
Another compelling place to use @BypassInterceptors is on basic object methods, such as
toString():
@BypassInterceptors
public String toString() {
return new StringBuffer()
.append(getClass().getName()).append("[")
.append("password=").append(password).append(",")
.append("confirm=").append(confirm)
.append("]).toString();
}
The toString() method, as well as equals() and hashCode(), does not benefit from the
functionality provided by the interceptors. If anything, the interceptors just get in the way. For instance,
required values enforced by @In and @Out can prevent the toString() method from completing
successfully, which can mask real problems when trying to debug.
While interceptors can sometimes be overzealous, the functionality they provide is essential for
decoupling business logic from boilerplate lookup code. One such area where interceptors help to simplify
code and cut out tight coupling is when a component wants to send a notification to other components so they
have a chance to act.
6.5 Component events
Dynamic injection helps to decouple components from the container because an explicit lookup for the context
variable is not required. However, the components still depend on each other because they are wired directly
together. This arrangement works well when the components are all working to accomplish a common goal, as
is the case with the registration example so far. However, in cases where tangential logic must be performed,
Licensed to Jaroslaw Gilewski <[email protected]>
perhaps even the spawning of an asynchronous operation, events are useful to separate the concerns and also to
make testing of the components in isolation easier.
Events offer a way for components to pass messages to one another. Events can also be generated during
any page-related activity. These events are passed through the Seam container, which acts as the messaging
mediator. In fact, Seam's event support works much like a messaging service such as JMS. There are both
producers and consumers. When an event is raised, it is posted to the Seam container. Seam then looks for
registered observers for that event and notifies them by executing the registered methods.
6.5.1 Raising an event from a component
Events can be raised from within a Seam component either using the Seam Events API—the built-in Events
component—or using an annotation. The Events API offers the most flexibility, but it does tie your code to
that API. The annotation, @RaiseEvent, on the other hand allow you to control event creation declaratively.
Using the Events API
The Events API supports passing an arbitrary number of parameters to the observer when an event is raised.
Passing data gives the observer a chance to access the data that is available within the scope of where the event
was raised without having to be present.
The default behavior is to notify observers of the event immediately. It is also possible to schedule events.
Seam supports both synchronous and asynchronous scheduling. In the synchronous case, you can schedule that
an event be fired when a transaction completes or only when it completes successfully. The asynchronous
scheduler allows you to supply either an EJB 3 Timer schedule or a Quartz Cron schedule, depending on
which asynchronous dispatcher you are using in your application.
Let's consider an example. The marketing department has requested that we gather statistics when
members register and when they cancel the registration form so they can determine the effectiveness of the
registration process. The requests from marketing are ever changing. Rather than disrupt our nicely tested
registration logic, we want to put this extra logic in an observer. That way, when the next feature request is
handed down from marketing, we can just take on another observer, once again avoiding disrupting working
code.
The register() method of the RegisterAction component has been modified to raise the
golferRegistered event once the new golfer has been persisted to the database:
public String register() {
// ....
entityManager.persist(newGolfer);
Events.instance().raiseEvent("golferRegistered");
// ....
return "/home.xhtml";
}
If we wanted to ensure that whatever logic performed by the collateral code operates in a separate
transaction so as not to jeopardize the transaction that is used to persist the new golfer, we can schedule this
event to be fired after successful completion of the current transaction:
public String register() {
@In Events events;
// ....
entityManager.persist(newGolfer);
Licensed to Jaroslaw Gilewski <[email protected]>
events.raiseTransactionSuccessEvent("golferRegistered");
// ...
return "/home.xhtml";
}
NOTE
Notice that we have injected the Events API into the RegisterAction component using @In in this
case. Most built-in Seam components allow you to either use the static instance() method to get an
instance of the component or you have to option of injecting it into your component using the associated
context variable.
You could also pass the newGolfer instance to the observer through the event:
public String register() {
// ...
entityManager.persist(newGolfer);
events.raiseTransactionSuccessEvent(
"golferRegistered", newGolfer);
// ...
return "/home.xhtml";
}
Of course, the observer could get a handle on the newGolfer using bijection, but parameter offers more
clarity, prevents the observer from having to rely on the Seam container to access the data making it more
testable, and it is easier for a fresh set of eyes to understand what is going on with the code—self-documenting
code is always a good thing.
We will get to observers in a moment. First, let's weigh using the @RaiseEvent annotation as an
alternative.
Using @RaiseEvent
The @RaiseEvent annotation, summarized in table 6.8, offers the most convenient way to raise an event
from a component, but it is more limited how it can be triggered. Observers are notified of an event raised by
the @RaiseEvent upon successful completion of the component method on which this annotation resides.
Table 6.8 The @RaiseEvent raises component events
Name:
RaiseEvent
Purpose:
Raises a component event, which is passed on to registered observers, upon successful completion of the component
method.
Target:
METHOD
Attribute
Type
Function
value
String[]
One or more event names to be raised. If a value is not provided, the name of the method
is used as the event name.
If you recall, successful completion means the method is either void or returns a non-null String value and
no exceptions were thrown. The events are fired after successful completion of the method because, like
bijection, it is implemented as an interceptor. What is convenient about this interceptor is that it wraps the
transaction interceptor, so if your transaction is configured to complete (commit or rollback) after successful
completion of the method, the event will be inherently raised after the transaction completes—though you still
don't have the granularity of distinguishing between a commit and a rollback.
Licensed to Jaroslaw Gilewski <[email protected]>
With that, let's change the previous example so that the raising of the golferRegistered event is
declared as metadata:
@RaiseEvent("golferRegistered")
public String register() {
// ....
entityManager.persist(newGolfer);
// ....
return "/home.xhtml";
}
The @RaiseEvent annotation can be used to raise an arbitrary number of events. However, it cannot be
used to pass arguments to the observer (at the time of writing). Thus, the @RaiseEvent is good when you
just want to notify observers that something has happened. If information is critical to understanding the event,
you likely want to use the Events API.
With all of these events floating around out there, let's see how to observe them.
6.5.2 Defining an event @Observer
Events are observed by component methods or EL method expressions that have been registered as observers.
An observer can be registered using the @Observer annotation or by attaching an event to an EL method
expression in the components descriptor. Let's explore the @Observer annotation first.
The @Observer annotation, summarized in table 6.9, can be added to a method of any Seam component.
It can observe one or more events by name, specified in the value attribute. You also have the option of
whether to have the owning component created if it does not exist at the time the event is raised by supplying
the create attribute. This flag is equivalent to the auto create functionality on components.
Table 6.9 The @Observer registers a method to observe events
Name:
Observer
Purpose:
Registers a component methods to observe events by name. The method can accept arguments if the producer
passes arguments along with the event.
Target:
METHOD
Attribute
Type
Function
value
String[]
One or more event names to be observed.
create
boolean
Should the component be created if it does not exist at the time the event is raised.
Defaults to false.
While it is possible to capture multiple events in the same method, you have to remember that it is possible
for an event to pass arguments, thus dictating the signature of the observer. You cannot use the same method to
observe multiple events if the type and amount of arguments passed by the events vary. You would be much
better off refactoring the observation logic into multiple methods in this case.
Let's observe the golferRegistered event and record it for the marketing team's statistics. Instead of
going through the exercise of setting up an entity and database table, we will just stub out the implementation
by logging it.
@Name("registrationDataCollector")
@Scope(ScopeType.APPLICATION)
public class RegistrationDataCollector() {
@Logger Log log;
Licensed to Jaroslaw Gilewski <[email protected]>
int registrationCount = 0;
@Observer("golferRegistered")
synchronized public void recordRegistration(Golfer golfer) {
registrationCount++;
log.info("Golfer registered – username: " +
golfer.getUsername());
log.info(registrationCount +
" golfers have registered since the last restart");
}
}
Since the recordRegistration() method is a component method, it will trigger bijection, which can
be useful for injecting the EntityManager when you go to build out the real implementation. You can also
register the observer using the component descriptor. The later is useful if you cannot add the @Observer
annotation to the class—perhaps because it cannot be modified—or to have non-Seam components observe
events. To do so, you would add the following declaration in the component descriptor:
<event type="golferRegistered">
<action
execute="#{registrationDataCollector.recordRegistration(newGolfer)}"/>
</event>
You can specify any number of actions to be executed for a single event. Notice that in this case, we are
taking advantage of the JBoss EL to pass an argument to the method expression. At the time the event is
raised, the newGolfer context variable is still in scope, so it is possible to reference it for the purpose of
passing it to the observer.
Let's look at another way that events triggered, by tying them to page events.
6.5.3 Raising events on page transitions
Events can be declared in the pages descriptor, allowing them to be tied to any page-related activity such as a
page render or navigation transition. The <raise-event> tag can be nested in any of the following nodes:
<page>, <navigation>, or <rule>. Like the @RaiseEvent annotation, it is not possible to pass
parameters using this approach.
Let's implement the other part of the marketing departments request to capture cases when the registration
form is canceled. In this case, we will trigger an event on a navigation rule:
<page view-id="/register.xhtml">
<navigation>
<rule if-outcome="cancel">
<raise-event type="registrationCanceled"/>
<redirect view-id="/home.xhtml"/>
</rule>
</navigation>
</page>
You will also want to ensure that the cancel button on the registration page, /register.xhtml, is changed to
send the cancel outcome:
Licensed to Jaroslaw Gilewski <[email protected]>
<h:commandLink id="cancel" value="Cancel"
action="cancel" immediate="true"/>
Events are also useful notifying observers when a page is going to be rendered.
Page events in place of page actions
In chapter 3, you used a page action to preload data before rendering the page. Since the goal is to observe and
perform logic for the render event, it would be more appropriate—and self-documenting—to raise an event
and have a component method observe it. Let's use this approach to preload the list of facilities on the facility
directory page:
<page view-id="/FacilityList.xhtml">
<raise-event type="facilityList.preRender"/>
...
</page>
Next, the @Observer annotation is applied to the preloadFacilities() method to watch for this
event:
@Observer("facilityList.preRender")
public void preloadFacilities() {
getResultList();
}
Even before you start producing your own events, you have a whole slew of built-in events that are raised
by the Seam container. You may find that your entry into the world of events comes from the side of observing
these native events.
6.5.4 Built-in events
Events are one of the richest parts of Seam. If you had a nickel for every event that Seam raised, you would
surely be a rich developer. Listing every event that Seam raises would eat a gratuitous amount of space. I
encourage you to consult the Seam reference documentation for a comprehensive list. I will, however, survey
the types of events you can expect. Seam raises events for the following actions:
z
a context variable changes (added, removed)
z
component life cycle events (created, destroyed)
z
scope events (created, destroyed)
z
authentication events
z
transaction events (before complete, commit, rollback)
z
exception events (handled, not-handled)
z
conversation, page flow, business process, and task boundaries
z
JSF phase transitions (before, after)
z
JSF validation failed
z
setting change (theme, time zone, locale)
z
Seam container initialized
Licensed to Jaroslaw Gilewski <[email protected]>
In certain cases, such as when a context variable is modified, the event raised includes the name of the
subject. For instance, when the context variable newGolfers is added to the page scope, the name of the
events that are raised are:
z
org.jboss.seam.preSetVariable.newGolfers
z
org.jboss.seam.postSetVariable.newGolfers
In some cases, the subject is also passed as an argument to the event, such as when a component instance
is
created.
When
the
profileAction
component
is
created,
the
event
org.jboss.seam.postCreate.profileAction is raised and the profileAction instance is sent as
an argument. Observing this event allows you to place your post-create logic for a component on a different
component entirely:
@Name("profileActionHelper")
public class ProfileActionHelper() {
@Observer("org.jboss.seam.postCreate.profileAction")
public void onCreate(ProfileAction profileAction) {
// ...
}
}
In chapter 11 you learn how events help minimize the disruption when requiring the user to login. One
such event is used to capture the current view before sending the guest user to the login page and another event
is used to return the newly authenticated user to the previous view after a successful login. Another example of
how to use the authentication events is to setup menu system entries. Instead of putting such logic in the
authentication routine, you can decouple it by using a method on another component that observes the post
authentication event.
6.6 Custom method interceptors
As you have learned, Seam makes liberal use of interceptors to weave functionality into its components. The
built-in interceptors control nearly every aspect of Seam that pertains to components and state management.
It's also possible to define your own interceptors to address your own cross-cutting concerns. In this section
you learn about Seam's interceptor support and how it correlates with the interceptors available to EJB 3
components.
6.6.1 Two sides to the interceptor coin
Interceptors in Seam are defined using the Seam @Interceptor annotation. This annotation can be applied
to both JavaBean components and EJB session bean components. To apply an interceptor to an EJB
component, you can always just use the standard interceptor annotations in the Java EE API. However, you
lose out on two extensions that Seam gives you, client-side interception and stereotypes.
When a Seam interceptor is registered on an EJB component, the corresponding interceptor can be slated
to be applied on the client-side or the server-side of the method call. If you aren't very familiar with EJB
components, you may be lost with regard to that distinction. Recall that both Seam components and EJB
session bean components support method interceptors. Seam can apply an interceptor to its own EJB proxy
object or register the interceptor in the EJB 3 component's interceptor stack.
Thus, you have two opportunities to trap and perform logic around a method invocation. The method call
to the Seam component proxy object can be intercepted, which is a client-side interceptor, or the interception
can be delayed and activated as part of the EJB interceptor stack, instrumented around the EJB component
Licensed to Jaroslaw Gilewski <[email protected]>
proxy object, which is a server-side interceptor. Note that this distinction only applies to Seam EJB
components. When a Seam interceptor is applied to a JavaBean, it is just a normal interceptor. The following
discussion focuses on the Seam EJB component.
Choosing an interceptor type
Which interceptor type you choose depends on the call stack in which you want your interceptor to execute.
You may want to short-circuit the call to the EJB component or perform work around the entire method call,
inclusive of its interceptor stack. In that case, you need to use a client-side interceptor
(InterceptorType.CLIENT). The client-side interceptor runs on the client proxy, before the actual session
bean is even consulted. On the other hand, if you want the interceptor to execute within the EJB interceptor
call stack, duking it out with the other interceptors that are installed on the session bean, then a server-side
interceptor is appropriate (InterceptorType.SERVER). Interceptors that are able to distinguish between
client-side and server-side proxies must be declared using the Seam @Interceptors annotation.
INFO
A class does not need to have the Seam @Interceptor annotation to act as an interceptor. Recall that
EJB 3 interceptors are only required to define a single @AroundInvoke method to be considered an
interceptor. The @Interceptor annotation is a Seam extension that supports the distinction between
client and server-side interception. If the class lacks this annotation, it defaults to the server-side interceptor
type.
The @Interceptors annotation is a meta-annotation, meaning it must be applied to an annotation, not
directly to a class. That annotation must then be used on the component class in order for the interceptors to be
registered. This layer of indirection is how Seam is able to gain control over the interceptor chain before the
EJB container takes over, assuming you are dealing with an EJB component. Of course, if the interceptors are
applied to a JavaBean, then Seam has control all the way. If an interceptor is defined directly on the component
class (or method) using the @Interceptors annotation from the Java EE API, then the EJB container takes
over control of the interceptor chain from the start and Seam has no say in the matter.
NOTE
Seam supports registering interceptors on JavaBean components. In that case, you can use the
@AroundInvoke annotation from the Seam API to declare the interceptor method when developing
outside of a Java EE environment.
Seam registers itself as an EJB interceptor at the top of the chain, dealing with the client side interceptors
and then delegating to the EJB interceptor chain. The execution order of interceptors around the invocation of
a session bean component is shown in figure 6.10.
Licensed to Jaroslaw Gilewski <[email protected]>
Figure 6.10 The call stack for a Seam session bean component, including client-side, Seam server-side and EJB interceptors.
As mentioned, the Seam interceptor is registered as an EJB interceptor. The EJB container triggers this
interceptor, which then delegates to the server-side Seam interceptors. Although it is possible for Seam
interceptors to define execution order using the around and within attributes on the @Interceptor
annotation, these interceptors are not intertwined with the interceptors declared on the EJB component class.
Instead, they all descend from the top level Seam interceptor, which acts as an interceptor controller.
Since the default interceptor type is InterceptorType.SERVER, the decision you need to make when
defining an interceptor for a session bean component is whether a switch to InterceptorType.CLIENT is
warranted. Let's look at an example when a client-side interceptor would be used.
6.6.2 Trapping calls with a client-side interceptor
A good use case for a client-side interceptor is a flood submit interceptor. Those spammer types are nasty little
buggers. They like to abuse the forms on your site, putting the database into overdrive. For the sake of
argument, let's assume you had to write an interceptor in a pinch because you were a victim of denial-ofservice attacks. Not only do you want to save the database, but you also want to relieve the EJB container as
well. Method calls are throttled on the client-side before being passed on to the EJB component. The
FloodInterceptor, shown in listing 6.9, traps calls to any method annotated with @Throttle to ensure
that there is at least one minute between the invocation of throttled methods. @Throttle is a custom
annotation. Please note that the implementation is over-simplified for the purpose of demonstration.
Listing 6.9 A flood interceptor that traps repetitive calls to an action listener
@Interceptor(stateless = true, type = InterceptorType.CLIENT)
public class FloodInterceptor {
@In(scope = ScopeType.SESSION)
@Out(scope = ScopeType.SESSION)
private Long timeOfLastThrottledMethodCall;
@AroundInvoke
public Object aroundInvoke(InvocationContext invocation)
throws Exception {
Long now = System.currentTimeMillis();
if (!isThrottled(invocation.getMethod())) {
if (timeOfLastThrottledMethodCall != null &&
(now - timeOfLastThrottledMethodCall < 60000)) {
throw new RuntimeException(
Licensed to Jaroslaw Gilewski <[email protected]>
"Actions must be at least 1 minute apart");
}
timeOfLastThrottledMethodCall = now;
}
return invocation.proceed();
}
private boolean isThrottled(Method method) {
return method.isAnnotationPresent(Throttle.class);
}
}
This interceptor executes in the interceptor stack on the Seam component proxy object. It maintains a
timeOfLastThrottledMethodCall attribute in the user's HTTP session to track the last time a throttled
method was triggered by that user. If less than a minute has elapsed since the last invocation, then the call to
the method will be denied. Although not shown here, it might be a good idea to report the incident if it happens
recurrently. If the time elapse is sufficient, the method invocation proceeds to the EJB component. Note that
the interceptor is defined to be stateless, which means that the same instance of this interceptor is used to
handle all session bean instances created from a single Seam component, a nice optimization trick if the
interceptor does not need to hold state.
To apply this interceptor to a component, it must be defined on an annotation interface:
@Target(TYPE)
@Retention(RUNTIME)
@Interceptors(FloodInterceptor.class)
public @interface PreventFloodAttack {}
Methods annotated with @Throttle on a component class that declare the @PreventFloodAttack
annotation are safe from flood attacks. The @PreventFloodAttack is known as a stereotype since it applies
behavior to component declaratively without exposing the mechanism by which that behavior is applied. You
see this pattern used heavily in JSR 299 (WebBeans). In that regard, using the Seam interceptor mechanism is
not only about having the interceptor applied on the client-side, but also to create stereotypes. Auditing is
another example of when a stereotype might be appropriate.
You can use a client-side interceptor if you want to retry a call to an EJB component (or other remote
service). If a RemoteException is thrown, you can reattempt the call a fixed number of times before giving
up. Some other examples of client-side interceptors that Seam uses are:
z
asynchronous interceptor
z
security interceptor
z
synchronization interceptor
z
stateful session bean remove interceptor
That should give you a solid foundation for when to use the client-side interceptor. When the interceptor's
role is to apply behavior to the method invocation, the server-side interceptor is sufficient. For example, the
bijection interceptor executes server-side. In the diagram shown in figure 6.9, bijection is handled by one of
the Seam-controlled server-side interceptors.
Just as events and interceptors offer a way to loosely couple sequential logic and apply cross-cutting logic,
respectively, factory and manager components offer a way to loosely couple the creation of context variables
Licensed to Jaroslaw Gilewski <[email protected]>
and interject values into the point in which they are reference. Let's explore how these special components help
to build on Seam's inversion of control capabilities.
6.7 Factory and manager components
Sometimes, there is more to a context variable than first meets the eye. Up to this point, you have learned that
a context variable can be used to create an instance of a component. After the instance is resolved, you still
have to operate on it to get to the real data. Factory and manager components allow you to use context
variables to produce the underlying data, rather than a component instance. When the context variable is
requested, a data provider is invoked—typically a method on a component somewhere—and the return value is
bound to the context variable. Thus, the value of a context variables bound to factory or manager components
is said to be calculated. You often recognize a factory component as one that has a @Factory method and a
manager component by one that has an @Unwrap method.
What makes factory components and manager components interesting is that although their values are
calculated, they can be used just like regular components. That means they can be injected using the @In
annotation, used in EL bindings, and trigger bijection. Manager components can even listen for events. Let's
begin by exploring the factory component.
6.7.1 A context variable @Factory
The factory component is a bit of a misnomer because it isn't technically a component at all. Rather, it's just a
data provider—a component method or EL expression—that can bind a value to a context variable upon
request. Factory components are useful for creating context variables lazily and for triggering bijection at
arbitrary places in the code, such as from within a JSF view.
Why factories are needed
Before we dive into factories, I want to clarify their benefit. After all, you may wonder what is wrong with just
creating a JavaBean-style "getter" method on a component to retrieve data (such as the list of new golfers).
What you have to understand is that the EL resolver is very persistent about resolving value expressions. If you
put logic in a "getter" method, it may get called an inordinate amount of times in one request. That's just part of
how JSF and the EL works.
There is no harm in using a getter method in a value expression as long as the method simply returns the
internal state of an object. However, if that method performs logic, especially logic that accesses the database,
this can result in a significant performance problem. A factory component allows you to calculate the value
once and bind the result to a context variable. Subsequent requests for that context variable use the previously
calculated value rather than running the logic again. This is excellent news for JSF views, as there are a lot of
places where you need to repeatedly access a calculated value to perform such things as conditional rendering
(by checking whether a collection is empty, perhaps). Be wary of putting heavyweight logic in getter methods
and prefer the use of factories. The performance conscious folks will be thankful to you.
That explanation should get you plenty excited about learning more about factories. Let's see what types of
data providers can serve as factories and then dive into the factory process.
Factory data providers
As discussed earlier, when a context variable is requested, Seam tries to locate it in one of the available
contexts. When that search turns up empty, and the lookup is operating with the create option, Seam turns to
initializing a value. I have held back some of the details of how this value is generated. Before consulting a
component definition to construct an instance, Seam first looks for volunteers to produce a value. These
Licensed to Jaroslaw Gilewski <[email protected]>
volunteers are known as factories in a Seam-based application. A factory can be implemented using one of the
following providers:
z
a Seam component method, marked with the @Factory annotation
z
an EL method expression, configured in the component XML descriptor
z
an EL value expression, configured in the component XML descriptor
The reason these delegates are called factories is because the delegates "manufacture" values for the
context variables they represent. When a factory is executed, one of the providers in the list above is exercised
and the return value is captured for use as an output from this factory. For instance, if the factory is a value
expression, the value expression is resolved and the resulting value is used as the factory product. A factory
data provider supports auto create functionality, as well, which enables it to be exercised even when the
context variable is requested in lookup only mode.
Factories can create any value, not just component instances. When the factory performs its magic, the
caller requesting the context variable is unaware that it was generated "just in time". The work done by the
factory is completely hidden from the consumer of the resulting value. These values can then be injected into
properties of a component, using the @In annotation, or exported back to a Seam context, using the @Out
annotation. In this regard, they are like any other Seam component. Factories even support the auto create flag.
There are of course subtle differences, but they are small enough that it is reasonable to refer to factories as
components. Let's consider what happens when a context variable associated with a factory is requested.
The factory process
The process of resolving a context variable from a factory can get fairly involved. Recall that a factory is
executed when the context variable it represents is absent or null. A factory can work in one of two ways. It
can either:
z
outject the context variable (the provider is a void method and relies on outjection or manual
assignment to produce a value)
z
return a value, which Seam then assigns to the context variable
If the factory produces a null value, Seam will move on, next looking for a component definition that can
be used to create the context variable. The factory process is just a stepping stone in the context variable
creation process.
What makes a factory so interesting, and a tad bit complicated, is that when the delegate of a factory is a
component method, the execution of the factory triggers bijection. Going a step further, if the delegate method
has a void return value, Seam excepts that outjection is going to populate the context variable that the factory
represents. In making this point, I want to raise awareness of that fact that bijection often plays a significant
role in the factory process. In fact, in this configuration, factory components are one way to force bijection to
occur at an arbitrary location in the execution of the application. (You saw earlier that page actions can be used
for this purpose as well).
The factory process is illustrated in figure 6.11. Pay close attention to what is done with the value that is
returned by the factory. If the context variable is set while executing the factory, then the lookup mechanism
just returns that value. This assignment may happen explicitly from within a factory method or as a result of
outjection. If the context variable is still absent after invoking the factory provider, then the lookup mechanism
takes the result of the factory and assigns it to the context variable in the specified scope.
Licensed to Jaroslaw Gilewski <[email protected]>
Figure 6.11 The process used by Seam to lookup a context variable backed by a factory component or expression.
Whew! There sure is a lot going on in the factory process. Don't let it overwhelm you. A factory is only as
complex as you make it. There are many different ways to use a factory, which is why the diagram has so
many steps. The best strategy is to pick the variation that works best—or makes the most sense—to you and
Licensed to Jaroslaw Gilewski <[email protected]>
stick with it. We are going to look at one such example in a moment that exercises one of the most common
uses.
If you only take away three points about a factory, make them these:
z
a factory provider is invoked when the context variable it represents is accessed and the context
variable is missing or its value is null
z
a factory can either assign the context variable itself (manually or as a result of outjection) or return a
value that will be used as the value of the context variable
z
a factory can trigger bijection if the factory provider is a component method (and bijection is not
disabled on that method)
Once you get the factory process under your belt, you will find yourself using factories all the time. In
fact, once you see a factory in action, you should have no trouble understanding it. How about we try an
example?
Initializing context variables on demand
Earlier, I showed you how to expose the collection of the newest golfers in the form of a JSF DataModel
using a page action. Page actions are useful in small quantities. Once the page starts to get reasonably complex,
the number of page actions can balloon out of proportion if used to prepare context variables needed in the
page. A better solution is to allow each variable to be initialized on demand by use a factory.
One way to define a factory is by placing the @Factory annotation, summarized in table 6.10, above a
component method. This method is then called whenever the context variable, whose name is provided in the
value attribute of the annotation, needs to be initialized.
Table 6.10 The @Factory annotation designates a method as a factory for a context variable
Name:
Factory
Purpose:
Marks a method as a factory for a context variable. Used when no value is bound to the associated context variable
name when it is requested.
Target:
METHOD
Attribute
Type
Function
value
String
The name of the context variable that this factory method will be used to initialize.
scope
ScopeType
The scope into which the context variable should be placed. If no scope is specified, then
the scope of the component that holds the factory method is used. The scope is not to be
used when the factory method is void.
autoCreate
boolean
Indicates that this factory should be used to initialize the context variable, even when the
request did not ask for it to be created. Default value is false.
Let's designate the findNewGolfers() method as a factory rather than as a page action. The
@Factory annotation above the findNewGolfers() method in listing 6.5 declares this method as the
factory for the newGolfers context variable. Seam invokes this method whenever the context variable needs
to be initialized. However, since the method's return type is void, Seam expects the context variable to be
populated as a result of outjection.
Listing 6.5 The ProfileAction component using a factory method.
@Name("profileAction")
public class ProfileAction {
protected int newGolferPoolSize = 25;
protected int newGolferDisplaySize = 5;
Licensed to Jaroslaw Gilewski <[email protected]>
@In
protected EntityManager entityManager;
//2
@DataModelSelection
@Out(required=false)
protected Golfer selectedGolfer;
@DataModel(scope = ScopeType.PAGE)
protected List<Golfer> newGolfers;
//5
public String view() {
// selectedGolfer is available
return "profile";
}
@Factory("newGolfers")
public void findNewGolfers() {
newGolfers = entityManager
.createQuery(
"select g from Golfer g order by dateJoined desc")
.setMaxResults(newGolferPoolSize)
.getResultList();
//1
//3
Random rnd = new Random(System.currentTimeMillis());
while (newGolfers.size() > newGolferDisplaySize) {
newGolfers.remove(rnd.nextInt(newGolfers.size()));
}
//4
}
}
The first time the #{newGolfers} value expression is encountered in the JSF view, the newGolfers
context variable is uninitialized. Seam will turn to the @Factory method [#1] to resolve a value. Before the
method proceeds, the EntityManager property [#2] gets injected. The method then uses the
EntityManager to query the database for the newest golfers [#3], shuffles the result, and pares it down to
the configured display size [#4]. Outjection is then applied, which initializes the newGolfers context
variable as a JSF DataModel [#5]. Seam then returns control to the view renderer, which uses the newly
formed collection to render the <rich:dataList>. The @Factory annotation is summarized in table 6.10.
There are some rules which you should be aware of when using a factory method:
z
If a scope is not specified for a factory method that returns a value, then the scope of the owning
component is used.
z
A factory method with a defined scope cannot allow a value with the same name to be exported as a
result of the call.
z
If a factory method does not specify a scope, and both a value is exported and the factory returns a
value, the exported value takes precedence over the value returned by the factory method.
The @Factory annotation on a component method is the only way to designate a factory in Java code. To
use a method binding expression or value binding expression, you must use the components.xml file. The
factory for the new golfers can be alternatively configured using the XML declaration:
<factory name="newGolfers" method="#{profileAction.findNewGolfers}"/>
Licensed to Jaroslaw Gilewski <[email protected]>
But a more common use of <factory> is for creating component aliases.
Factories as aliases
Factories that are scoped to the stateless context and are mapped to an EL value expression are referred to as
aliases. They are nothing more than a shorthand for a more complex name. Since the stateless context does not
retain the value after lookup, aliases are resolved each time they are requested.
TIP
I prefer to scope aliases to a real context to avoid unnecessary, redundant lookups. As long as there is no
chance that the target value will change during the lifetime of the context in which the alias is stored, it's a
safe bet. The event scope is convenient for this purpose.
One typical use of an alias is to provide a shorter name for a fully-qualified context variable name. All of the
built-in Seam components use namespaces, which makes their names very long. For instance, the context
variable
name
bound
to
the
Seam
FacesMessages
component
is
org.jboss.seam.faces.facesMessages. It would be pretty inconvenient to have to type these
whenever you wanted to use it in a value expression or inject it using @In. You can shorten it by defining the
following alias in the component descriptor:
<factory name="facesMessages"
value="#{org.jboss.seam.faces.facesMessages}"
scope="stateless"
auto-create="true"/>
However, there is no reason to set up such an alias for built-in Seam components. This need is so common
that Seam supports a built-in mechanism to define aliases for fully-qualified components. By using the
<import> tag in the component descriptor, it is possible to import an entire namespace of context variables.
The import statement works just like it does in Java.
<import>org.jboss.seam.faces</import>
But again, since using built-in components is so common, Seam automatically imports all built-in
components for you. Thus, you can uses the facesMessages context variable without any additional setup.
I encourage you, though, to use namespaces in your context variable names and then import them using
the <import> tag, especially if you are building reusable Seam libraries. In the Open 18 application, we could
prefix all of the components with org.open18. and them import them using:
<import>org.open18</import>
The <import> tag in the component descriptor applies globally for a project. If you want to just import a
context variable namespace for a particular component or a Java package, you can do so using the @Import
annotation. Let's assume the context variable name of the GolferValidator component is
org.open18.validation.golferValidator. We could import it into the RegisterAction
component using @Import:
@Name("registerAction")
@Import("org.open18.validation")
public class RegisterAction {
// ...
Licensed to Jaroslaw Gilewski <[email protected]>
@In protected GolferValidator golferValidator;
// ...
}
It could be imported for all components in the org.open18.action package by applying it to the
package-info.java file in that package:
@Import("org.open18.validation")
package org.open18.action;
import org.jboss.seam.annotation.Import;
Note that just like in the Java package system, it is possible to refer to a context variable in the same
namespace as the current component by referring to that variable using its unqualified name. If you bound the
RegisterAction component to org.open18.action.registerAction, you could refer to it from
another component bound to a context variable prefixed org.open18.action using the name
registerAction.
Factories are an unbelievably powerful and flexible feature of Seam. They help to close gaps when data is
needed in an arbitrary location, such as a component buried deep inside of a JSF view or as an injected
dependency that needs to perform prerequisite work. Factories allow context variables to be dynamic, boasting
more meaning than what is observed at first glance. There is also another type of component that warrants a
second glance, the manager component.
6.7.2 Components that @Unwrap
The @Unwrap annotation acts like a magician's handkerchief. You see the component go into the hand, but
when the hand opens, you get something entirely different. This trick is called the manager component pattern.
A manager component has exactly one method that is marked with the @Unwrap annotation. When a manager
component's context variable is requested from the Seam container, an instance of the component is located or
created, and then "unwrapped" before it is returned to the caller, thus beginning the handkerchief trick. During
the unwrap stage, the @Unwrap method is executed and the return value of the method is passed on to the
requester in place of the component itself. Indeed, the manager component is a slippery little devil. A summary
of the @Unwrap annotation is shown in table 6.11.
Table 6.11 The @Unwrap annotation used on manager components
Name:
Unwrap
Purpose:
Identifies the method whose return value is used in place of the component instance itself when the context variable
is resolved.
Target:
METHOD
The @Unwrap method differs from the @Factory method in that it is invoked every time the owning
component's context variable is accessed. The reason the @Unwrap method has to be called each time is
because the return value is not bound to the context variable, like with a factory. Instead, the context variable
stores the instance of the manager component. To get to the actual value, the @Unwrap method must be called.
In a sense, the @Unwrap method is said to be stateless because the result is discarded as soon as it is retrieved.
Only the owning component instance is stateful. Obviously, you have to be careful about what you put into the
@Unwrap method since Seam is not shy about executing it for every reference to its context variable.
The manager component is more powerful than the factory component for several reasons.
Licensed to Jaroslaw Gilewski <[email protected]>
z
it is a real component (the factory is only a pseudo component)
z
its properties can be configured just like any other component
z
it can participate in life cycle methods (@Create, @Destroy)
z
the unwrap method is called every time the context variable is used
z
it can hold internal state
z
it can observe events that can trigger updates to its state
As its name suggests, a manager component is good at managing data. The data stands in for the
component when the component is accessed or injected using @In, but otherwise is behaves just like any other
component. You can think of a manager component as a sophisticated alias, where the @Unwrap method is
responsible for looking up the value of the alias. What makes the manager component compelling is that it can
alias an object that is not accessible via a value or method binding expression. One such example from the
Seam API is the manager component that retrieves the JSF FacesContext instance, a static method on the
FacesContext class:
@Name("org.jboss.seam.faces.facesContext")
@Scope(ScopeType.APPLICATION)
public class FacesContext {
@Unwrap
public javax.faces.context.FacesContext getContext() {
return javax.faces.context.FacesContext.getCurrentInstance();
}
}
Another example from the Seam API is a manager component that retrieves the current date:
@Name("org.jboss.seam.framework.currentDate")
@Scope(ScopeType.STATELESS)
public class CurrentDate {
@Unwrap
public Date getCurrentDate() {
return new java.sql.Date(System.currentTimeMillis());
}
}
The manager component offers a convenient way to make Java constants, Enum values, and other static
method return values accessible via the EL. Manager components are also good for loading resources such as a
Hibernate SessionFactory, a Java mail session, JMS connection. In fact, Seam uses manager components
for all of these resources. It would be possible to create regular components for these resources and then
register an alias that returns the value of the method that retrieves the runtime configuration, but the @Unwrap
method saves you the extra step. If you have ever used one of Spring's factory beans, a manager component is
the same idea.
Let's put the manager component into practice by using it to maintain the list of new golfers shown on the
home page. You see, we are hoping that Open 18 is going to be a big success and as a result, a lot of people are
going to be hitting the home page. We don't want every single hit to result in a database query or else it could
hurt performance. While we could certainly get a faster database server, it's prudent to take the burden off of
the database, especially since we are repeatedly asking it the same question. Who are the new golfers?
Listing 6.6 shows a manager component that maintains a cache of the new golfers in the application scope.
Any time a new golfer registers, it observe the golferRegistered event and flushes the cache.
Licensed to Jaroslaw Gilewski <[email protected]>
Listing 6.6 A component that manages the collection of new golfers
package org.open18.action;
import
import
import
import
import
org.jboss.seam.ScopeType;
org.jboss.seam.annotations.*;
org.open18.model.Golfer;
javax.persistence.EntityManager;
java.util.*;
@Name("newGolfersList")
@Scope(ScopeType.APPLICATION)
public class NewGolfersList {
private int poolSize = 25;
private int displaySize = 5;
@In EntityManager entityManager;
//3
protected List<Golfer> newGolfers;
public void setPoolSize(int poolSize) {
this.poolSize = poolSize;
}
public void setDisplaySize(int displaySize) {
this.displaySize = displaySize;
}
@Create
public void onCreate() {
fetchNewGolfers();
}
//1
@Unwrap
public List<Golfer> getNewGolfers() {
return newGolfers;
}
//4
@Observer(value = "golferRegistered", create = false)
synchronized public void fetchNewGolfers() {
List<Golfer> results = entityManager
.createQuery(
"select g from Golfer g order by g.dateJoined desc")
.setMaxResults(poolSize)
.getResultList();
//2
Collections.shuffle(results);
Random random = new Random();
while (results.size() > displaySize) {
results.remove(random.nextInt(results.size()));
}
Licensed to Jaroslaw Gilewski <[email protected]>
newGolfers = results;
}
}
Cueballs in code and text
The golfers are fetched when the component is first created [#1] and whenever a new golfer registers [#2].
Manager components support bijection [#3] just like any other component. Every time the newGolferList
context variable is accessed, the @Unwrap method [#3] is called and the list of golfers previously retrieved is
returned.
You may notice that this component exposes two configuration properties, poolSize and
displaySize which control how many golfers are selected from the database and how many are displayed,
respectively. To change the defaults, we can use the following component configuration:
<component name="newGolfersList">
<property name="poolSize">10</property>
<property name="displaySize">3</property>
</component>
If we swapped out the newGolfers on the home page with the newGolfersList, we would lose the
ability to capture a selection using a command link. It is not possible to create clickable lists using a manager
component alone (without some creative backbreaking). Manager components are designed to provide data,
not act as an action handler or data model selector. Therefore, we still need a factory to provide a value for the
newGolfers property on ProfileAction to keep the current functionality. Instead of querying for golfers
in the ProfileAction, the newGolfersList manager component will be consulted to get the list. The
relevant changes to ProfileAction are shown in listing 6.7.
Listing 6.7 The manager component acts as the data provider for the newGolfers factory
@Name("profileAction")
public class ProfileAction {
// ...
@In protected List<Golfer> newGolfersList;
@DataModel(scope = ScopeType.PAGE)
protected List<Golfer> newGolfers;
// ...
@Factory("newGolfers")
public void fetchNewGolfers() {
newGolfers = newGolfersList;
}
//1
//3
//2
}
<Annotation #1> Manager component injected
<Annotation #2> Use list maintained by manager component
<Annotation #3> Outject result as before
The clickable list of new golfers works just as it did before. The only change is that the manager
component now provides the list of new golfers rather than performing the entity query directly in the factory
method. The best part about this design is that the manager now acts as a data access object (DAO), separating
the data access logic from the business logic and making the components easier to test in isolation.
Factory and manager components act as "just in time" variable assignment. Your code references a context
variable as if it has already been set and then one of two delegates, either the factory or the manager, runs away
Licensed to Jaroslaw Gilewski <[email protected]>
and creates it, just in time for you to make use of it. But what makes it even better is that the method has access
to dynamic injects via bijection to help it create the variable.
6.8 Summary
This chapter introduced you to how Seam delivered a much needed technology update to inversion of control
by making injections dynamic and introducing the concept of outjection—the two sides of of bijection. You
hardly need to be reminded now that bijection occurs every time a method on a Seam component is invoked,
so not only are dependency references kept up to date, but it is also possible to inject data from a narrow scope
into a component stored in a wider scope.
You learned how you can inject components, request parameters, and value expressions into the bean
properties of a Seam component. You also learned that property values can be pushed back out into a Seam
context, allowing them to be accessed by other components or in a JSF template. When pushing out
collections, Seam can wrap them in a JSF DataModel to be used in a UIData component. Seam can also
capture the selection from that UIData component and inject it into another property on the component on a
subsequent postback.
You discovered that there is a lot more to the component retrieval process than what appears at first
glance. Often times, context variable names resolved to component instances, but they can also be handled by
a factory method or value expression or even resolve to the return value of a delegate method on manager
components.
To accomplish the pinnacle of inversion of control, you learned that it is possible to severe the ties
between components completely while still being able to establish a linear flow of logic by allowing the Seam
container to mediate component event notifications and trigger custom interceptors. Both make it easy to apply
cross-cutting concerns to objects without jeopardizing their status as POJOs.
The exchange of context variables in the Seam container is very rich, yet for most uses, is confined to a
couple of commonly used annotations. Injecting, outjection, and resolving of context variables is crucial for
exchanging data between components and the view. In the next chapter you are going to learn how
conversation annotations and page flow definitions help to propagate state across requests. The factory
component also gets additional limelight, playing a key role launching conversations. The promise of
improved state management mentioned in chapter 4 is given substance in the next chapter, so read on because
the most exciting parts are yet to come.
Licensed to Jaroslaw Gilewski <[email protected]>
7
The conversation: Seam's unit of work
After returning home from your golf getaway, you learn that, in your absence, someone has been having fun at
your expense, swiping your credit card around town. To reclaim your assets, you have to endure the disparate
customer service process deployed by your bank. I'm sure this exchange will sound eerily familiar.
"How can I help you?"
This is your cue. You launch into your rant about being a victim of fraud, your whereabouts during the
previous week, and which charges you are disputing.
Then silence.
"Can I have your account number?"
Your momentum is temporarily interrupted as you cut through the red tape. The agent then informs you
that you need to be transferred to the fraud department, which is more equipped to deal with this matter. There
is no chance to object as the switch happens without delay. A new voice appears on the other end of the line.
"How can I help you?"
Sigh. Time to start over from the beginning.
You started the day as a victim of fraud. Now you have become the victim of a stateless process. Critical
information failed to make the leap between the customer service representative and the fraud representative.
This mishap could have been avoided had the two representatives engaged in a conversation during the switch
to retain the record of your story. Keeping track of state within the context of a use case is referred to as
stateful behavior, something that would surely benefit this process.
In this chapter, you learn how Seam encourages stateful behavior through the use of web conversations. A
conversation encompasses two forms of state to help manage a use case, data and navigation, as illustrated in
figure 7.1. A use case represents the ultimate goal the user is trying to accomplish, not just an atomic stage
along the way.
Figure 7.1 A conversation is a means of holding state. State is divided into data and navigation.
The chapter begins by establishing what it means to have a conversation in a web application and the
difficulties of propagating a working set of data in the stateless HTTP environment. You are then introduced to
Seam's conversation scope as a convenient place to store data across page views, even in light of ad-hoc
requests. You learn the multitude of ways to control the boundaries of a conversation through the use of Seam
Licensed to Jaroslaw Gilewski <[email protected]>
directives. You then learn how combine a conversation with a stateful page flow to constrain the user to a
predetermined track for the duration of a use case. Page flows represent the navigational aspect of
conversations, which draw on data available in the conversation to make navigation decisions, as figure 7.1
suggests.
By the end of the chapter, you will have learned how to make effective use of the conversation state to
retain short-term, yet critical, information that users provide, keeping them content and in stride. Let's begin by
exploring ways in which state is maintained in a web application and how Seam can help manage the task.
7.1 Conversational state in a web application
One of Seam's primary goals is to focus on the user experience and to that end, conversations are a critical part
of how Seam functions. In this section, you learn how Seam redefines an application's unit of work to be that
of a conversation, thus giving the use case—a single user's interaction with the application—a formal
representation within the framework.
As part of establishing this unit of work, the application must store a working set of data that supports the
goal at hand. The traditional approaches to tracking state in web application are contrasted with Seam's
conversations. The conversation context promises to relieve the burden of tracking state and even opens the
door to more sophisticated functionality such as nested conversations and parallel workspaces. Let's see how
this new context helps to focus the application on the user's interaction.
7.1.1 Redefining the unit of work
In technical terms, a unit of work is often defined to be an atomic database transaction. The story told at the
beginning of this chapter is faithful to this definition at the expense of the caller's time. But technical
definitions don't solve a user's problem. The customer service representative records the call in one system and
passes the caller off to a different system, writing off the job as finished. Clearly, this short-lived unit of work
is not conducive to a good user experience. It is not stateful, meaning it is too fine-grained and does not
propagate its memory. As far as the user is concerned, the use case is far from over at this point. From the
user's standpoint, the unit of work extends from the time the call is made to when it is complete, which
encompasses the interaction with the first and second representative and ultimately the resolution.
Conversations reflect the way that a user perceives the application. In a conversation, database
transactions, page requests, and other fine-grained units of work may come and go, but the use case does not
end until the goal at hand is accomplished. In a web application, these smaller units of work are divided up by
individual page views. A conversation represents a linkage between two or more page views, as depicted in
figure 7.2. This linkage is established through the use of a special token and a creative use of the HTTP
session, which you learn about later in the chapter. The conversation scope is longer than the request scope but
(much) shorter than the session scope. How long the conversation lasts is determined by the boundary
conditions of the use case. In section 7.3 you will learn how these boundary conditions are defined.
Licensed to Jaroslaw Gilewski <[email protected]>
Figure 7.2 The conversation scope spans multiple page views but does not last as long as the session scope.
To put theory in to practice, let's consider an example whereby a conversation is used to allow the user to
search and compare golf courses. The user starts on the course directory page, which is used to filter the list of
courses for the purpose of selecting a course. Depending on the mode held in the conversation scope, the
selection either results in the detail screen being displayed or it queues the course to be compared. When the
user is ready to compare the course, the Compare button is clicked and the comparison screen is displayed,
showing each selection side by side. Upon returning to the directory page, the user finds that the selections
have been retained. As long as the user stays within the boundaries of the conversation, the use case is
considered active and the application remembers its state, which is stored in a working set of data. Let's take a
closer look at the working set and consider ways it can help support a use case.
7.1.2 Building a working set of data
The need for a working set arises when the application must perform work that spans multiple requests—
which Seam terms a conversation. To provide continuity, data is stashed into the conversation's working set
during user "think" time—the time after the response is sent to the browser but before the user activates a link
or submits a form. Information is accumulated in the working set as the user moves from screen to screen.
There are four general categories of data that a working set is used to store, all of which we be
demonstrated as the chapter proceeds:
z
non-persistent data – An example of non-persistent data is a search criteria or a selection of records.
The user can establish the state in one request and then operate on it in another.
z
transient entity data – A transient entity instance may be built and populated as part of a wizard and
thus won't be ready to be persisted until the final screen is reached. Once the wizard is complete, the
transient entity instance is drawn from the working set and made persistent.
z
managed entity data – The working set provides an ideal way to work with database-managed entity
data for the purpose of updating its fields. The entity instance is placed into the working set and then
overlaid on a UI form when the user clicks on Edit. When the user clicks Save, the changes made to
the form are applied to the entity instance stored in the working set (whose object identity has been
preserved) and the instance is synchronized with the database.
z
resource sessions – No example is more relevant to developers than the persistence context (JPA
Licensed to Jaroslaw Gilewski <[email protected]>
EntityManager or Hibernate Session). As part of a working set, the persistence context can be
kept open to prevent entity instances from becoming detached during the course of the use case. The
next three chapters focus on how conversations benefit persistence.
Having stateful data close at hand sounds convenient, but managing it with the Java Servlet API alone is
different story. Far too much effort has gone into the task of carrying information from one page to the next
since the dawn of web applications. In fact, propagating state has become a downright burden. Let's weigh the
existing options and then see how Seam's conversation scope is able to offer transparent management of this
state without the headache.
7.1.3 The burden of managing state
To support conversations, there must be a place for the working set to live and a means to propagate it from
one page to the next. The goal of migrating data from page to page is not a new concept in web-based
applications. In fact, it's often one of the most frequently discussed topics in the design phase or thereafter. I'm
sure just about every web developer has used one or more of the following choices from the Java Servlet API
or HTTP protocol at one point or another to send data along to the next request:
z hidden form fields
z request parameters (i.e. the query string)
z HTTP session attributes
z cookies
I'm not going to stand here and say that none of these options are viable. Obviously, all of them have been
used numerous times in the past to create functional applications. But, they each have their limitations that
seek to slow you down on every project you take on, bringing along with them varying degrees of pain. After
looking at some of the problems inherit in these existing options, you will learn about a fifth option, the
conversation scope, which seamlessly ties together the state from one request to the next.
Passing data through the request
Hidden form fields and URL parameters are merely two sides of the same coin, both applied through the use of
HTTP request parameters. The former is used when passing data through a form submission and the latter
when appending data to a URL in a link. All the data is transfered out in the open as string values.
You learned in chapter 3 how Seam helps to propagate data from page to page using page parameters.
While page parameters are helpful at times, if used in excess, managing them can quickly become as
burdensome as managing hidden form fields. Since page parameters are hardwired to a given page, you end up
having to mix parameters need to serve different use cases together. In addition, you run into situations where,
when you link to a page with page parameters, you get more than you bargained for and it becomes hard to
shake off the unwanted state.
The main weakness of request parameters is that the values are disassembled as they cross the request
boundary and then reassembled once they arrive at the server, as depicted in figure 7.3. However, when an
object passes through this funnel, it looses its identity. What exists on the server after the request is a clone of
the original object, possibly even a partial one. This makes it impossible to transfer managed entity instances
and resource sessions, which cannot be serialized into string values without jeopardizing their integrity.
Licensed to Jaroslaw Gilewski <[email protected]>
Figure 7.3 Passing an object using request parameters is akin to teleportation,
If maintaining object identity is important, or the values being propagated cannot be broken down into
chunks to be passed through this funnel, it is necessary to keep them intact by storing them in the HTTP
session.
Storing data in the HTTP session
The HTTP session can be used to store arbitrarily complex values in their original state (maintain object
identity) and have those values be available to any subsequent request made by the same browser. While this
sounds ideal, it's unfortunately too good to be true. The main downfall of the HTTP session is that it quickly
becomes a tangled mess and can interfere with multi-window application usage.
Consider the case in which the session is used to store the golf course entity instance while the record is
being updated in an editor. If you select a course to be edited in one browser tab (or window) and then select a
different course to be edited in another browser tab, when you click Save in the first tab, you may inadvertently
apply the changes to the wrong course instance. Things get even tricker if you are working with a multi-page
wizard, since the leakage of data can be less apparent. It's possible to work around these problems by using the
session carefully or putting in extra validation checks to ensure that cross-tab leakage is not occurring, but that
is more of a burden on you as a developer.
The session scope has other problems due to the fact that it unmanaged. If objects continue to build up in
the session, and there is no application-provided garbage collection routine in place to clean them up, it can
lead to memory leaks. Access to objects in the session are also not protected from concurrent use. In general,
heavy use of the session scope is a common source of bugs and the unexpected behavior caused by state
collision can be very difficult to reproduce in a test environment.
Although not discussed here in detail, cookies share the combined problems of request parameters and
session data. They can only store string data, and even then only a limited size of data, and they are not easily
partition by use case. While these existing storage options are workable, they are not well suited to serve a use
case. Clearly there is room for a better solution. For as much as I just discredited the HTTP session as an
option for storing state, it's not all bad. It just needs to be better managed. That's exactly what Seam does to
give rise to the conversation context.
7.2 The conversation context
As presented in chapter 4, the conversation context is one of two contexts Seam introduces to serve businessworld time frames as opposed to servlet life cycles (the other being the business process context). From
reading the previous section, you should have a very clear picture as to the purpose of the conversation context
for serving a multi-page user interaction. In this section, you learn of the conversation's identity.
Licensed to Jaroslaw Gilewski <[email protected]>
7.2.1 Carving a workspace out of the HTTP session
The conversation context is carved out of the HTTP session to form an isolated and managed memory
segment, as illustrated in figure 7.4. Seam uses the conversation context as the home for a working set of
context variables.
You may shudder at any mention of using the HTTP session for data storage, given the problems that were
cited in the last section. But, you should not confuse a conversation's working set with that of session
attributes. What makes a conversation different from its parentage is that it has its own distinct life cycle. It is
much shorter lasting than session (typically on the order of minutes), capable of being terminated or timing out
separately from the session in which it is lives, pertains only to the current context, and is kept isolated from
other conversations in the same session so as not to interfere with them.
The key distinction between the session and a conversation is that a user can have many conversations
while there is only one HTTP session. From the user's vantage point, a working set is viewed as a workspace.
It represents state for one of the multiple tasks the user may be performing at any given time.
Figure 7.4 Conversation workspaces are isolated segments of the HTTP session, each assigned a unique identifier.
A conversation has a clear set of life cycle boundaries that typically coincide with a single use case,
represented by the interaction with one or more JSF view IDs—or pages. You will learn how to define these
boundaries in the next section. When the user triggers a condition that begins a conversation, a new area of the
HTTP session is sectioned off and dedicated to that conversation. A unique identifier, known as the
conversation id, is generated and associated with this segment of the session. This token is passed as a request
parameter, hidden form field, or JSF view root attribute to retrieve the memory segment and associate it with
the current request.
The conversation context is the ideal place to store data that is needed over the course of several pages. It
enables you to hang on to that data, keeping it in scope while you are working on it. All you have to do to use
it is keep track of the single conversation id request parameter, which you will learn is handled for you
automatically in most cases. Storing data in the conversation context has the same advantage of using the
HTTP session in that arbitrarily complex objects can be preserved without them losing their object identity,
unlike with hidden form fields or URL parameters. This enables you to easily migrate objects, such as the
persistence manager, between requests. By the same token, the conversation context does not get out of hand—
it does not become a tangled mess or suck up tons of memory.
What makes conversations truly unique is that they can coexist with one another, allowing the same
context variable names to be used in isolated memory regions.
Licensed to Jaroslaw Gilewski <[email protected]>
Solving the concurrency problem
The HTTP session is notorious for wrecking havoc on multi-window behavior. The session identifier is stored
as either a cookie in the user's browser or appended to each form target or link URL generated by the
application (URL rewriting). Many browsers will share cookies between tabs and windows—the viewport
herein referred to as a tab. When multiple tabs naively share the same session data, it can quickly lead to
confusing and undesirable behavior as the same data is often manipulated in conflicting ways.
NOTE
Even when URL rewriting is used, spawning new tabs from links may carry over the session identifier. The
result is that each tab is accessing the same HTTP session on the server.
Conversations do not suffer the same leaky behavior as session data across multiple tabs because, in
addition to requiring the session identifier, they also require that a conversation id be sent with the request. The
conversation id gives the request access to an isolated area of the session.
Let's revisit the scenario in which different golf course records are being edited in separate tabs, but this
time consider how the conversation context can be used to keep the entity instances isolated. The user selects a
golf course in the first tab, which starts a new conversation and presents the user with an editor form. Then the
user switches to the second tab and selects a different course, again resulting in a conversation being created
the rendering of an editor form. Each tab has its own conversation. Next, the user switches back to the first tab
and clicks Save. The updates are sent to the server along with the conversation id which was automatically
added to the form—technically it is stored as an attribute in the JSF view root. The conversation id is used to
activate the conversation created by that tab, the entity instance is retrieved from the working set in that
conversation, the changes are applied to the instance, and it is synchronized to the database.
Although the two tabs are serving the same use case, with the same context variables, data is not shared
between them. When the record from the first tab is saved, the state held by the conversation associated with
the second tab is not affected. The important point to be made is that these context variables do not collide.
Section 7.4 demonstrates how it is possible for the user to toggle between concurrent workspaces using a
conversation switcher component.
Without the help of conversations, it becomes a real burden on the developer to track data, keep it in
scope, and prevent it from interfering with other parallel activity in the user's session. But conversations are
more than just working sets of context variables. Let's consider the other benefits that come about as a result of
having stateful behavior.
7.2.2 More than just a context
In one sense, the conversation is just another one of Seam's contexts whose boundaries are controlled by the
application logic. Aside from just providing a place to store context variables, the conversation can be used to
accomplish broader goals thanks to its awareness of the application's state.
Starting from the beginning
Conversations are closely tied to navigation. The pages involved in a conversation can either be requested adhoc without a definitive navigation path or they can be linked together as part of a page flow. In either case,
you can restrict access to certain pages and components in the event that a conversation is not active.
If you are using stateless navigation, you can redirect the user to a starting page if they try to access a page
that requires a conversation to be active. If stateful navigation is being used, a similar restriction can be
enforced. Stateful navigation gives the added control of constraining the user to a given page sequence with the
boundaries of an active conversation. As you can see, conversations are useful both from a state management
perspective and as a navigation and page flow device. (some rework needed)
Licensed to Jaroslaw Gilewski <[email protected]>
Business caching
Conversations can also serve as a domain-specific cache. A point brought up earlier, stateful architecture
ensures that data retrieved from persistence storage or service endpoints is maintained until the application is
ready to relinquish it. That means there is no need to repeatedly consult the data provider for same static
information. You look up the data once and then hang on to it until you consider it to be stale. I refer to this
natural caching mechanism as the business cache.
Generic caching systems, such as a second-level ORM cache, must perform their work based on an
intelligent algorithm that will allow them to hold data for as long as possible, but not so long that it becomes
inconsistent with the underlying data storage. Your application, on the other hand, knows intimate details
about the data and can perform checks relevant to the current use case. In short, there is no one more suitable
for deciding when to keep data and when to throw it away then your own business logic. You can even provide
the user with controls to help the application decide when data should be fetched again. Keep in mind that
using a second-level ORM cache for maintaining conversational data is a misuse of the technology.
You should take advantage of the conversation scope to reduce load on the database. Of all the tiers in
your application, it is the least scalable tier. You don't want to abuse it. Don't make the database pay for the
application failing to track data that it was already fed. A server-side memory will provide much needed relief
to the database. Especially in the world of Web 2.0 in which Ajax requests are fired off to the server at a rate
that far exceeds the previous usage pattern of web applications. Ajax brings up another important issue that is
often ignored, synchrony.
Serialized requests
Seam serializes concurrent requests that access the same conversation. This means only one thread is allowed
to access a conversation at any given time. Therefore, you can be confident that data stored in the conversation
will not be modified by another incoming request while the original request is being served. This safety net
may not be so important in pre-Web 2.0 applications, but when Ajax starts firing off requests like they are
going out of business, the likelihood of having a collision that causes the data to enter an inconsistent state
dramatically increases. Because Seam serializes access to the conversation, you do not have to worry about
such collisions. It's one at a time.
The combination of serialized access and stateful behavior drastically minimizes the risk of using Ajax in
your application. With these mechanisms in place, you can rest assured that performance and data consistency
will not suffer. Conversations fit so naturally with Ajax that you will likely discover that no other solution is as
viable. You will learn more about using Ajax with Seam in chapter 12.
Ajax isn't the only Web 2.0 mechanism that benefits from stateful behavior. Web services can also tap into
the conversation scope.
Stateful web services
Conversations can be used for interactions that don't have access to the HTTP session, or which have
traditionally been stateless. The conversation id can be passed along with the web service header to allow
subsequent requests to access the state accumulated in the HTTP session on the server. A web service can
either be the formal definition or a more lightweight interface such as a RESTful URL. Either way, subsequent
requests can be linked to the same server-side working set.
In this section you learned what we mean when we say "conversation": a context for keeping data in scope
for the duration of a use case and a means of enabling stateful behavior in your applications. The next step is to
learn about the conversation life cycle and then how to control conversations by defining conversation
boundaries.
Licensed to Jaroslaw Gilewski <[email protected]>
7.3 Establishing conversation boundaries
The conversation context is unique from other Seam contexts in that it has explicit boundaries dictated by
application logic, as opposed to implicit boundaries that correlate with some demarcation in the servlet or JSF
life cycle. The application logic controls the boundaries of the conversation context by using conversation
propagation directives. This section introduces these directives and demonstrates how they can be used to
control the life cycle of a long-running conversation.
7.3.1 Conversation propagation
There are actually three conversation states: temporary, long-running, and nested. Switching the state of a
conversation is referred to as conversation propagation. We are going to focus on nested conversations later
on. For now, I want to expound the distinction between a temporary and a long-running conversation. When
you set the boundaries of a conversation using the conversation propagation directives, you are not initiating
and destroying the conversation, but rather transitioning it between the temporary and long-running state.
Temporary versus long-running conversations
Most of the time, when people talk about Seam conversations, they are talking about long-running
conversations. The discussions in the early part of this chapter all have to do with long-running conversations.
As described, a long-running conversation remains active over a series of JSF requests.
In the absence of a long-running conversation, Seam will create a temporary conversation for the purpose
of serving a single request. A temporary conversation is created immediately following the Restore View phase
of the JSF life cycle and is destroyed after the Render Response phase.
You can think of a temporary conversation as serving the same purpose as the flash hash in Ruby on Rails.
In that regard, the role of the temporary conversation is to transport conversation-scope context variables
across a navigation redirect. As a primary example, the temporary conversation is how Seam manages to keep
JSF messages alive during the redirect-after-post technique, as long as they are added using the conversationscoped FacesMessages component. So, the temporary conversation really is destroyed only after the Render
Response phase, even if a redirect proceeds it.
The other purpose of a temporary conversation is to serve as a seed for a long-running conversation. What
you will learn is that a long-running conversation is nothing more than a temporary conversation whose
termination has been suspended—as dictated by a begin conversation directive—until an explicit end
conversation directive has been encountered. Instead of just surviving a navigation redirect, a long-running
conversation is capable of surviving a whole series of user interactions. Only when it reacquires its designation
as temporary can it be terminated. Therefore, learning to use long-running conversations is about learning how
to use the conversation propagation directives and how they transform a temporary conversation to a longrunning conversation and back.
The conversation life cycle
The life cycle of a conversation is controlled using the conversation propagation directives. There are five
conversation propagation directives, as shown in table 7.1.
Table 7.1 A list of the conversation propagation directives.
Propagation type
Description
begin
Promotes a temporary conversation to a long-running conversation. If a long-running conversation is
already active, an exception will be thrown.
join
Promotes a temporary conversation to a long-running conversation if a long-running conversation is
not already active. An exception will not be thrown if a long-running conversation is already active.
Licensed to Jaroslaw Gilewski <[email protected]>
end
Demotes a long-running conversation to a temporary conversation.
nest
If a long-running conversation is active, suspend it and add a new, long-running conversation to the
conversation stack. If a long-running conversation is not active, promote the temporary
conversation to a long-running conversation.
none
Abandon the current conversation. The previous conversation is left intact and a new, temporary
conversation is created to serve the request.
The begin and join directives are logistically the same, the join directive merely asserting that a longrunning conversation is not active before converting the temporary conversation into a long-running one. The
last two directives allow you to step out of the context of the current conversation and are covered in section
7.5.
The conversation propagation directives can be applied using any of the following:
z Method-level annotations
z UI component tags
z Seam page descriptor
z Seam conversation API
z Stateful page flows (jPDL) (end conversation only)
All of these options will be presented when exploring the conversation directives. You are given a plethora
of options so that you can associate the conversation boundary with the most logical point in the code
execution, whether it be the invocation of a component method, the activation of a link or button in the UI, the
request for a page, or, if none of those options apply, an arbitrary point as controlled using the Seam
conversion API.
What this flexibility means for you is that you don't have to go out of the way to define conversation
boundaries. Hopefully, though, you will establish a favorite approach and adhere to it a majority of the time.
Just because you have all of these options doesn't mean you should use every last one of them.
Figure 7.5 diagrams the conversation life cycle, which shows how the state of the conversation may
change during the processing of the request as a result of a conversation propagation directive being
encountered.
Licensed to Jaroslaw Gilewski <[email protected]>
Figure 7.5 The conversation life cycle over the course of a single JSF request. The propagation directives toggle the current
conversation between the temporary and long-running state.
An existing long-running conversation is restored if its conversation id is detected in the request. Although
not shown in this diagram, if the long-running conversation being restored is invalid or has previously timed
out, Seam either forwards the user to a fall back page or silently ignores the restore request. If a long-running
conversation is not detected, or it is invalid, Seam creates a temporary conversation. At any point during the
processing of the request, a conversation propagation directive may be encountered. If this happens, the
temporary conversation may be promoted to a long-running conversation or vice versa. At the end of the
request, the temporary conversation is destroyed, whereas the long-running conversation is tucked away in the
session to be retrieved by a subsequent request.
NOTE
Pay particular attention to how a conversation is ended, as this can be a bit difficult to grasp at first. The
term "end" is deceptive. When a conversation is ended, it isn't destroyed outright. Instead, it is demoted to a
temporary conversation to be terminated at the end of the request, after the view has been rendered.
Demoting a conversation does not result in the context being cleared. Therefore, the conversation-scoped
context variables that were available during the long-running conversation are still available in the Render
Response phase that immediately follows the demotion of the long-running conversation. If you want to
part ways with the conversation altogether, you set the "before redirect" flag on the end conversation
directive and then issue a redirect after demotion has taken place. Having become a temporary
conversation, it will not last the redirect and the next page view will use a fresh conversation.
Licensed to Jaroslaw Gilewski <[email protected]>
Now that you have a clear picture of the long-running conversation and its origin, let's look at how you set
the boundaries of a long-running conversation with the conversation propagation directives, starting at the
beginning.
7.3.2 Beginning a long-running conversation
To demonstrate the use of a long-running conversation, we will work through several examples. The first
example is a multi-page wizard that is used to capture information about a golf course and add it to a directory.
A complete golf course record can be very intimidating, so the wizard format is used to break up the form into
bite-size pieces as shown in figure 7.6.
Figure 7.6 The screens in the wizard for entering a new golf course into the directory.
Each box in figure 7.6 represents a screen containing a form to fill out. As the user moves from screen to
screen, the information from previous screens must be accumulated and stored so that it is available when the
final screen is complete and the course is persisted to the database.
The textbook choice for starting conversations such as the golf course wizard is to add the @Begin
annotation to the action handler method that spawns the wizard. However, there may be times when you need
to start a conversation from a GET request, in which case either the <begin-conversation> page
descriptor tag or a UI component tag is a more appropriate choice. The later two may be attractive to you if
you prefer to keep your navigation controls out of Java code or you need more fine-grained control over the
conversation boundaries. As you read through this section, keep in mind that you only need to begin the
conversation once, so these options are mutually exclusive (unless conversation joining is enabled, which is
covered at the end). Let's begin with Seam's pride and joy, the annotation-based approach.
An annotation-based approach
One of the interceptors that Seam wraps around components is the ConversationInterceptor. This
interceptor looks for the @Begin annotation on the method and if it finds one, it converts the temporary
conversation to long-running after executing the method. The addCourse() method on the CourseWizard
component may be used to begin a long-running conversation for the course wizard. Invoking the
addCourse() method will in turn outject the newCourse context variable to be used by the JSF form:
@Name("courseWizard")
@Scope(ScopeType.CONVERSATION)
public class CourseWizard implements Serializable {
@Out private Course newCourse;
@Begin
public void addCourse() {
newCourse = new Course();
}
}
In the first variation, the long-running conversation will be started as a direct result of a user interaction by
attaching the addCourse() method to a JSF command button component tag:
Licensed to Jaroslaw Gilewski <[email protected]>
<h:commandButton action="#{courseWizard.addCourse}"
value="Add course..."/>
To advance to the first page in the wizard requires the following navigation rule:
<page view-id="/CourseList.xhtml">
<navigation from-action="#{courseWizard.newCourse}">
<render view-id="/selectFacility.xhtml"/>
</navigation>
</page>
Instead of using a JSF command button and a navigation rule, you could trigger the addCourse()
method using a page action from the first screen in the wizard:
<page view-id="/coursewizard/selectFacility.xhtml"
action="#{courseWizard.addCourse}"/>
In this case, when the servlet path /coursewizard/selectFacility.seam is requested, the
addCourse() method is invoked and a long-running conversation is started. The benefit of using a page
action is that it can start a long-running conversation from a bookmarked page or direct link, rather than
relying on a JSF postback.
NOTE
There are two conditions that prevent the @Begin annotation from starting a long-running conversation. If
the action handler method has a non-void return type and the method returns a null value, then the @Begin
annotation is ignored. The conversation also does not begin if an exception is thrown. In all other cases, the
conversation will begin. The same rules apply to methods marked with the @End annotation, which is used
to demote a long-running conversation.
The @Begin annotation is outlined in table 7.2. The pageflow attribute is used to initiate a stateful page
flow and is covered in section 7.4. The flushMode attribute is used to set the flush mode of the persistence
context while the conversation is active and is covered in chapter 9.
Table 7.2 The @Begin annotation marks the start of a long-running conversation.
Name:
Begin
Purpose:
Indicates that a long-running conversation should begin when this method is invoked. To take effect, the method
must complete successfully and have either a void return type or return a non-null value.
Target:
METHOD
Attribute
Type
Function
join
boolean
A true value allows this method to be invoked even when a long-running conversation is
active. Without this override, an exception is thrown indicating that a long-running
conversation has already begun.
nested
boolean
A true value causes the active long-running conversation to be suspended and starts a
new, nested long-running conversation. The default is to begin a non-nested
conversation. This attribute is mutually exclusive with join.
pageflow
String
The name of the jPDL page flow for used to manage this conversation.
flushMode
FlushModeType
Changes the flush mode of the managed JPA EntityManager or Hibernate
Session for the duration of the conversation.
Licensed to Jaroslaw Gilewski <[email protected]>
The @Begin annotation isn't limited to methods acting as action listeners. You can also combine this
annotation with either a life-cycle annotation, such as @Create, or the @Factory annotation. In either case,
the long-running conversation is started at an arbitrary point in the Seam life cycle. But, as you know, it is
never to late to begin a long-running conversation.
As an example, you could define a factory method for the newCourse context variable, which would be
referenced in the first screen in the wizard, and designate it as the start of a long-running conversation:
@Out private Course newCourse;
@Begin
@Factory("newCourse")
public void initCourse() {
newCourse = new Course();
}
Factory methods are a good place to start conversations because they don't require user interaction, but it
doesn't require any XML to be defined in the page descriptor either. The same goes for the @Create life cycle
method. You can also have the conversation begin the very first time the CourseWizard component is used,
regardless of which action listener method is invoked:
@Begin
@Create
public Course initCourse() {
newCourse = new Course();
}
Now, if you prefer your code with a side of angled-brackets, you might instead find the page descriptor to
be an ideal place to begin a long-running conversation.
A page-oriented approach
You just witnessed how it is possible to start a long-running conversation by using a page action to execute a
method annotated with @Begin. This task is so common that Seam includes a built-in page action in the form
of either the method binding expression #{conversation.begin} or the <begin-conversation> tag.
In addition to page requests, the <begin-conversation> tag can be used to begin a conversation during a
page transition, where a page action cannot reach.
Let's start by applying the <begin-conversation> tag to a page transition. We'll assume that the
command button shown earlier is activated, only the action handler method does not make use of the @Begin
annotation. In response to that action, the following navigation rule will start a long-running conversation and
take the user to the /coursewizard/selectFacility.xhtml page:
<page view-id="/CourseList.xhtml">
<navigation from-action="#{courseWizard.addCourse}">
<begin-conversation/>
<redirect view-id="/coursewizard/selectFacility.xhtml"/>
</navigation>
</page>
Licensed to Jaroslaw Gilewski <[email protected]>
If you want to preclude the use of the command button, you can instead declare the long-running
conversation to begin when the /coursewizard/selectFacility.xhtml view ID is requested. This is done either by
using the built-in page action for beginning a conversation, which is part of the Seam conversation API:
<page view-id="/coursewizard/selectFacility.xhtml"
action="#{conversation.begin}"/>
or by nesting the <begin-conversation> tag directly inside the <page> node:
<page view-id="/coursewizard/selectFacility.xhtml">
<begin-conversation/>
</page>
If there is a fine-grained page descriptor associated with the first screen of the wizard, you could exclude
the view-id attribute, thus simplifying the declaration to:
<page>
<begin-conversion/>
</page>
The only downside of using the built-in page action is that you cannot perform "prep work" in a
component method before the first page is rendered, which in the examples above was to initialize and outject
the newCourse context variable. As you will see in a later example, the built-in page action is best suited for
making a conversation generally available without a specific goal or action in mind.
The page descriptor offers a lot of control for defining conversation boundaries because you can
distinguish between initial requests, postbacks, and even action listener outcomes. However, you may want to
be able to associate the start a long-running conversation with a link or button directly. For that, you can use
any of the tags from Seam's JSF component library to control the boundaries of the conversation.
A UI component tag approach
As a third option, you can begin a long-running conversation using one of Seam's UI component tags. You can
either enhance an existing UICommand component with conversation propagation capabilities by adding a
nested <s:conversationPropagation> tag or you can use one of Seam's link tags, <s:link> or
<s:button>, which both have native support for conversation propagation. The value of the propagation
attribute on all three of these tags can be any of the values listed in table 7.1 and is therefore not constrained in
its use to begin conversations.
The UI component tag approach works by passing the conversationPropagation a request
parameter in the URL. However, rather than having to add this parameter yourself, you can use the component
tags cited here to add it for you, which abstracts away the name so that it is not hard-coded in your source
code. If you need to use a custom UICommand component or submit a JSF form when starting the
conversation, you can add a nested <s:conversationPropagation> tag to any UICommand component
tag:
<h:commandButton action="#{courseWizard.addCourse}"
value="Add course...">
<s:conversationPropagation propagation="begin"/>
</h:commandButton>
Licensed to Jaroslaw Gilewski <[email protected]>
Instead of using the <s:conversationPropagation> tag as a nested element in a regular JSF
component tag, you can use the Seam link tags to specify the conversation propagation using the
propagation attribute:
<s:button action="#{courseWizard.addCourse}" propagation="begin"
value="Add course..."/>
As long as you don't need to submit form data, the <s:link> and <s:button> tags are great
alternatives to use when working with conversations. The advantage is that they have conversation controls
built right in. In the next section you will see that they are also capable of passing the conversation token to the
next request. The conversation abilities add to the benefit of producing RESTful URLs that can be reliably
bookmarked, which was covered in chapter 3.
If you have been experimenting with the begin directive while reading through this section, you may have
run into an exception stating that a long-running conversation is already active. To remedy this problem, you
need to learn about how to join an existing conversation.
Enabling conversation joining
By default, Seam only begins a long-running conversation if one is not already in play. A long-running
conversation is active if it was restored from a previous request or if a begin directive has already been
encountered. In either case, if an attempt to cross the begin threshold happens again in the same request, Seam
will throw an exception. It is said that when the begin directive is used, conversation joining is disabled.
This restriction could cause undue errors if it is possible for the user to navigate through one of the defined
conversation boundaries after having already entering into a long-running conversation. For example, assume
that you are using the <begin-conversation> page directive to begin a long-running conversation when
the first page in the course wizard is requested. If the user submits the form on that page with validation errors,
then when JSF attempts to redisplay the page, an exception will be thrown because the <beginconversation> tag is once again encountered, this time in the presence of a long-running conversation.
You may run into other situations where the user is in the middle of a long-conversation and manages to
click on a link that attempts to begin a new long-running conversation on top of the old one. This case is not
that rare since the possible execution paths are numerous in an application that uses free-form navigation. You
probably don't want to surprise your user with erroneous exceptions because they traversed a path that you did
not anticipate. Therefore, unless you have good reason to assert that a long-running conversation is not yet
active, I recommend that you use the join directive rather than the raw begin directive. In the absence of a
long-running conversation, the join directive acts just like the begin directive anyway (without the risk of an
exception).
To enable joining of an existing long-running conversation in the event that one already exists, or to begin
one if it does not, you add the join attribute to the @Begin annotation if you are using the annotation-based
approach:
@Begin(join = true)
public void addCourse() {
course = new Course();
}
Likewise, if you are using the page-oriented approach to mark the boundaries of your conversation, then
you add the join attribute to the <begin-conversation> tag:
<page view-id="/CourseList.xhtml">
Licensed to Jaroslaw Gilewski <[email protected]>
<navigation from-action="#{courseWizard.addCourse}">
<begin-conversation join="true"/>
<redirect view-id="/coursewizard/selectFacility.xhtml"/>
</navigation>
</page>
If you are using the built-in conversation control #{conversation.begin} in a page action, there is
nothing you have to change since this method is already "join safe". Finally, if you are using the UI component
tag approach, you would switch the value of the propagation attribute from begin to join:
<s:button action="#{courseWizard.addCourse}" propagation="join"
value="Add course..."/>
I realize that all of the options just presented to you may have been a tad overwhelming. You can finally
breathe knowing that I am turning off the fire hose. I laid out all of these options so that you can get an
appreciation for how flexible Seam is when it comes to setting the boundaries of long-running conversations.
As you might expect, there are just as many options for declaring the end of a long-running conversation as
there is to begin one. However, since they follow the same patterns, it should be easy to pick up, just as it was
to switch from begin to join.
AUTHOR CHOICE
I find the best approach for controlling conversations is to use the page descriptor. It has the benefit of
making your components more reusable because they aren't tied to conversation boundaries. In one
scenario, you may want a long-running conversation to begin when a component method is invoked, while
in other cases you don't want the invocation to have an affect on the current conversation. The page
descriptor tags even support conditional conversation boundaries, as you learn next.
Now for the bonus material! The <begin-conversation> directive supports a conditional clause using
the if attribute. The value of the if attribute is a boolean value expression that is evaluated to determine
whether or not the conversation propagation should be applied. The following declaration has the same effect
as a join because it checks against the current conversation's long-running status:
<page view-id="/CourseList.xhtml">
<navigation from-action="#{courseWizard.addCourse}">
<begin-conversation if="#{!conversation.longRunning}"/>
<redirect view-id="/coursewizard/selectFacility.xhtml"/>
</navigation>
</page>
The condition on the <begin-conversation> tag becomes very useful as your pages mature to serve
more variant use cases with different entrances and exits.
All this work of creating a long-running conversation is only useful if you can restore it. Let's see what
you need to do to ensure that you can continue using the conversation on the next request.
7.3.3 Keeping the conversation going
Now that you have learned how to strike up a conversation, the next logical step is to learn how to keep the
conversation going. By that, I mean how to restore the conversation on a subsequent request. The secret to
restoring a conversation is to pass its conversation id on to the next request using the conversation token.
Depending on the style of the request, this token may be passed as a request parameter or tucked away in the
JSF component tree. Either way, when Seam detects the conversation token in the request, it uses its value to
Licensed to Jaroslaw Gilewski <[email protected]>
restore the existing long-running conversation rather than spawning a new temporary conversation to handle
the request.
If passing the conversation token sounds like tedious work, there is good news for you. This task is
handled automatically. In the presence of long-running conversation, Seam will add the conversation token to
the JSF component tree so that is available on any subsequent JSF postback. For instance, to submit the form
to select a facility and advance to the basic course information screen while, at the same time, retaining the
long-running conversation, you can simply use a JSF command button:
<h:commandButton action="#{courseWizard.selectFacility}"
value="Next &gt;&gt;"/>
Notice that there is no indication of the conversation token. In fact, if you view the source of the rendered
page, you won't find the conversation token there either. Seam works by placing the conversation token into a
JSF component tree attribute. On a postback, when the component tree is restored, Seam picks the
conversation id off the component tree and uses it to restore the long-running conversation.
That takes care of JSF postbacks, but what about non-JSF postbacks that do not have access to the JSF
component tree? Seam can restore a conversation from a GET request by reading a special request parameter.
Restoring a conversation from a GET request
The conversation is restored automatically on JSF postbacks because Seam is able to leverage the state-saving
ability of the JSF component tree by storing the conversation id in the JSF component tree. A GET request, on
the other hand, is always a fresh start. Seam cannot read your thoughts, so unless you explicitly tell Seam
which conversation to restore, a new temporary conversation is created to handle the request.
To have Seam restore a long-running conversation, all you have to do is pass the conversation token along
with the request as a URL parameter. The default name for this parameter is conversationId. You can
access the current conversation id using the value expression #{conversation.id}, which references the
built-in conversation component bound to the conversation context variable.
Let's say that, at any point in the course wizard, you want to allow the user to be able to view a summary
of what they have entered in a preview window. You give that page access to the long-running conversation
associated with the wizard using the following hyperlink:
<a href="preview.seam?conversationId=#{conversation.id}"
target="_blank">Preview</a>
You may prefer to use JSF component tags rather than raw HTML tags. In that case, you can use the
<h:outputLink> tag, defining the conversationId request parameter using a nested <f:param> tag:
<h:outputLink value="preview.seam" target="_blank">
<f:param name="conversationId" value="#{conversation.id}"/>
<h:outputText value="Preview"/>
</h:outputLink>
There is one serious flaw in the previous two links. Seam allows the name of the conversation token to be
customized, but these links hard-code the default name, conversationId. The name used for the
conversation token is set using the conversationIdParameter on the built-in conversation manager
component, bound to the manager context variable. You can override the name of the conversation token using
the following component configuration:
Licensed to Jaroslaw Gilewski <[email protected]>
<core:manager conversation-id-parameter="cid"/>
With this override in place, any links with a hard-coded conversationId parameter will no longer
perpetuate the long-running conversation. Fortunately, Seam provides a special UIParameter component
tag, <s:conversationId>, that can be used to add the conversation id parameter to the parent JSF link
component, replacing the need for the nested <f:param> tag in the previous example:
<h:outputLink value="preview.seam" target="_blank">
<s:conversationId/>
<h:outputText value="Preview"/>
</h:outputLink>
But, Seam's link tags, <s:link> and <s:button>, are a much better equipped for this purpose. They
are aware of the presence of a long-running conversation and will add the conversation token to the URL that
they generate, saving you from having to remember to include the <s:conversationId> component in the
link:
<s:link view="preview.xhtml" target="_blank" value="Preview"/>
Keep in mind that Seam's link tags do not submit the JSF form and do not restore the JSF component tree,
so they would not be useful for advancing the course wizard. But for basic linking, they are the best choice for
navigating within the context of the long-running conversation and have the added benefit of being able to
control the conversation using the propagation attribute.
These conversation token is not only useful for links and buttons, but also to control how long-running
conversations are restored through alternate channels, such as Ajax requests and conversational web services.
The conversation token is the key to the storage locker holding the context variables in a given conversation.
You have now struck up a long-running conversation and learned how to navigate within its boundaries,
let's consider how to take advantage of this working set by contributing to it on one screen and accessing the
values it holds on adjacent screens.
7.3.4 Enlisting objects in a conversation
You enlist objects, typically component instances or outjected variables, in a conversation by storing them into
the conversation context. When you see that a component is scoped to the conversation context, or that a value
is outjected to the conversation context, you might think to yourself, "But which conversation?"
As you know, there is always a conversation active during a JSF/Seam request and that conversation is
either temporary or long-running. It's possible that multiple parallel conversations exist in the server's memory,
but never mind those for right now because a request can only serve a single conversation at a time. So, the
answer to your question is the conversation that is active at the time the conversation-scoped component is
accessed, or the outjection occurs, depending on which case you are asking about.
What you may find interesting—perhaps even surprising—is that a temporary conversation does not need
to be converted to a long-running conversation before you can start using it as one. You can access
conversation-scoped components and outject to the conversation-scope, thus contributing to the conversion
context, while the conversation is still temporary. Any variables added to the conversation context while the
conversation is temporary remain part of the conversation context once the conversation transitions to longrunning. In fact, the conversation status is nothing more than a marker which dictates whether to clean up the
conversation context or store it in the session as a working set after the Render Response phase is complete.
Licensed to Jaroslaw Gilewski <[email protected]>
Attaching to the active conversation
You should be able to put this knowledge together with what you have learned about component instantiation
to recognize that when a conversation-scoped component is requested via its context variable name, the
instance is attached to the active conversation. To put substance to this, I will talk through what happens when
the course wizard is launched. Let's assume that the CourseWizard class is defined as follows:
@Name("courseWizard")
@Scope(ScopeType.CONVERSATION)
public class CourseWizard implements Serializable {
@Out private Course newCourse;
@Begin
public void addCourse() {
newCourse = new Course();
}
}
NOTE
Conversation-scoped components should implement the java.io.Serializable interface since they
will be stored in the HTTP session between requests. Implementing the java.io.Serializable
interface allows the object to properly serialize when using distributed sessions.
When the addCourse() method is invoked either from a JSF command button or a page action, the
conversation context is populated according to table 7.3.
Table 7.3 The steps by which the context variables are bound to the conversation serving the course wizard.
Step
Description
1. User activates JSF command button
JSF life cycle is invoked, #{courseWizard.addCourse} action queued
temporary conversation is created
Invoke Application phase is entered, action is processed
CourseWizard is instantiated and bound to courseWizard context variable in
conversation context
2. Action handler addCourse() invoked
Course is instantiated and assigned to private field newCourse
newCourse is outjected into conversation context
temporary conversation promoted to long-running conversation
3. Navigation rule fires
Render Response phase is entered
first screen of course wizard is rendered
long-running conversation stored in JSF component tree
When the first screen of the course wizard comes to rest, there are two context variables in the
conversation, courseWizard and newCourse. The newCourse context variable is outjected to the
conversation scope since the owning class, CourseWizard, is a conversation-scoped component. For the
duration of the wizard, the newCourse context variable is progressively populated by each screen.
If a parallel conversation were taking place in a separate tab, and the same action handler invoked, the
process described above would be executed, only it would occur in another isolated area of the session
managing a separate conversation. The same two context variables, courseWizard and newCourse, would
exist in that conversation without interfering with the context variables in the first conversation. Two
conversations, two sets of variables.
TIP
Licensed to Jaroslaw Gilewski <[email protected]>
You can inspect which context variables are available in the conversation by using the Seam debug page.
Ensure that debug mode is enabled (see chapter 5) and then visit the servlet path /debug.seam. You can
use this page to inspect all active conversations as well as the session and application scope.
You should note that the component that hosts the @Begin method does not have to be a conversationscoped component. You could begin the conversation for the course wizard using the following definition of
the CourseWizard class:
@Name("courseWizard")
public class CourseWizard {
@Out(scope = ScopeType.CONVERSATION) private Course newCourse;
@Begin
public void addCourse() {
newCourse = new Course();
}
}
In this case, the newCourse context variable has to be explicitly pushed out to the conversation context
since the owning component is no longer scoped to that context. Thus, only the newCourse context variable
is present in the conversation. To ensure that subsequent action handlers have access to this context variable,
you may want to enforce the presence of a long-running conversation.
Making the conversation a prerequisite
If you want to enforce that a component or method only be used within the scope of a long-running
conversation, then you annotate the component class or the method with the @Conversational annotation.
For instance, you could declare that the submitBasicInfo() method only be used in the context of a longrunning conversation:
@Conversational
public String submitBasicInfo() {
// ...
}
If an attempt is made to execute a @Conversational method outside the presence of a long-running
conversation, Seam will raise the org.jboss.seam.noConversation event and then throw the runtime
exception NoConversationException.
Using this annotation is superficial in most cases since there is likely more to the story than just the
existence of a conversation. In the next section we are going to see how a conversation page flow is a much
better way to enforce this requirement. However, if you are trying to protect a very sensitive area of the code
than this simple annotation might make sense.
The course wizard offers a nice simple example to get started with conversations, but it could really
benefit from a page flow and it also falls short on demonstrating the true convenience of having a working set
of context variables. While the user is working through the course wizard, let's consider the example of
comparing courses.
Learning to use the working set
In the course wizard, the conversation context relieves you of having to carry along the course data using
hidden fields as you progress from screen to screen, but without having to use the crutch of the HTTP session.
Licensed to Jaroslaw Gilewski <[email protected]>
In this next example, the conversation context will be used in a non-linear fashion to build up a selection of
courses which are then compared side-by-side on a comparison screen. This activity will be supported by the
conversation-scoped CourseComparison component.
To capture a selection of courses, we first need to "open" a working set. That is done by beginning a longrunning conversation when the CourseList.xhtml page is requested using the following stanza in the
CourseList.page.xml descriptor:
<begin-conversation join="true"/>
Next, we need to enable data model selection on the CourseList.xhtml page. To do that, we first need to
put the result list in the page scope so that it remains stable on postback. The courses context variable is
populated using a factory method on CourseList:
@Name("coruseList")
public class CourseList extends EntityQuery<Course> {
...
@Factory(value = "courses", scope = ScopeType.PAGE)
public List<Course> getResultList() {
return super.getResultList();
}
}
Next, the courses context variable is used in the value attribute of the <rich:dataTable> and the table
is wrapped in an <h:form>:
<h:form id="courses">
<rich:dataTable id="courseList" var="course" value="#{courses}"
rendered="#{not empty courses}">
...
</rich:dataTable>
</h:form>
With the table setup, we need to enable a course to be selected. We don't want to interfere with the Select
link that takes the user to the course detail page, so we will introduce the concept of modes. When comparison
mode is enabled, the Select link adds the selected course to the comparison queue. When comparison mode is
disabled, the Select link will have to existing behavior. The mode is toggled using the enable() and disable()
methods on CourseComparison. The comparisonModeEnabled context variable is outjected to the
conversation scope to indicate whether the mode is active. Another variable named readyToCompare
indicates whether there is at least two courses selected. To capture the course selection, a parameterized action
handler select(Course) on CourseComparison is used. The CourseComparison component is
shown in listing 7.1.
Listing 7.1 A conversation-scoped component to support the comparing courses use case
package org.open18.action;
import ...;
@Name("courseComparison")
@Scope(ScopeType.CONVERSATION)
public class CourseComparison implements Serializable {
@Out private boolean comparisonModeEnabled = false;
Licensed to Jaroslaw Gilewski <[email protected]>
@Out private boolean readyToCompare = false;
@Out private List<Course> comparedCourses =
new ArrayList<Course>();
public void select(Course course) {
if (comparedCourses.contains(course)) {
return;
}
comparedCourses.add(course);
if (comparedCourses.size() > 1) {
readyToCompare = true;
}
}
public void enable() {
comparisonModeEnabled = true;
if (courses.size() > 1) {
readyToCompare = true;
}
}
public void disable() {
comparisonModeEnabled = false;
readyToCompare = false;
}
}
The CourseList.xhtml requires three additions. First, buttons to enable and disable comparison mode:
<s:button id="enableCompare" action="#{courseComparison.enable}"
value="Enable comparison mode" rendered="#{not comparisonModeEnabled}"/>
<s:button id="disableCompare" action="#{courseComparison.disable}"
value="Disable comparison mode" rendered="#{comparisonModeEnabled}"/>
Next, a page fragment that lists the courses to be compared:
<s:fragment
rendered="#{comparisonModeEnabled and comparedCourses.size gt 0}">
<rich:panel>
<f:facet name="header">Courses selected for comparison</f:facet>
<ui:repeat var="_course" value="#{comparedCourses}">
<div>#{_course.name}</div>
</ui:repeat>
</rich:panel>
</s:fragment>
Finally, the Select link in the course list table needs to be conditionally rendered to either be a drill down
link or a link to select a course for comparison, depending on the mode:
<s:link id="course" view="/#{empty from ? 'Course' : from}.xhtml"
Licensed to Jaroslaw Gilewski <[email protected]>
value="Select" rendered="#{not comparisonModeEnabled}">
<f:param name="courseId" value="#{course.id}"/>
</s:link>
<h:commandLink action="#{courseComparison.select(course)}"
value="Select" rendered="#{comparisonModeEnabled}"/>
With comparison mode enabled, each time the Select link is clicked for a previously unselected course, it
is added to the list of compared courses in the conversation context. You are free to search, sort, and paginate
the course list without worry of tracking the selected courses. No hidden form fields, no <t:saveState>.
When the user is ready to compare the courses, they navigate to the CompareCourses.xhtml page using the
following Seam link tag which sends the conversation token along with the request:
<s:button value="Compare" view="/CompareCourses.xhtml"
rendered="#{readyToCompare}"/>
The comparison page has full access to the conversation-scope variables established on the course list page
and can direct the user back to the course list page to select additional courses. The course comparison is as
follows:
<rich:panel>
<f:facet name="header">Compare Courses</f:facet>
<h:panelGrid columns="#{comparedCourses.size + 1}">
<rich:panel>
<f:facet name="header">&#160;</f:facet>
<div>Location:</div>
<div>Type:</div>
<div>Holes:</div>
<div>Green Fee:</div>
...
</rich:panel>
<c:forEach items="#{comparedCourses}" var="_course">
<rich:panel>
<f:facet name="header">#{_course.name}</f:facet>
<div>#{_course.facility.city}, #{_course.facility.state}</div>
<div>#{_course.facility.type}</div>
<div>#{_course.numHoles}</div>
<div>#{_course.facility.greenFee</div>
...
</rich:panel>
</c:forEach>
</h:panelGrid>
</rich:panel>
<div class="actionButtons">
<s:button view="/CourseList.xhtml" value="Add courses"/>
</div>
Notice the traversal of the facility association. The persistence context is scoped to the conversation,
allowing lazy associations to be loaded in the view since the Course entity instance never enters a detached
state. You are going to learn how this works in chapters 8 and 9 on managed persistence.
Since the CompareCourses.xhtml page requires that you have a conversation active and that at least two
courses are selected, you may want to enforce these restrictions in the CompareCourses.page.xml descriptor:
Licensed to Jaroslaw Gilewski <[email protected]>
<page conversation-required="true"
no-conversation-view-id="/CourseList.xhtml"
action="#{courseComparison.validate}">
<navigation from-action="#{courseComparison.validate}">
<rule if-outcome="invalid">
<redirect view-id="/CourseList.xhtml">
<message severity="warn">
You must select at least two courses.
</message>
</redirect>
</rule>
</navigation>
</page>
If the page is requested and a long-running conversation is not active or has expired, the
org.jboss.seam.noConversation event is raised and the user is redirected to the view ID defined by
the no-conversation-view-id attribute. If there is a long-running conversation, then the
#{courseComparison.validate} page actions gets its chance to validate the request.
Having learned to fear the session, you may be uneasy about letting data accumulate in the active
conversation. Apart from ending the conversation, which ultimately destroys the conversation context, you
may want to be able to remove a particular context variable.
Unregistering conversation-scoped context variables
Any objects associated with a conversation are held in the conversation context until the conversation ends or
the object is explicitly removed from the conversation context. If you get to a point where a context variable is
no longer needed and is just wasting space in the conversion context, you can remove it in one of two ways:
z
Set the property annotated with @Out(required = false) to null
z
Remove the context variable using the Seam Context API
For instance, in the course wizard, you don't want to append a new tee set to the Course instance until it
has been validated. Thus, the tee set information form (the fifth screen in the wizard) works on temporary
TeeSet instance bound to the newTeeSet context variable. When the form is submitted and passes
validation, the newTeeSet context variable can be assigned to the Course instance and discarded. Setting
the outjected property to null accomplishes this task:
@Out(required = false) private TeeSet newTeeSet;
public void saveHoleData() {
newTeeSet = new TeeSet();
}
public void saveTeeSet() {
newCourse.getTeeSets().add(newTeeSet);
newTeeSet = null;
}
Another way to accomplish this task, and may be better suited if not using outjection, is to retrieve the
conversation context via the Seam Context API and manually remove the context variable from it:
Contexts.getConversationContext().remove("newTeeSet");
Licensed to Jaroslaw Gilewski <[email protected]>
The best way to ensure that conversation-scope context variables are cleaned up is to just end the
conversation. Leaving conversations active is not as dangerous as letting objects linger in the session, but is
still a good idea to prevent excess consumption of memory.
7.3.5 Ending a long-running conversation
As you have learned, conversations are a managed region of the HTTP session. Thus, it is possible to terminate
a working set without destroying the entire conversation. A conversation can either be ended explicitly using
an end propagation directive or it can be automatically garbage collected by Seam when its idle time exceeds
the timeout value of the conversation.
The end propagation directive is used in the same way as the begin directive. The most obvious use case
for ending a conversation is when the user wants to cancel out of a form or wizard. In this case, you want to
discard the conversation and return the user to the home page. The easiest way to do this is to use the
<s:link> component tag.
<s:link view="/CourseList.xhtml" propagation="end" value="Cancel"/>
However, if you prefer to keep your conversation directives out of the JSF views, you can use the
pages.xml configuration.
<page view-id="/coursewizard/*">
<navigation>
<rule if-outcome="cancel">
<end-conversation/>
<redirect view-id="/CourseList.xhtml"/>
</rule>
</navigation>
</page>
You pair this navigation rule with the following simplified link:
<s:link action="cancel" value="Cancel"/>
You could also use a regular JSF command link, but you must be sure to set the immediate flag to true
so that the action is handled prior to applying any validations. The <s:link> and <s:button> components
do not have this requirement since they do not submit the form.
<h:commandLink action="cancel" immediate="true" value="Cancel"/>
Ending a conversation merely demotes it from long-running to temporary. That means that whatever
values were present in the conversation will still be available when rendering the ensuing page. This might be
desirable if the next page is a confirmation screen that shows a summary of the persisted data. However, if you
really want to wipe out all evidence of the conversation before rendering the next page, you need to instruct
Seam to terminate the conversation prior to issuing the redirect. The pages.xml configuration has been updated
to reflect this change.
<page view-id="/coursewizard/*">
<navigation>
<rule if-outcome="cancel">
Licensed to Jaroslaw Gilewski <[email protected]>
<end-conversation before-redirect="true"/>
<redirect view-id="/CourseList.xhtml"/>
</rule>
</navigation>
</page>
By setting the before-redirect attribute to true on the <redirect> element, the context variables
that were active during the wizard are no longer available to the CourseList.xhtml page when it is rendered.
Assume that the user made it all the way through the wizard and is ready to save the new course. This case
is perfect for showing off the @End annotation. Let's place a command button on the last page that will be used
to invoke the save() method on the CourseWizardAction component.
<h:commandButton action="#{courseWizard.save}" value="Save"/>
Next, you place the @End annotation on the save() method so that the conversation is demoted to
temporary when the method call is complete:
@End
public String save() {
try {
// ...business logic...
entityManager.persist(newCourse);
FacesMessages.instance().add(
"#{newCourse.name} has been added to the directory.");
return "success";
} catch (Exception e) {
FacesMessages.instance().add(
"Saving the course failed.");
return "failure";
}
}
The @End annotation is summarized in table 7.4.
Table 7.4 The @End annotation is used to transition the long-running conversation to temporary.
Name:
End
Purpose:
Indicates that a long-running conversation should end when this method is invoked. To take effect, the method must
have a void return type or return a non-null outcome when invoked.
Target:
METHOD
Attribute
Type
Function
beforeRedirect
boolean
If set to true, instructs Seam to terminate the temporary conversation prior to issuing a
navigation redirect. The default is to propagate the conversation across the redirect and
terminate it once the response is complete.
Alternatively, you may want to end the conversation using the <end-conversation> tag in the
pages.xml rather than by using the @End annotation.
<page view-id="/coursewizard/*">
<navigation from-action="#{courseWizard.save}">
<rule if-outcome="success">
<end-conversation/>
Licensed to Jaroslaw Gilewski <[email protected]>
<redirect view-id="/coursewizard/summary.xhtml"/>
</rule>
</navigation>
</page>
The newCourse context variable is still available on the summary page since the conversation is not ended
prior to the redirect.
HINT
You want to be careful using the beforeRedirect flag because you will lose any JSF messages that
you added in the action handler method. An intermediate screen is recommended if you must display
messages.
The other, less graceful way of terminating a conversation it to allow it to expire. The default timeout
period of the conversation is assigned using the built-in conversation manager component, bound to the
manager context variable. The timeout is specified in milliseconds. The following stanza sets the timeout
period to one hour:
<core:manager conversation-timeout="3600000"/>
You can also customize this value per-conversation by setting the timeout attribute on the <page> node
in the page descriptor. What this allows you to do is modify the timeout period per screen, to perhaps give the
user more time to fill out a more complex form:
<page view-id="/coursewizard/holeData.xhtml" timeout="7200000"/>
You can also manually set the value by using the setTimeout() method on the built-in conversation
component, which is bound to the conversation context variable.
Generally, you want to avoid abandoning conversations because it makes the session larger than it has to
be. The important point to take away, however, is that doing so is not dangerous. In fact, it may even be
desirable if your intent is to be able to switch between existing long-running conversations, which are covered
in section 7.6.
All this time you have spent with the course wizard example has probably gotten you thinking that this use
case would be better served by a page flow. Your right! Let's hook up the course wizard to Seam's jBPM-based
page flow support. (these last three paragraphs are choppy)
7.4 Driving the conversation with a page flow
There are two types of navigation models in Seam, stateless and stateful. Up to this point you have worked
solely with the stateless navigation model. The stateless model is ideal if you want the user to be able to
perform steps in no particular order. The course comparison offered a good example of that type of use case.
But when the path to the end goal is very clear, like in the course wizard example, it makes far more sense to
drive it with a page flow.
In Seam, page flows are implemented using a special integration with the jBPM library. It may seem like
overkill to use a Business Process Management (BPM) library to control a page flow. But, understand that
Seam is leveraging jBPM for its generic process definition language (jPDL) and interpretor, which together
serve as a framework for building flow-based software modules. It is on this framework that Seam has built its
page flow module. You learn more about using jBPM to drive actual business processes in chapter 14.
Licensed to Jaroslaw Gilewski <[email protected]>
7.4.1 Learning to use a page flow
A jPDL descriptor defines the page flow for a single conversation. We will use a page flow named
courseWizard, defined in the courseWizard.jpdl.xml descriptor, to drive the course wizard and even
perform some logic based on the user's input. Rather than just dump the whole descriptor on your lap, I am
going to step through it piece by piece so that you can better understand how it works. The complete descriptor
is available in the source code that accompanies the book.
First things first, it is necessary to "install" the page flow, courseWizard.jpdl.xml, in the Seam
conversation descriptor:
<bpm:jbpm>
<bpm:pageflow-definitions>
<value>courseWizard.jpdl.xml</value>
</bpm:pageflow-definitions>
</bpm:jbpm>
The page flow descriptor can be located anywhere on the classpath. For seam-gen projects, it should be
placed in the resources folder at the root of the project tree. With the file in place, you are ready to start
populating it.
The root tag of a page flow is <pageflow-definition>. The name of the page flow is defined in the
name attribute on this node. Seam provides an XSD schema for the page flow descriptor so that you get all of
the tag completion goodness that you are enjoying with the other Seam descriptors. The outer shell of the page
flow descriptor for the course wizard appears is shown here:
<pageflow-definition
xmlns="http://jboss.com/products/seam/pageflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://jboss.com/products/seam/pageflow
http://jboss.com/products/seam/pageflow-2.0.xsd"
name="courseWizard">
...
</pageflow-definition>
To put the page flow in motion, you have to create a process instance that will manage it. Fortunately,
Seam makes this task extremely easy. Since a page flow is typically mapped one-to-one with a conversation,
you begin a page flow using the exactly same directive that you use to begin the conversation. Regardless of
whether you are using the @Begin annotation, the <begin-conversation> page descriptor tag, or the
Seam UI component tags, you specify the page flow definition in the pageflow attribute. When the
conversation begins, an instance of the page flow definition will be created and the process token is advanced
to the start node. More on that shortly.
Here, the CourseWizard component has been augmented to the page flow to the conversation that is
started by the addCourse() action handler method:
@Begin(pageflow = "courseWizard")
public void addCourse() {
newCourse = new Course();
}
Licensed to Jaroslaw Gilewski <[email protected]>
I want to encourage you by saying that the hardest part of page flows is the first step. Once you get beyond
that, I am positive that you will pick up on page flows without any trouble. When the process instance, which
is used to track the state of the page flow, is created, it looks for a starting node. You have two choices,
<start-state> or <start-page>. If you are starting a page flow from an action—meaning at a point in
the Seam life cycle where a navigation event can still occur—then you use the <start-state> node. This
node is the most appropriate choice for the course wizard. We will consider when the <start-page> node is
used next. The page flow definition for the course wizard begins as follows:
<pageflow-definition name="courseWizard">
<start-state>
<transition to="basicCourseInfo"/>
</start-state>
</pageflow-definition>
The <transistion> node is analogous to the <rule> node in the page descriptor in that it matches an
outcome value—the return value of an action handler method—specified in the name attribute. However, if
there is no outcome value, which is the case with the addCourse() method, then the name attribute can be
omitted.
A transition implies that there must be a target. The to attribute specifies the name of the node to advance
to next. There are four main nodes that can appear in the page flow definition after the start state. These nodes
are summarized in table 7.5.
Table 7.5 The main nodes in the page flow descriptor
Node
Purpose
page
Renders a JSF view and declares transitions that are used upon exiting that view
decision
Evaluates an EL expression and follows a declared transition based on the result
process-state
Used to spawn a sub-page flow
end-state
Terminates the process instance without ending the long-running conversation; typically used to end a subpage flow
The <page> node is what ultimately leads to the next JSF view in the flow being rendered and is thus the
"wait" state in the process. Do not confuse it with the <page> node in the page descriptor as it does not have
the same semantics. The <page> node indicates a JSF view ID should be rendered when the process token
arrives at the node. The following <page> node renders the first screen in the course wizard, coming out of
the start state:
<page name="basicCourseInfo"
view-id="/coursewizard/basicCourseInfo.xhtml" redirect="true">
<transition name="cancel" to="cancel"/>
<transition name="next" to="description"/>
</page>
If the redirect attribute is included on the <page> node and has a value of true, then Seam performs a
redirect prior to rendering the page. Doing so resets the URL in the browser so that it reflects the current page
and avoids the confusing message about resubmitting post data that is thrown up as the result of using the
browser's "refresh" button. I will mention more about browser buttons in a moment.
NOTE
You may also specify the redirect functionality using a nested <redirect/> element. Which you choose is
Licensed to Jaroslaw Gilewski <[email protected]>
up to you, but I prefer the redirect attribute since it is adjacent to the view-id attribute to which it
applies.
If the conversation, and the page flow association with it, is started from the Render Response phase, then
it is not possible to invoke a navigation event. Therefore, the beginning of the page flow is specified using a
<start-page> node. The <start-page> node acts exactly like a <page> node, only it has the special meaning of
being the first page. Consider the case in which the newCourse context variable is created by a @Factory
method and that method is what begins the conversation:
@Begin(pageflow = "courseWizard")
@Factory("newCourse")
public void initCourse() {
newCourse = new Course();
}
Rather than executing the addCourse() action handler, the user is directed to the
/coursewizard/basicCourseInfo.xhtml screen directly. Then, the start of the page flow is defined as follows:
<start-page name="basicCourseInfo"
view-id="/coursewizard/basicCourseInfo.xhtml">
<transition name="cancel" to="cancel"/>
<transition name="next" to="description"/>
</start-page>
There is one important bit of information that you must know to make sense of a <page> node (as well as
the <start-page> node). This node bundles configuration for entering the node and leaving the node. Okay,
so that isn't so different from other process nodes. But what makes the <page> node so confusing to grasp at
first is that it is a wait state node, which means that the entry configuration is processed in one request and the
exit configuration is processed in another. Thus, the <transition> nodes in the <page> node shown above
apply to the outcomes of actions on the basicCourseInfo.xhtml view. While the user is filling out the form, the
process instance is paused.
There is nothing special about the JSF views that participate in a page flow. The only difference is how the
UICommand components are defined. As mentioned earlier, transitions are chosen based on the outcome of an
action. You have a number of options on how to arrive at that outcome. Let's start with the literal approach,
which is used on the first screen in the wizard. There are two command buttons, one that cancels the wizard
and one that advances to the next screen:
<s:button id="cancel" action="cancel" value="Cancel"/>
<h:commandButton id="next" action="next" value="Next"/>
When either of these two buttons are activated, Seam merely matches the literal values of the action with
the transitions defined on the <page> node and advances the token to the named nodes, shown here:
<page name="description"
view-id="/coursewizard/description.xhtml" redirect="true">
<transition name="cancel" to="cancel"/>
<transition name="next" to="holeData">
<action expression="#{courseWizard.prepareHoleData}"/>
</transition>
</page>
Licensed to Jaroslaw Gilewski <[email protected]>
<page name="cancel" view-id="/CourseList.xhtml" redirect="true">
<end-conversation before-redirect="true"/>
</page>
Now things are starting to get interesting. Let's first focus on the cancel transition, which transitions to
the <page> node named cancel. In the cancel <page> node, we see another familiar element, <endconversation>. The <end-conversation> element is interpreted in the same was as when it is used with a
<rule> element in the page descriptor. It is applied upon entering the <page> node just like the redirect
attribute. Since the two are combined in this case, when the CourseList.xhtml page is rendered, the
conversation that served the page flow will be completely wiped out, along with all of its context variables.
That also means that the process instance is terminated. This termination happens even in the absence of the
<end-state> node since the process instance is bound to the conversation.
The description.xhtml page has the same two buttons as on the first screen. The cancel button behaves as
before. The next button, however, behaves differently. In the process of transitioning to the <page> node
named holeData, an action is executed. This behavior is the opposite of what you are used to from using
stateless navigation in that the action is executed after the transition is decided. The reason for this ordering is
to keep the method binding expressions out of the UI so that only the literal outcome value must be supplied in
the command button. As an alternative, you could use the method binding expression in the action attribute
of the command button:
<h:commandButton id="next" action="#{courseWizard.prepareHoleData}"
value="Next"/>
The prepareHoleData() method would need to return the string value next, which then allows the
<transition> to return to its basic form:
<transition name="next" to="holeData"/>
Now its time for some decisions to be made. Although the game of golf is designed in such a way as to
give men and ladies different par and handicap values, many courses don't make that distinction. Therefore, the
user will be allowed to enter the men's par and handicap data and then asked whether it is necessary to provide
different data for ladies. The answer to that question dictates whether or not to return to the holeData.xhtml
view to capture the additional information. This decision is handled by the decideHoleData node, which
either transitions back to the holeData <page> node or on to the next step in the flow (in this case,
teeSet):
<page name="holeData"
view-id="/coursewizard/holeData.xhtml" redirect="true">
<transition name="cancel" to="cancel"/>
<transition name="Men" to="decideHoleData">
<action expression="#{courseWizard.submitMensHoleData}"/>
</transition>
<transition name="Ladies" to="teeSet">
<action expression="#{courseWizard.submitLadiesHoleData}"/>
</transition>
</page>
<decision name="decideHoleData"
expression="#{courseWizard.ladiesHoleDataRequired}">
<transition name="true" to="holeData"/>
Licensed to Jaroslaw Gilewski <[email protected]o.pl>
<transition name="false" to="teeSet"/>
</decision>
Once again, we see the inverted actions that are applied during the transition from the Men and Ladies
outcomes. After the men's hole data is submitted, a decision has to be made as to whether to return to the
holeData.xhtml screen to capture data for the ladies. The command buttons on the holeData.xhtml page are
conditionally rendered based on the value of the gender context variable:
<h:commandButton id="nextMen" action="Men" value="Next"
rendered="#{gender eq 'Men'}"/>
<h:commandButton id="nextLadies" action="Ladies" value="Next"
rendered="#{gender eq 'Ladies'}"/>
The page flow continues on in this manner until the review screen, where the user can check the
information one last time before persisting it. The final two pages are defined as follows:
<page name="review"
view-id="/coursewizard/review.xhtml" redirect="true">
<transition name="cancel" to="cancel"/>
<transition name="success" to="end">
<action expression="#{courseHome.setCourseId(newCourse.id)}"/>
</transition>
<transition name="failure" to="review"/>
</page>
<page name="end" view-id="/Course.xhtml" redirect="true">
<end-conversation/>
</page>
The review screen assumes the use of an action handler in the UI command button, since the transitions
are setup to handle the outcome of that method:
<h:commandButton id="save" action="#{courseWizard.save}" value="Save"/>
Putting the method binding expression in the UI is acceptable here since the review screen is not likely to
change its purpose. It allow allows us to leverage the transition action to set the newly established id of the
course so that it can be displayed after the page flow is complete. The rendering of the course is handled by the
CourseHome component, bound to the courseHome context variable.
You have now completed your very first page flow! While the excitement is still present and page flows
are fresh in your mind, I want to address to additional features. First, let's talk about those pesky browser
buttons, back and refresh.
7.4.2 Backing up in the flow
If you have heard it asked once, you have heard it a hundred times. "Can we disable the back button?" Lucky
are those who are so blissfully unaware. It's a stateless world and we have to learn to live in it. Fortunately,
Seam specializes in bring state to that world and thus, you can appreciate that Seam has taken care of this
problem, not by disabling the back button, but by being smart enough to now when it is used.
If, during a page flow, the user attempts to return to an earlier page and resubmit the form, Seam will
gracefully redirect them to the current page—the <page> node where the process token is reseting in its
Licensed to Jaroslaw Gilewski <[email protected]>
"wait" state. That also applies to when the user hits the refresh button and the browser attempts to resubmit the
form. Of course, that problem has already been solved by performing a redirect during the transition, but it's
still nice to know that Seam has that base covered. This describes the default behavior of a page flow.
But, you may decide that it is alright for users to back up and change their answers. If you want to allow
this behavior as a way for users to check their work, I encourage you to bake it into the page flow. If you don't
want to go through that extra effort and there is no harm done in allowing them to resubmit forms that have
already been submitted, then you can enable back button by setting the back attribute on the <page> node to
enabled:
<page name="review" view-id="/coursewizard/review.xhtml"
redirect="true" back="enabled">
...
</page>
The only downside to this setting is that it allows the user to back up to any page leading up to this page.
Once you open the door, it remains wide open.
7.4.3 Delegating to subflows
In addition to a linear page flow, it is possible to spawn a sub-page flows. A sub-page flow is a good way to
synthesize a page flow from more elemental parts so as to enable sharing of common page flow logic (DRY).
In the current implementation, we assume that the course wizard is launched from the facility page so that it
can be immediately associated with the new course. If we want to allow the user to launch the flow from an
arbitrary point in the application and let them select a facility as the first step, we need to incorporate that logic
into the flow.
First, we change the addCourse() method to return an outcome based on whether a facility is available
and has thus been selected:
@In(required = false)
private FacilityHome facilityHome;
@Begin(pageflow = "courseWizard")
public void addCourse() {
newCourse = new Course();
newCourse.setFacility(facilityHome.getDefinedInstance());
return newCourse.getFacility() != null ?
"facilitySelected" : "facilityNotSelected";
}
Next, we modify the page flow to handle this initial outcome and transition into the sub-process for
selecting a facility if necessary:
<start-state>
<transition name="facilitySelected" to="basicCourseInfo"/>
<transition name="facilityNotSelected" to="selectFacility"/>
</start-state>
<process-state name="selectFacility">
<sub-process name="selectFacility"/>
<transition to="basicCourseInfo"/>
</process-state>
Licensed to Jaroslaw Gilewski <[email protected]>
When the selectFacility sub-process ends, the process will transition to the basicCourseInfo
<page> node. Of course, its possible to do more sophisticated logic using transition actions and <decision>
nodes following the return to the page flow.
All that is left is to build the selectFacility process, which is defined in the selectFacility.jpdl.xml
descriptor. Note that you will need to declare this descriptor in component descriptor above the page flow
definition in which it is used:
<bpm:jbpm>
<bpm:pageflow-definitions>
<value>selectFacility.jpdl.xml</value>
<value>courseWizard.jpdl.xml</value>
</bpm:pageflow-definitions>
</bpm:jbpm>
The selectFacility page flow takes advantage of the FacilityList.xhtml page, which allows the user to
search, sort and paginate to find a facility to select.
<pageflow-definition name="selectFacility">
<start-state>
<transition to="facilityList"/>
</start-state>
<page name="facilityList"
view-id="/FacilityList.xhtml" redirect="true">
<transition name="select" to="end">
<action expression="#{courseWizard.assignFacility}"/>
</transition>
</page>
<end-state name="end"/>
</pageflow-definition>
Note that the <end-state> is used in this page flow to signal the of the process and return to the parent
flow. We do not want to end the conversation since the main page flow is not yet complete. The
assignFacility() method merely grabs the defined facility instance from the FacilityHome
component and assigns it to the newCourse instance:
public void assignFacility() {
newCourse.setFacility(facilityHome.getDefinedInstance());
}
What's nice about having split off the select facility logic is that it allows you expand that part of the flow
in the future to perhaps allow the user to create a new facility instead of selecting an existing one. As far as the
main flow is concerned, that's all just low-level details.
That wraps up the introduction to page flows. And what an introduction it has been. In this tutorial, you
have had a chance to see a couple of the more advanced features. Trust that there is a wealth of additional
features that were not covered here that are left for you to discover, including the ability to execute actions in
more places, configure fall back pages when the conversation times out, set the timeout per page, end tasks,
initiate a business process, and even tap into the native extension points of the jPDL. You can seriously
micromanage the user's interaction with your system using page flows. The XML can get quite verbose, but
then again, if power is what you are after, it may be worth the trouble.
Licensed to Jaroslaw Gilewski <[email protected]>
Remember, though, that your user is a person and people like to multi-task. The popularity of browser tabs
reflects this fact. Just because the page flow represents the most logical progression through the application
doesn't mean the user is necessarily going to follow it without interruption. In the next section you learn how to
allow the user to step out of a conversation to perform other tasks. Then, in section 7.6 you learn to create
widgets that give the user the ability to switch between parallel conversations that have been spawned.
7.5 Stepping out of the conversation
So far we have talked about propagating the conversation, but what about the case when you want to disable
propagation? Disabling propagation is not the same thing as ending a long-running conversation. Instead, you
can think of it as abandoning the existing conversation, or stepping outside its bounds.
7.5.1 Abandoning a conversation
You may be wondering why you would want to avoid propagating a conversation. The need is actually more
common than you think. While the stateful behavior that conversations provide is a drastic improvement over
the stateless approach, sometimes the conversation can have too good of a memory. After you take a bad shot
in golf, you are told to let it go. But it's easier said than done. While you are trying to focus on your next shot,
you just cannot seem to shake off the memory of the prior shot. Conversations can be the same way.
Sometimes you just want to get away from one, yet it just wants to keep holding on. This section shows you
how to stop it in its tracks so that you can move on to the next use case.
Let's assume there you want to put a link to start the course wizard on a page that uses a conversation, such
as the CourseList.xhtml page or perhaps even somewhere in the middle of the course wizard itself. When you
start the wizard, you don't want to keep using the same conversation. Instead, you want to leave the existing
conversation so that a new conversation will be created to handle the page flow. For now, don't worry about
how to get back to the partially complete wizard as you will learn later how to do that using a conversation
switcher. Let's just break ties with that conversation without looking back. Once again, we can use the
<s:conversationPropagation> tag, only this time we are using it to prevent conversation propagation:
<h:commandLink action="#{courseWizard.addCourse}">
<s:conversationPropagation propagation="none"/>
Add course...
</h:commandLink>
Being the wise and savvy Seam developer that you are, you decide to cut down on keystrokes and use the
<s:link> tag to trim the size of the markup and make a bookmark-friendly link:
<s:link action="#{courseWizard.addCourse}" propagation="none"
value="Add course..."/>
The none directive is necessary in cases where the conversation token is added automatically, such as
with command components and Seam link components. Another way to get away from the current longrunning conversation is to use the no-arguments leave() method on the built-in conversation component in
an action listener. This method can be used as an action listener thanks to the Seam extended EL. Calling this
method has the same effect as the none propagation directive:
<h:commandLink actionListener="#{conversation.leave}"
action="#{courseWizard.addCourse}">
Add course...
Licensed to Jaroslaw Gilewski <[email protected]>
</h:commandLink>
Non-JSF links have no awareness of the current long-running conversation, so abandoning a conversation
in those cases is just a matter of leaving off the <s:conversationId> tag.
When you just cannot seem to shake a conversation, setting the propagation directive to none will be sure
ward it off. When a conversation is abandoned, it becomes subject to conversation timeouts.
If you get to the end of the use case and no longer need the current long-running conversation, it's usually
best to end the conversation properly, rather than abandoning it. However, if you just want to put the
conversation to the side for the moment, then abandoning the conversation is the appropriate choice. But,
before you go abandoning conversations, consider whether it is more appropriate to suspend the current longrunning conversation by nesting a new long-running conversation within it. That way, when the nested
conversation is finished, you can return to the parent conversation since the association is still established. You
will see that this mechanism is almost identical to how sub-page flows work.
7.5.2 Creating nested conversations
Nested conversations allow you to isolate context variables within the scope of a conversation in the same way
that conversations allow you to isolate context variables within the session scope.
If you are familiar with how child processes work on a Unix system, you will find that nested
conversations have similar semantics and share the same relationships with their parents. When you begin a
nested conversation, you are effectively suspending the state of the active long-running conversation and
starting a brand new long-running conversation with its boundaries. What makes this nested conversation
different from just another primary conversation is that it has read-only access to the context variables in its
parent, just as a child process inherits the environment of its parent. Another similarity that these conversations
share with child processes is that changes made to the nested conversation do not disrupt the state held in the
parent conversation. Finally, conversations can be nested to arbitrary depth, opening up the possibility that a
parent of a nested conversation can itself be a nested conversation.
Branching off the mainline
Nested conversations begin by branching off the main conversation, as shown in figure 7.7. As you can see,
multiple nested conversations can exist concurrently within a parent conversation.
Figure 7.7 Nested conversations are branched off of the main long-running conversation.
Figure 7.7 also highlights the three ways to leave a nested conversation. The conversation can be
terminated by:
z
using an end propagation directive
z
disabling propagation or using the browser's "back" button
z
ending the parent conversation (which terminates its nested conversations)
Licensed to Jaroslaw Gilewski <[email protected]>
When the nested conversation is terminated or abandoned, the main conversation is popped back into
place. Not only is it possible to return to the main conversation when the nested conversation and related page
flow ends, but you can also restore the last page visited by the main conversation.
This conversation stack "memory" takes a lot of burden off your plate of trying to keep track of where the
user has been and to where they should be routed when they come off a tangent. Let's consider when you
might use a nested conversation.
The need for nested conversations
You typically a use nested conversation when you want to allow the application to move along a tangential
page flow without affecting the main conversation. When determining if a nested conversation is necessary,
you keep the end in mind. If you need the application to perform a task within the boundaries of a
conversation, but you don't want to destroy the conversation-scoped data that is available when the task begins,
then it is appropriate to descend into a nested conversation.
Let's consider the example of comparing courses. Assume that while comparing courses side-by-side, the user
recognizes that the information about one the courses is incorrect. The user disables comparison mode and
selects the course of interest with the intention of updating that course. When the course information is
overlaid on the form, a conversation is started to keep a reference to the course instance and the persistence
context that manages it. When the save button is activated, the course is updated, synchronized with the
database, and the conversation is ended. Unfortunately, this means that the course comparison selections are
also wiped out.
To prevent the course editor from affecting the active long-running conversation, you want to instead use a
nested conversation for this purpose. A nested conversation is started when the nested propagation directive is
encountered.
The CourseEdit.page.xml descriptor, which was generated by seam-gen for controlling the course editor
page, begins a conversation upon entry:
<begin-conversation join="true"/>
In order to make the editor perform its work within a nested conversation, we switch to using the nested
propagation directive:
<begin-conversation nested="true"/>
If there is no long-running conversation available at the time, the nested directive has the same affect as
the begin directive. However, you cannot join and nest at the same time, so each time the user performs a
postback, it is going to spawn yet another nested conversation. We prevent Seam from beginning a nested
conversation using a conditional, only beginning a nested conversation if one is not already active:
<begin-conversation nested="true" if="#{!conversation.nested}"/>
When the user submits the editor form, the nested conversation is ended and the parent conversation is
restored as the active long-running conversation, thus allowing the user to continue comparing the same set of
courses that were previously selected.
One thing to note is that a nested conversation cannot modify the context variables in the parent
conversation, but it does have read access to them. This is done to protect the integrity of the parent
conversation while the nested conversation is in play.
Licensed to Jaroslaw Gilewski <[email protected]>
The most critical part of letting the user go off on a tangent is to return them to the correct spot when they
are done. If the navigation is unpredictable, the user will be afraid to leave the current page. Let's look at how
to close the nested conversation and return back to its parent.
Popping out of a nested conversation
When a nested conversation is ended, Seam "pops" the conversation stack and the parent conversation is once
again active in read-write mode. A nested conversation is ended in exactly the same was as a primary
conversation is ended, by using a variant of the end propagation directive. Rather than terminating the longrunning conversation outright, the nested conversation is flushed and the parent conversation is restored as the
active long-running conversation. On the other hand, if the parent conversation where to be terminated first,
then all nested conversations which it owns will also be terminated.
Left up to your own home-grown logic, you may find it is difficult to determine where to navigate when
the nested conversation is ended. To help your application find its way, Seam provides a built-in method on the
conversation component, bound to the conversation context variable, that can be used to terminate the
nested conversation and redirect back to the page from which the nested conversation was spawned.
Using the restore page feature of nested conversations is most useful when you are developing a
breadcrumb-link interaction. For instance, let's assume that you want to allow users to branch off of the detail
page of a course to view related courses. From the page of the related course, they may repeat the process. To
allow them to go back to the previous course and back up the chain—without needing to use the browser back
button—you can use nested conversations.
Let's assume there is a context variable nearbyCourses that is used to display a list of courses in close
proximity to the current course. To allow the user to navigate to one of these courses, you place links on the
page:
<ui:repeat var="_course" value="#{nearbyCourses}">
<s:link view="/Course.xhtml" value="#{_course.name}"
propagation="nested">
<f:param name="courseId" value="#{_course.id}"/>
</s:link>
</ui:repeat>
Notice the use of the nested propagation directive. When the user visits the nearby course, it is now in the
context of a nested conversation. On the course detail page, we can now add a link that allows them to return to
the previously viewed course if a nested conversation is detected:
<s:link action="#{conversation.endAndRedirect}"
value="Return to previous" rendered="#{conversation.nested}"/>
The built-in action to switch back to the parent conversation is the first example you have seen of
switching between conversations. Except, in this case, the nested conversation is simultaneously being
terminated. In order to toggle between conversations, you have to be willing to temporarily leave one
abandoned as you move to the next one. When you do that, you need to give the user controls to view the other
conversations and hence return to them. That is where the conversation switchers come into play.
7.6 Switching between conversations
Abandoning a conversation may sound remiss, but it can actually be a very powerful tool for allowing the user
to multitask. When a conversation is abandoned, it's not lost forever. It's just sitting behind the scenes waiting
Licensed to Jaroslaw Gilewski <[email protected]>
to be rediscovered. Until an abandoned conversation reaches its timeout period, it is possible to restore it using
a conversation switcher widget. Switching between existing long-running conversations in the same browser
window is referred to as workspace management. In this section, you are introduced to workspaces, how they
are defined, and how you can allow the user to toggle between them.
7.6.1 The conversation as a workspace
To emphasize a point I made earlier, conversations are more than just a context. You will now learn that a
conversation can also represent a user's workspace. A workspace is a named conversation. But if there was
only one workspace, there wouldn't be much need for a name and we would just call it a conversation. The
distinction arises when the user has more than one parallel conversations going at once. The browser can only
display one page at a time and thus only focus on one conversation at a time. (JavaScript Remoting is an
exception to this rule, which is covered in chapter 12).
Workspaces are useful for two reasons. First, they allow a user to pause the current task and pick up with
something else with the intention of picking up with the original task later. For instance, while working
through the course wizard, the user may need to return to the directory to lookup information on another
course. Instead of having to open another browser tab, the user can switch tasks from within the application
without losing the progress made thus far in the course wizard. In this sense, the workspace gives the
application native support for tabs, sanctioning the usage scenario that the user is going to do anyway.
A workspace is also useful for limiting the number of active long-running conversations. Because users
are going to inevitably perform ad-hoc navigation, conversations are going to be abandoned inadvertently. By
presenting the user with a widget that allows them to acknowledge the presence of these abandoned
workspaces, you encourage the user to once again given them attention and perhaps end them cleanly.
As you have learned, the application tracks and restores conversations using a conversation token, which
passes along the value of the conversation id. The task of switching workspaces is going to be lost on the user
if you require them to specify a numeric id to continue a conversation. Instead, you need to be able to provide a
friendly name so that they recognize the workspace, thus giving them motivation to return to it. But names are
more than just friendly. A conversation is not a workspace unless it has a name assigned to it.
7.6.2 Giving conversations a name
To name a conversation, you assign a name to each view ID that is used within the context of a long-running
conversation. This point may be a bit counter-intuitive at first. Why would you name the page rather than the
conversation itself?
If you step back and think about it, the state of a conversation changes over the course of its use. By
naming the conversation only once, when it is created, that name will be too general, failing to reflect the
"current" state of the conversation. Conversations are really shaped by the current page and the navigation that
brought the user to that point. Even then, a page name may not help them to understand the context of that
page in the conversation. Thus, page names can be defined using the EL, allowing you to tap into the
conversation working set to derive a contextual name.
Page names can be assigned both in the stateless navigation model and the stateful navigation model. In
both cases, the name is defined using a nested <description> tag within the <page> node. The name of
the workspace is dynamic, updated to reflect the last page that has a description. Here, the terms name and
description are used interchangeably.
Let's start by focusing on the stateless model. For the course comparison example, a description is
assigned to each of the <page> nodes in the various page descriptors, shown here populated with the viewid attribute for clarity:
Licensed to Jaroslaw Gilewski <[email protected]>
<page view-id="/CourseList.xhtml">
<description>
Course search results (#{courseList.resultList.size})
</description>
</page>
<page view-id="/CompareCourses.xhtml>
<description>
Compare courses (#{courseComparison.nameList})
</description>
</page>
Page nodes in the stateful navigation model can also be assigned names. It is especially important to assign
descriptions in the stateful model so that it is clear which node is current in the "wait" state:
<pageflow-definition name="courseWizard">
...
<page name="basicCourseInfo"
view-id="/coursewizard/basicInfo.xhtml" redirect="true">
<description>
Course wizard (New course @ #{newCourse.facility.name}):
Basic information
</description>
...
</page>
<page name="description"
view-id="/coursewizard/description.xhtml" redirect="true">
<description>
Course wizard (#{newCourse.name}): Description
</description>
...
</page>
...
<page name="review"
view-id="/coursewizard/review.xhtml" redirect="true">
<description>Course wizard (#{newCourse.name}): Review</description>
...
</page>
...
</pageflow-definition>
The description of a page is evaluated just prior to being rendered. You can see where this happens in the
Seam life cycle by looking at table 3.2 in chapter 3. The description is then stored in the conversation. If the
conversation is abandoned, the description will remain fixed so that when the list of conversations is presented
to the user, the description will reflect its last known state.
Allowing the switch to occur
Before we get into the conversation switcher components, it is important to know what makes a workspace a
candidate for switching. In Seam terms, what makes a workspace "switch enabled"?
First, the user must encounter a page with a description to promote a conversation to a workspace. If the
user does not visit a page that has a description within the context of a given conversation, that conversation
cannot be assigned a name and therefore cannot be restored using a workspace switcher.
Licensed to Jaroslaw Gilewski <[email protected]>
To return a given view ID when the workspace is restored, the page must allow switching, which is
controlled via the <page> node (in both the stateless and stateful models). If the view ID does not support
switching, then the last view ID that does will be restored. Switching is disabled using the switch attribute on
the <page> node:
<page view-id="/FacilityList.xhtml" switch="disabled">
<description>Select a facility</description>
...
</page>
The <page> can still update the name of the workspace, it is just not used when the user is returned to the
workspace. The next step is creating a UI control that presents the user with a list of workspaces and allows
them to select one, thus making it the active workspace, or conversation. Seam includes a handful of built-in
components that aid in creating such a control.
7.6.3 Using the built-in conversation switchers
Workspaces are a new concept in web applications and it is going to take some time for developers to pick up
on them. To encourage their use, Seam provides several built-in conversation switchers that you can drop into
your application with very little effort. Seam provides a simple select-one menu switcher, an advantage tablebased switcher, and an unordered list of breadcrumbs. The first two list all active, parallel conversations and
the later represents just the active conversations stack. Let's start simple.
The basic conversation switcher
Seam's built-in conversation switcher component, switcher, is a ready-made component intended to be used
by a UISelectOne JSF component, such as <h:selectOneMenu>. Listing 7.2 shows an example of how
the switcher component is used in a JSF template.
Listing 7.2 A Seam conversation switcher
<h:form>
<h:selectOneMenu value="#{switcher.conversationIdOrOutcome}">
<f:selectItems value="#{switcher.selectItems}"/>
</h:selectOneMenu>
<h:commandButton action="#{switcher.select}" value="Switch"/>
</h:form>
#1
#2
#3
<Annotation #1> The current conversation
<Annotation #2> The set of conversations
<Annotation #3> The action that initiates the switch
The value of each select option that Seam generates is a conversation id. The names are the conversation
descriptions. When the action handler is invoked, Seam uses the selected value to lookup the conversation and
redirects to it, abandoning the previous conversation in the process.
HINT
Notice that I did not use <s:link> or <s:button> to invoke the action handler of the switcher
component. In order for this component to work, the form must be submitted so that the value selected in
the UISelectOne component can be captured. The <s:link> and <s:button> components do not
submit the form, even if they are enveloped within it.
Licensed to Jaroslaw Gilewski <[email protected]>
You can also tack on your own options, which is where the OrOutcome part of the method becomes
relevant. If the selected value is not numeric, then the action handler will return the selected value as a logic
outcome so that the standard navigation rules take affect. Let's add an outcome for the user to return to the
homepage and an outcome to start a new course entry wizard.
<h:form>
<h:selectOneMenu value="#{switcher.conversationIdOrOutcome}">
<f:selectItem itemLabel="Return home" itemValue="home"/>
<f:selectItem itemLabel="Enter new course" itemValue="addCourse"/>
<f:selectItems value="#{switcher.selectItems}"/>
</h:selectOneMenu>
<h:commandButton action="#{switcher.select}" value="Switch"/>
</h:form>
To support the addCourse outcome, you will need to add a global navigation rule that launches the user
into the course entry wizard. In this case, the join isn't really necessary since the switcher disables conversation
propagation.
<page view-id="*">
<navigation from-action="#{switcher.select}">
<rule if-outcome="addCourse">
<begin-conversation/>
<redirect view-id="/coursewizard/basicInfo.xhtml"/>
</rule>
</navigation>
</page>
[[screenshot]]
That wraps up what you can do with the switcher component. It is effective in switching between
conversations, but it leaves a little to be desired. For one, it can only show the description, even though a
conversation entry has a lot more useful information that may help the user understand the situation.
Additionally, it does not allow the user to end conversations. Both of these features are supported by the builtin conversation list component.
A more powerful conversation switcher
Seam maintains a list of all long-running conversations and metadata about each one. Seam exposes this list as
a collection of workspaces via the built-in Seam component, conversationList. This list is built from the
collection of conversation entries maintained by the built-in ConversationEntries component, which can
be used to develop your own custom switcher. Each entry in this collection has a lot more information than just
the conversation id and description. The properties of a conversation entry, or workspace, are provided in table
7.6.
Table 7.6 The properties of a single conversation entry that may be used when displaying the conversation list.
Property
Type
Description
id
String
A value that identifies this conversation. This value is typically numeric, though business
key identifiers are also supported.
description
String
The descriptive name of the conversation evaluated from the expression value specified
in the description node of the page entry.
current
boolean
A flag indicating whether or not this conversation entry is the current conversation.
Licensed to Jaroslaw Gilewski <[email protected]>
viewId
String
The last JSF view ID that was rendered while this conversation was active.
displayable
boolean
A flag indicating whether this entry is displayable, meaning it must still be active and it
must have a description. The conversationList component automatically excludes
all entries that are not displayable.
startDatetime
java.util.Date
The timestamp when this conversation began.
lastDatetime
java.util.Date
The timestamp when this conversation was last restored.
lastRequestTime
long
The timestamp when this conversation was last restored.
timeout
Integer
The timeout period that must be elapsed after its last use for this conversation to be
automatically garbage collected.
nested
boolean
A flag indicating whether or not this conversation is nested.
ended
boolean
A flag indicating whether or not this conversation has ended.
removeAfterRedirect
boolean
A flag indicating that this conversation will be removed immediately after a redirect.
While you may not want to display all of these properties to the user, they can be useful when deciding
how to display the list. In addition to these properties, each conversation entry also has several built-in action
handlers. Table 7.7 lists the action handlers and their purposes.
Table 7.7 Action handlers on a single conversation entry that act on the selected conversation.
Action handler method
Purpose
select()
Selects the conversation entry, making it the current conversation, and redirects to the last rendered
view ID when that conversation was active. The previous conversation is abandoned.
destroy()
Selects the conversation entry and ends that conversation. The previous conversation is abandoned, so
it is necessary to select it again, if it still exists.
Armed with these properties and action handlers, you are ready to construct your advanced workspace
control. Here we will use the UIData component, <h:dataTable>, to provide a list of active conversations.
Alternatively, you can use any UIData component of your choice. The conversations are sorted based on the
last time they were used, with the most recent conversations appearing first.
<h:form id="workspaces">
<h:outputText value="There are no active workspaces"
rendered="#{empty conversationList}"/>
<h:dataTable value="#{conversationList}" var="entry"
rendered="#{not empty conversationList}">
<h:column>
<f:facet name="header">Workspaces</f:facet>
<h:commandLink action="#{entry.select}" value="#{entry.description}"
rendered="#{not entry.current}"/>
<h:outputText value="#{entry.description}"
rendered="#{entry.current}"/>
</h:column>
<h:column>
<f:facet name="header">Last used</f:facet>
<h:outputText value="#{entry.lastDatetime}"
rendered="#{not entry.current}">
<s:convertDateTime type="time" pattern="hh:mm a"/>
</h:outputText>
<h:outputText value="current" rendered="#{entry.current}"/>
</h:column>
<h:column>
Licensed to Jaroslaw Gilewski <[email protected]>
<f:facet name="header">Action</f:facet>
<h:commandLink action="#{entry.select}" value="Select"
rendered="#{not entry.current}"/>
<h:commandLink action="#{entry.destroy}" value="Destroy"/>
</h:column>
</h:dataTable>
</h:form>
[[considering the use of cueballs]]
When a command link in one of the rows is activated appropriate action handler method, either
select() or destroy(), is called on the conversation entry associated with that row. JSF is able to locate
the appropriate conversation entry to invoke because the conversationList is a page-scoped component
and is therefore available on a JSF postback because it is stored along with the component tree.
HINT
If you want to use <s:link> or <s:button> to operate on the entry, you will need to use bijection to
expose the conversationList as a JSF DataModel using @DataModel and then capture the
selected row using @DataModelSelection. You will need to move the select() and destroy()
action handlers to your custom component, and then have them delegate the work of selecting or destroying
the selected entry to the entry itself. The only downside is that your custom component becomes tightly
coupled to the Seam API from having to import, and operate on, the ConversationEntry class.
The select() action handler on the conversation entry works the same as the equivalent method on the
switcher component. Seam will issue a redirect to the last used view ID. The destroy() component is a
bit tricker. When the destroy() method is invoked, Seam will switch to the selected conversation and issue
an end operation with the before redirect flag disabled.
If you put the conversation switcher on all screens, you may run into the situation where you are trying to
destroy the conversation that is currently active, which can lead to bizarre behavior. You have two options.
You either disallow terminating the current conversation or you have to put some navigation rules in place to
handle the event gracefully.
Without any navigation rules in place, JSF will attempt to restore the same view again, with the terminated
conversation lingering on during the rendering of that view. The result can be very bizarre because the screen
may still show data from that conversation, even though it is no longer available. To ensure that the
conversation is ended prior to rendering the page again, you will want to install the following global navigation
rule.
<page view-id="*">
<navigation from-action="#{entry.destroy}">
<end-conversation before-redirect="true"/>
<redirect/>
</navigation>
</page>
The <end-conversation> tag is not redundant. It is there to change the semantics of how the
conversation is ended. The empty <redirect> tag simply issues a redirect to the same view ID. With this
rule in place, destroying a workspace will result in the page being rendered with no conversation active. A
conversation-required on the page being redisplayed can prevent any undesirable behavior.
Unfortunately, without installing some of your own logic, it is not possible to restore the previous conversation
that was active when the destroy() method was triggered using the built-in component.
Licensed to Jaroslaw Gilewski <[email protected]>
NOTE
The only downside of switching to another conversation is that any non-submitted form data will be lost.
You can work around this issue by using an Ajax component library such as Ajax4jsf that can periodically
synchronize the state of a form with the server. That way, when it comes time to abandon a conversation,
the partially complete form data is already preserved in the server-side model.
Workspaces truly are an innovative concept. Their use can cut down on the proliferation of tabs in the
user's browser because the user will not feel like they are "losing their place" by taking a temporary detour.
TIP
The conversation switcher is a great way to test conversation timeouts. Open two tabs in your browser, one
that you are using to test the screens in the application and one which displays the list of workspaces. Use
the workspace tab to destroy the conversation that is active in the application tab to emulate the
conversation reaching its timeout point.
The conversation switcher just shown displays both top-level and nested conversations. It is also possible
to create a switcher that moves solely along the ancestral chain of a series of nested conversations using the
conversation stack component.
Tracing your steps with breadcrumbs
Breadcrumb navigation complements switching between parallel conversations. Each breadcrumb represents a
point where the conversation was branched to create a nested conversation. Since conversations can be nested
to arbitrary depth, it is possible to have a long chain of breadcrumbs from which to choose. Seam makes
generational conversations available via the built-in conversationStack component.
Your application must support a nested conversation model for this component to be populated.
Navigating between related golf courses is a perfect use case for this component. We will use the Facelets tag
<ui:repeat> to create a delimited linear chain.
<h:form id="breadcrumbs">
<s:fragment rendered="#{not empty conversationStack}">
Trail:
<ui:repeat value="#{conversationStack}" var="entry">
<h:outputText value=" > " rendered="#{entry.nested}"/>
<h:commandLink action="#{entry.select}" value="#{entry.description}"
rendered="#{not entry.current}"/>
<h:outputText value="#{entry.description}"
rendered="#{entry.current}"/>
</ui:repeat>
</s:fragment>
</h:form>
NOTE
If the conversation is not nested, then the conversation stack will have only one entry. If you don't want to
display the conversation stack in this case, you can use the #{conversation.nested} expression to
enable a conditional check.
As you can see, the conversation stack is used in exactly the same way as the conversation list component.
In fact, the only difference is that it just consists of hierarchical entries rather than parallel conversations. You
could provide a destroy() action as well, which would terminate the selected entry and all of its
descendants.
Licensed to Jaroslaw Gilewski <[email protected]>
While Seam provides you with built-in components to either switch between parallel conversations or
ancestor conversations, it does not include a component that allows you to view both simultaneously. Clearly,
providing such a workspace switcher would provide ultimate control over the existing workspaces. Let's tap
into the Seam's conversation API to make it happen.
7.6.4 Taking control of the current conversation
With all the attention on the other active conversations, the current conversation—the one servicing the current
request—is feeling left out. Seam gives you a wealth of properties and controls that are related to the current
conversation, just as it does for each entry in the collection of active long-running conversations. Having
information about the current conversation can help tremendously when making decisions about navigation or
rendering markup on the page. The built-in controls can be useful as page actions or to serve as action listeners
for links and buttons. These methods offer another alternative for controlling the boundaries of the current
long-running conversation.
You can access information about the current conversation, not surprisingly, by using the conversation
component. You have already seen a couple of this component's properties used in this chapter. The value
expression #{conversation.id} was used to reference the id of the current long-running conversation in
order to propagate the conversation across a non-JSF request. Table 7.8 provides a complete list of properties
that are available on this component.
Table 7.8 The properties of the current conversation available under the context variable named conversation.
Property
Type
Description
id
String
A value that identifies this conversation. This value is typically numeric, though business key
identifiers are also supported.
parentId
String
The conversation id of the parent conversation of this nested conversation.
rootId
String
The conversation id of the primary (top-level) conversation of this nested conversation.
description
String
The descriptive name of the conversation evaluated from the expression value specified in the
description node of the page entry.
viewId
String
The last JSF view ID that was rendered while this conversation was active.
timeout
Integer
The timeout period that must be elapsed after its last use for this conversation to be
automatically garbage collected.
longRunning
boolean
A flag indicating whether or not this conversation is long-running. Conversations scheduled to
be removed after a redirect are not considered to be long-running.
nested
boolean
A flag indicating whether or not this conversation is nested.
The conversation component also has a number of built-in action handlers that can be used to control the
current conversation, or to switch to a related conversation. These action handlers are listed in table 7.9.
Remember, these action handlers can be conveniently used as page actions defined on page nodes in the
pages.xml configuration.
Table 7.9 Action handlers on the conversation context variable that can be used to control the current conversation.
Action handler method
Purpose
redirect()
Switch back to the last known view ID for the current conversation.
endAndRedirect()
End the current nested conversation and redirect to the last known view ID for its parent conversation.
endBeforeRedirect()
End the current conversation and set the before redirect flag to true. Does not trigger an automatic
redirect.
end()
End the current conversation and set the before redirect flag to false. Does not trigger an automatic
redirect.
Licensed to Jaroslaw Gilewski <[email protected]>
leave()
Step out of the current conversation. A new temporary conversation will be initialized and used for the
duration of the request.
begin()
Start a new long-running conversation if one is not already active. This method is the equivalent of
setting join to true.
reallyBegin()
Start a new long-running conversation regardless if one already exists. This method is equivalent to
setting join to false.
beginNested()
Start a nested conversation by branching off of the current long-running conversation. If a long-running
conversation is not active, an exception will be thrown.
pop()
Switch to the parent conversation, leaving the current conversation intact. Does not trigger a redirect.
redirectToParent()
Switch to the parent conversation, leaving the current conversation intact, and redirect to the last known
view ID for the parent conversation.
root()
Switch to the root level conversation, leaving the current conversation intact. Does not trigger a redirect.
redirectToRoot()
Switch to the root level conversation, leaving the current conversation intact, and redirect to the last
known view ID for the root conversation.
There are likely more controls here than you may ever use. But that is a good thing, because it means that
if you have some unique circumstance that requires you to manipulate the current conversation, or switch to a
related conversation, the groundwork has already been laid.
Conversation switching is likely going to be a new, yet surprisingly refreshing, experience for the user. If
instrumented correctly, they will not have to resort to the crutch of multiple tabs to be productive. Instead, you
can bring the power of tabs into the application by supporting multiple workspaces and providing the user with
a control to move between them.
7.7 Summary
Users become frustrated when their story is forgotten. It forces them to have to go over the same information
again and again to keep the application up to speed, rather than enabling them to pick up where they last left
off. As a result of this failure to track state, the user is kicked back to the starting point and ready to hang up on
your application. That is the void conversations are designed to fill.
Conversations are one of the crowning features of Seam. They reach into almost every aspect of the
framework. That explains why it has been so difficult to refrain from presenting the material in fragments up
until now. Hopefully, those missing pieces came into line for you in this chapter.
You learned in this chapter how conversations are more than just a context. Instead, they are units of work
from the perspective of the user. At times, a unit of work may only span one request, which Seam supports
using a temporary conversation. The temporary conversation ensures that context variables scoped to the
conversation are held until the response is rendered, even if the life cycle is unhinged by a redirect—known as
the "flash" scope in some other frameworks.
That discussion gave rise to the long-running conversation, which extends the conversation to span
multiple pages, as dictated by well defined boundary points. You learned that long-running conversations are
formed by promoting a temporary conversation to long-running when a "begin" conversation propagation
directive is encountered. You studied the wide variety of propagation directives, which include annotations, in
the pages configuration, using UI component tags, or via the Seam API. You came to understand that a
conversation is really just a segment of the HTTP session and that the reference to that memory segment is
passed along as a token in the request. The process of how a long-running conversation ends was explained,
which happens either by demoting it to a temporary conversation when an "end" conversation propagation
directive is encountered or because it times out from lack of use, and automatically garbage collection
mechanism. What you were able to take away from this discussion is that it is no longer your responsibility to
manage stateful data tucked away in the HTTP session.
Licensed to Jaroslaw Gilewski <[email protected]>
The discussion then turned from singular to plural as you learned that you can have multiple conversations
going at once, either sharing a nested relationship or isolated from one another in the session so as to prevent
interference. This discussion gave rise to the concept of workspaces, which you learned represent Seam's
awareness of concurrent conversations.
Knowing that users of your application are multitaskers, you learned how to extend the concept of a "unit
of work" to be multiple units of work by leveraging Seam ability to switch between workspaces. This feature
enables you to provide your users with conversation switching controls so that they can pick up where they left
off amongst the parallel units of work. While Seam provides a number of built-in conversation controls, you
learned to tap into the rich conversation API and the RichFaces component palette to create a custom
conversation switcher control.
By no means did this chapter exhaust everything there is to say about conversations. This chapter is just
the beginning. But it certainly builds a strong foundation. What you have learned in this chapter is close to
everything you need to know about controlling conversations. From here on out, you are going to learn more
about what you can do to use conversations. Conversations play a central role in Seam's managed persistence
support, providing an ideal place to store the persistence manager to take advantage of its in memory
representation of the database. Before you can learn how conversations and persistence play off of one another,
you first need to take a lesson in understanding Java persistence so as to develop a vocabulary that is used to
discuss Seam's pioneering work in the persistence field.
Licensed to Jaroslaw Gilewski <[email protected]>
8
Understanding Java persistence
Java persistence is the mechanism by which entities are exchanged between the Java runtime environment and
a relational database. Java persistence is undoubtedly the most popular feature of the Java EE platform,
perhaps even the Java language. This popularity can be attributed to the fact that persisting data is central to all
enterprise applications.
This chapter gives an overview of Java persistence and prepares you to use it with the Seam framework. It
covers both the Java Persistence API (JPA)—the standard persistence mechanism in Java EE—and
Hibernate—the popular open source persistence framework and now JPA implementation. You can't get far
with using persistence in the absence of explicit transaction boundaries, so this chapter also covers the role
transaction plays in the Java persistence mechanism.
Persistence is such a fundamental feature of Seam that you cannot get very deep into a Seam application
without encountering it. In fact, you have used persistence already, starting all the way back in chapter 2, but
have not yet studied it in depth. In the last chapter, you learned how conversation can be used to extend state
over a series of pages. One of the missing pieces in that discussion is the concept of extending the persistence
context. Starting with this chapter and continuing into the next, you are going to learn what role conversations
play in managing persistence and why it is such and important combination in Seam. Trying to cover all
aspects of transaction and persistence in just a handful of chapters would be difficult, though. There are already
several books available that explain the fundamentals of transactions and persistence using JPA or Hibernate in
great detail. I highly recommend Java Persistence with Hibernate and also EJB 3 in Action, JPA 101 Java
Persistence Explained and Spring in Action, Second Edition.
Once you tackle the core concepts, you will be ready to make the call between JPA and native Hibernate,
both of which are well integrated into Seam. The JPA support has a understandable bias towards the Hibernate
implementation of JPA given that Hibernate is a sibling project of Seam. Before the chapter is through, a
comparison of the two will be conducted. But, given that the APIs of the two frameworks are so similar, we
can establish a generic persistence terminology that allows us to address both APIs at a higher level. By the
time you finish this chapter, you will be compelled to discover what Seam brings to the table with its own
transaction and persistence management.
8.1 Java persistence principles
Persisting data, and doing it consistently and reliably, is vital to enterprise business applications. It is so
important that JPA and Hibernate have become the most widely used, and arguably the most successful,
frameworks in Java. But, transactions and persistence are complex subjects. They are both technically
challenging—to the point of being academic—and they can be very difficult to manage and tune. The
complexity is magnified by the fact that there is a lot of misinformation out there. Once you start down the
wrong path, it can be very difficult to correct your approach. In my opinion, developers should not expect to
sprinkle magic pixie dust around POJOs in hopes that they will become persistent without understanding the
underlying mechanisms. The persistence framework is intended to make the process easier, not do the work for
you.
Licensed to Jaroslaw Gilewski <[email protected]>
This shallow, magic pixie dust approach is what landed those folks spreading fear, uncertainty, and doubt
(FUD) about Hibernate and JPA into such hot water. In contrast to what may have been reported, these
frameworks are stunningly adept at handling and optimizing persistence operations. Sadly, developers are
doomed from the start because of problems that stem from the stateless architecture of the frameworks that
offer to manage persistence, not from poor or careless application code.
For instance, the persistence context is often times scoped to the atomic database transaction, and even
worse, each persistence operation. This usage scenario is the nexus of the most frequently reported "bug" in
Hibernate, the unnerving LazyInitializationException. You are going to learn about this exception
and why the persistence context was intended to be carried on for the duration of the business use case,
regardless of whether that be for one request or a whole series of pages. In this chapter, you can not only
expect to learn about Java persistence, but also the principles behind its design and how it is supposed to work.
The next chapter will put this information to practice by showing how Seam properly manages these vital
resources.
Throughout this book, you really haven't had to deal with the persistence configuration since the seam-gen
tool took care of it for you, sort of a blessing and a curse. Seam-gen set up the data source connection pool, the
persistence unit and persistence manager, the transaction manager, and even put a set of entity classes and
transactional components to manage them in place by reverse engineering the database schema. But, you won't
always have seam-gen to do all the work for you, so you need to take the time to understand Java persistence.
8.1.1 The four pillars of Java persistence
The Java Persistence API (JPA) can be used in a Java EE container without any additional libraries. You can
configure Seam to use the JPA runtime configuration that is managed by the Java EE container. However,
Seam's built-in persistence management, which wraps the underlying persistence frameworks, is broader,
adding support for the native Hibernate API in addition to JPA. You need to be aware of the differences
between the two persistence frameworks so that you can make the decision as to which one to use in your
application. Each choice has its benefits.
Consider this section your crash course in Java persistence. The definitions presented here are going to be
integral to the remainder of the book where it will be expected that you understand how to use the persistence
mechanism.
NOTE
Whenever I use the term Java persistence, it refers to both JPA and the native Hibernate API. Hibernate
shares a close resemblance to JPA and therefore the persistence terminology introduced in this section
applies to both frameworks. The JPA interfaces will be used in the code snippets throughout this chapter.
The four main elements of Java persistence are:
z
entities (entity classes)
z
the persistence unit (represented at runtime by a persistence manager factory)
z
the persistence manager
z
transactions
Entities are seen twice, as their mapping metadata is grouped under a persistence unit and the life cycle of
the instances are managed by a persistence manager. Figure 8.1 illustrates how these elements relate to each
other. The persistence manager factory is the central piece. It uses the persistence unit as a configuration
source and then hands out persistence manager objects that are used to manage the entities defined by that
configuration. Operations performed on the persistence manager are almost always performed within the scope
of a transaction.
Licensed to Jaroslaw Gilewski <[email protected]>
Figure 8.1 The persistence manager factory uses the configuration metadata supplied by the persistence unit to create persistence
managers that manage the life cycle of entities. Units of work performed against one or more persistence managers are expected to
be wrapped in a transaction.
The fundamental goal of Java persistence is to move data between entity instances and the database. You
have already used entities quite extensively throughout this book. Let's quickly review what you have learned
about them.
8.1.2 Entities and relationships
Entities in an ORM tool, such as JPA or Hibernate, are the join point between the application and the
underlying database. They transport data between the application and the database, as illustrated in figure 8.2.
Being such a central piece of the application, the entities should be treated as more than just dumb data
holders. Seam allows you to use entity classes to serve as JSF backing beans, to establish prototypes for new
transient instances, to load additional data (lazy associations) in the view, and to perform business logic. You
should take advantage of the these objects as they are central to tying together the layers of the application.
Figure 8.2 Entities are the central piece in a database-oriented application, transporting data to and from the database.
While the entities serve as a representation of the data stored in the database tables, they do not have to
mimic the database schema. The ORM tool gives you free reign to assemble your entity graph as you choose,
perhaps by following domain driven design. You can then use the mapping metadata of the ORM tool to formfit the classes to the database schema. The flexibility that the mapping provides includes, but is not limited to,
preventing certain properties from being persisted (using @Transient), using different names for the
properties that are mapped to their respective columns (using @Column), organizing tables along an
inheritance hierarchy (using @Inheritence), or subdividing a table across multiple entities (using
@SecondaryTable). Naturally, there are limits to how far you can go. If the mapping requirements are too
steep, you have to start questioning whether the database schema is serving its purpose of representing the
domain of the business or whether ORM is the right approach to your problem.
The mapping metadata can even be used to auto-generate and apply the schema at runtime, so you can
start working without a database in place. You have taken this approach for each entity that was added to Open
Licensed to Jaroslaw Gilewski <[email protected]>
18 since running seam generate in chapter 2. You might recall the following two questions from the seamgen questionnaire:
z
Are you working with tables that already exist in the database?
z
Do you want to drop and recreate the database tables and data in import.sql each time you deploy?
Had you answered no for the first question and yes for the second, you could have started your application
from existing entities alone, the user interface generated by seam generate-ui and the tables being
constructed by Hibernate each time the application loaded.
Transitive persistence
One of the core benefits of ORM entities is transparent association handling. There are two sides to this
feature. The first side is the read operations. Managed entities can lazy load associations by traversing them
within the confines of an active persistence context (and hopefully a transaction). The other side is the write
operations. When an entity is flushed to persistence storage, modifications of any associated objects are also
processed and synchronized with the database. When removed, the removal may cascades into child entities,
depending on the attributes in the mapping. This process is referred to as transitive persistence.
Entities can save you a lot of time, not because they shield you from SQL but rather because they handle a
majority of the grunt work that is necessary to store related objects in a database. However, to use ORM
effectively, the persistence context and transactions must be managed appropriately or confusion reigns.
Fortunately, Seam provides the proper environment.
Extending annotations to the persistence layer
If you have bought into the benefits of annotations, you will most likely use them to configure your entities.
The standard Java persistence annotations (in the package javax.persistence.*) work in both JPA and
Hibernate. In addition to the standard JPA annotation set, Hibernate has its own "vendor" annotations to
support additional mapping features and association types that are not part of the JPA specification and Seam
often leverages them. Hibernate strives to be the prototype for future versions of the specification, so these
annotations may represent early versions of what will be available in JPA.
The @Entity annotation is used to declare a Java class as an entity. You have seen this annotation used
many times throughout the book, often accompanying the @Name annotation to allow the class to serve a dual
purpose a persistence entity and Seam component. Here is an excerpt of the Course entity, which is used to
store information about a golf course. Several key mapping annotations are shown that define how the class
maps to the table in the database:
@Entity
@Table(name = "COURSE")
public class Course extends Serializable {
private Long id;
private Facility facility;
private String name;
private Set<Hole> holes = new HashSet<Hole>(0);
...
@Id @GeneratedValue
@Column(name = "ID", unique = true, nullable = false)
public Long getId() { return this.id; }
public void setId(Long id) { this.id = id; }
@ManyToOne(fetch = FetchType.LAZY)
Licensed to Jaroslaw Gilewski <[email protected]>
@JoinColumn(name = "FACILITY_ID", nullable = false)
public Facility getFacility() { return facility; }
public void setFacility(Facility facility) {
this.facility = facility; }
@Column(name = "NAME", nullable = false, length = 50)
public String getName() { return this.name; }
public void setName(String name) { this.name = name; }
@OneToMany(cascade = CascadeType.ALL,
fetch = FetchType.LAZY, mappedBy = "course")
public Set<Hole> getHoles() { return this.holes; }
public void setHoles(Set<Hole> holes) { this.holes = holes; }
...
}
It is also possible to define all of the entity metadata in XML mapping files for either JPA or Hibernate. In
JPA, all of the XML mappings are declared in the file META-INF/orm.xml. Hibernate reads XML mappings
individual .hbm.xml files, which can bundle metadata for a group of entities or can be fine-grained, supporting
a single entity per mapping file. Consult the resources mentioned earlier for details of how to use these
mapping descriptors.
The metadata alone is not enough to make to allow these classes to be persisted. They must be associated
with a persistence unit.
8.1.3 The persistence unit
The persistence unit groups the entities to be managed, how they are associated with a database runtime, and
which transaction type is to be used. The persistence unit consists of three main parts:
z
entity metadata—A set of all annotated classes or XML mappings to be managed, containing
instructions for how Java classes and JavaBean properties are mapped to relational database tables. It
also indicates the relationships between entities and defines the global fetching strategy that is used to
traverse relationship.
z
persistence deployment descriptor—Specifies which persistence provider to use (not applicable for
native Hibernate), database connection information, transaction type and lookup, and vendor
extensions.
z
persistence manager factory—The runtime object representing the configuration of the persistent unit
as a whole. Used to create individual persistence managers which provide the services for managing
entity instances.
It's important to understand that there is a distinction between application-managed and containermanaged persistence. The former is where the application bootstraps the persistence unit from the persistence
deployment descriptor and the later, which only applies to JPA, is where the container manages these task,
dishing out persistence manages on demand. Regardless of whether you are using application-managed or
container-managed persistence, you must set up a persistence unit.
There is one prerequisite for setting up a persistence unit, the JCA data source. After all, the goal of
persistence is to talk to a database and the data source provides the channel to that resource. Application
servers extend JCA to allow a database resource adapter to integrate with the server connection pooling. What
that basically means is that it is possible to stick a database connection configuration into the JNDI tree and
have it managed as a connection pool. Data sources can be of the non-transaction, local transaction, or XA
transaction varieties. For now, you will be using a local transaction, but you will get a chance to play with XA
Licensed to Jaroslaw Gilewski <[email protected]>
transactions in chapter 14. In JBoss AS, files that end in *-ds.xml are used to install a data source. Seam-gen
sets up one of these deployment artifacts for each profile, dev and prod, and puts it in the resources folder of
the project. When the build runs, the file is shipped off to JBoss AS. If you are using a different application
server, such as Glassfish, you may set up the data source in the administration panel instead.
Once the data source is in place, you are ready to configure the persistence unit. The persistence
deployment descriptor hosts the only XML that is required in Java persistence.
The persistence deployment descriptor
What brings all the entity classes together under a single persistence unit and hitches them to an actual
database is the persistence deployment descriptor. For JPA, this file is META-INF/persistence.xml and for
Hibernate it is hibernate.cfg.xml. So why use annotations for the mappings and XML for the persistence unit
configuration? After all, they both represent metadata about how entity classes tie in to database columns and
tables. The answer gets right down to the essence of ORM.
One of the core principles of Hibernate and JPA is to abstract vendor-specific database information from
the Java code. The entity mappings are generally fixed. Regardless of which database you use, whether it be
H2, MySQL, or Oracle, the database schema for your application retains the same structure. But, when you
switch between these environments, the SQL dialect, transaction manager, connection URL, and credentials do
change. These environment-specific properties are penciled in XML so that it is easy to tokenize them and
allow a build to sweep through and apply the appropriate replacement values. What's better is you can still
override the entity mappings using XML if you really need to, perhaps because a different table-naming
convention was used in Oracle over H2. You get the rapid development of using annotations without losing the
flexibility of control in an XML descriptor.
Listing 8.1 is the deployment descriptor used in the Open 18 directory application.
Listing 8.1 The JPA persistence unit deployment descriptor
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version="1.0">
<persistence-unit name="open18" transaction-type="JTA">
#1, #4
<provider>org.hibernate.ejb.HibernatePersistence</provider>
#2
<jta-data-source>open18Datasource</jta-data-source>
#3
<properties>
<property name="hibernate.hbm2ddl.auto" value="validate"/>
<property name="hibernate.dialect"
value="org.hibernate.dialect.H2Dialect"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.transaction.manager_lookup_class"
value=
"org.hibernate.transaction.JBossTransactionManagerLookup"/> #5
</properties>
</persistence-unit>
</persistence>
Cueballs in code and text
The file identifies several key pieces of information that inform the container how to operate. The fact that
there is only one <persistence-unit> node [#1] indicates that we are only connecting to a single
Licensed to Jaroslaw Gilewski <[email protected]>
database. There is a one-to-one mapping between the persistence unit and the database. Therefore, if you are
working with many different databases, or one or more read-only replicas of a master database, you will need
multiple persistence units, and hence multiple <persistence-unit> nodes.
The persistence unit configuration shown above [#1] creates a persistent unit with the name open18, [#2]
identifies Hibernate has the JPA provider, [#3] indicates which JNDI data source the persistence manager
should use to obtain connections to the database, [#4] configures the persistence manager to use JTA
transactions, and [#5] specifies which class maintains the JNDI names of the UserTransaction and
TransactionManager objects. You can also use local resource transactions—often referred to as entity
transactions—in environments where JTA is not available or if you would rather not use it. The remaining
properties in the descriptor are specific to the Hibernate provider.
There are important differences in how JPA handles the deployment descriptor in comparison to
Hibernate. In JPA, the following rules apply:
z
the deployment descriptor must always be META-INF/persistence.xml
z
annotated classes are automatically discovered unless indicated otherwise in the descriptor using the
<exclude-unlisted-classes/> element
z
if META-INF/persistence.xml is available on the classpath of an EAR or EJB-JAR archive, the
persistence units in this descriptor will automatically be loaded
As you can see, there are some optimizations in JPA that allow it to follow configuration by exception
semantics. The fact that you cannot change the location and name of the deployment descriptor may leave you
scratching your head as to how to define multiple persistence units. Unlike the Hibernate configuration, JPA
supports multiple persistence units within the same descriptor, so you don't need separate files.
Hibernate does less work for you when setting up a persistence unit, perhaps as a trade off for giving you
more control. If you are using JPA annotations, you must explicitly define each class in the Hibernate
configuration file. You also need multiple Hibernate configuration descriptors (hibernate-database1.cfg.xml,
hibernate-database2.cfg.xml) if you need multiple persistence units. Finally, Hibernate is not automatically
loaded into the Java EE environment. If you want any of these features, then you have to use Hibernate as a
JPA provider, rather than as a standalone framework.
Reading the deployment descriptors, XML mappings, and scanning the classpath for annotated entities is
an expensive operation. It should only be done once, when the application boots. That is the role of the
persistence manager factory.
The persistence manager factory
When the persistence unit is loaded, either by the container or by the application, its configuration is stored in a
runtime object known as the persistence manager factory. In JPA, the persistence manager factory class is
EntityManagerFactory. The equivalent class in Hibernate is SessionFactory. Once the configuration
is loaded into this object, it is immutable. For each persistence unit (either a <persistence-unit> node in
the JPA deployment descriptor or a distinct Hibernate configuration file), there is a persistence manager
factory object to manage it.
When the persistence unit is managed by the container, within a Java EE environment, the persistence
manager factory can be injected using the @PersistenceUnit annotation. In the absence of containermanaged persistence, you have to load the persistence unit in application code using the Persistence class.
For instance, you could load the open18 persistence unit shown in the previous listing using the following
call:
EntityManagerFactory entityManagerFactory =
Persistence.createEntityManagerFactory("open18");
Licensed to Jaroslaw Gilewski <[email protected]>
The persistence manager factory gets its name because its only role in this world is to create persistence
managers. It is a thread-safe object that is designed to be loaded once when the application starts and closed
when the application ends for the sole reason that it is very expensive to create. Therefore, it is almost always
stored in the application scope, the longest running scope in an enterprise application.
That wraps up the persistence unit. As you have learned, the persistence unit is all about defining which
entity classes are to be managed by the persistence API, the provider implementation, and which type of
transaction should be used by the persistence manager. The persistence unit may either be loaded by the
container or by the application, and there are different usage scenarios depending on how it is loaded. But the
real workhorse of Java persistence, and the one you will see most often, is the persistence manager.
8.1.4 The persistence manager
The persistence manager is the API that you use to move entity instances to and from the database and to track
changes in the state of the entity instances that are managed. In JPA, the persistence manager class is
EntityManager, whereas in Hibernate it is the Session class. When container-managed persistence is
being used, within a Java EE environment, the persistence manager can be injected using the
@PersistenceContext annotation. Outside of the container, a persistence manager must be created
manually from the persistence manager factory. Persistence managers are very inexpensive to create. In fact,
they don't even allocate an underlying JDBC connection until a transaction begins. The following code
demonstrates creating a JPA persistence manager from the entity manager factory object.
EntityManager entityManager =
entityManagerFactory.createEntityManager();
A persistence manager is used to serve a single use case. Some developers only use it to service a single
transaction, though it was designed to outlive the scope of an atomic transaction. You do have to be conscious
of where you store the persistence manager, though, because it is not a thread-safe object. That is precisely
why the ideal context for the persistence manager is Seam's conversation scope. Seam helps you to extend the
persistence context beyond the boundaries of a single transaction.
The persistence manager performs three main functions:
z
persistence service API—This API is used to manage entity instances within the scope of a single use
case. It can create, remove, update, find by id, and query entity instances. It can also be said that it
manages the life cycle of an entity instance, as it moves between four possible states: transient,
persisted, detached, and removed. The entity life cycle is shown in figure 8.3.
z
persistence context—An in-memory cache of all entity instances that have been loaded into memory by
this manager. It is the key to optimizing performance and enabling write-behind database operations
(queued SQL statements). It is often referred to as the "first-level" cache.
z
automatic dirty checking—The state of managed objects that reside in the persistence context are
monitored throughout the lifetime of the persistence context. When the persistence manager is flushed,
the pending changes in the entity instances are sent to the database as a batch of SQL statements. Once
an object is detached, its state is no longer monitored.
The most significant aspect of the persistence manager is its persistence context. In fact, you could argue
that the persistence context is what makes using Java persistence worthwhile. It is more intelligent than SQL
because it understands that data going to and from the database has a structure (the entity) and that structured
data has a life cycle, represented in 8.3. Entity instances start off as unmanaged, the transient state. They then
become managed, allowing them in synchronize with the database. If they are removed, that synchronization
becomes a deletion. When a removal occurs, or the persistence context is closed, the entity instance becomes
Licensed to Jaroslaw Gilewski <[email protected]>
detached, which means it has been abandoned by the persistence context and changes to it are no longer
monitored.
Figure 8.3 The entity life cycle in an ORM framework.
The persistence context can forgo trips to the database when it knows it already has an object loaded in
memory, it can traverse lazy associations without forcing you to assemble a new query, and it can monitor the
state of entity instances and propagate outstanding changes to the database, including changes to related
entities. These features make it a persistence manager, not just a database access layer.
Persistence context scoping
If scoped in appropriately, the persistence context can actually become more of a hindrance than a help. You
often see this misuse in stateless architectures, popularized by the Spring Framework. In these cases, the
persistence manager is scoped to the atomic transaction, rather than the intended lifetime of the entity in the
business context. By putting the persistence context on such a short leash, you are stripping out all of its value
as a manager of entity instances. The persistence manager was designed so that it could outlive a transaction
and later be re-associated with a new transaction, informing that transaction, and the database, of the changes
to the entities while it was away. Without this ability, the persistence manager really is reduced to a database
access layer.
Hibernate helps some in this regard by associating a Session with the current thread. Thus, once you
create a Session, it is available for the duration of the request, so long as you don't close prematurely. You
can access the active Session by calling the getCurrentSession() method on SessionFactory. JPA
does not support the same construct, forcing you to bind the EntityManager to the current thread yourself if
you want to use the same pattern. Seam offers a better solution by automatically associating the persistence
manager, either Session or EntityManager, with the active conversation, which you will learn about in
the next chapter. Not only does Seam allow the persistence manager to last for the entire request, it can remain
available for the entire duration of a conversation, only flushing changes when the conversation ends.
You cannot go far with a persistence manager with a transaction. To wrap up the discussion of Java
persistence, we will briefly touch on transactions.
8.1.5 Transactions
Transactions are about as important as the persistence operations themselves. Without transactions, you are
risking corrupt or inconsistent data. It is crucial that whenever you perform work against a database, you
ensure that the boundaries of a transaction are well defined.
From the standpoint of the application, a transaction is an atomic unit of work. A unit of work is a set of
operations, or tasks, that you want to perform on the database. Since you are working with Java persistence,
you perform these operations against the persistence manager rather than the database. When you reach the
Licensed to Jaroslaw Gilewski <[email protected]>
point when you want to commit the changes to the database, you are guaranteed by the transaction that they
either all happen, or none of them happen. The following is an example of using an application-managed
transaction with JPA. Proper exception handling is hidden for clarity, but certainly not optional.
entityManager.getTransaction().begin();
Course course = new Course();
course.setName("Patuxent Greens Country Club");
// add other attributes
entityManager.persist(course);
entityManager.getTransaction().commit();
I promised myself that if I ever wrote a book I would not use the bank account scenario to explain
transactions. Instead, we are going to talk golf! In addition, I will leave you with two separate scenarios to
chew on.
Consider that you use a tee time reservation system to secure your time slot at your favorite golf course.
As a user, you browse through the available times for your course and find one that fits best with your
schedule. You pick one and submit the form. The action handler is then tasked with performing a unit of work.
It must first mark the time as occupied in the course_schedule table and then add a row to the
golfer_schedule table so that you don't forget about your obligation. The transaction needs to guarantee
two things, from the standpoint of the application. First, the transaction needs to ensure that while you were
daydreaming about golfing, someone hadn't come behind you and swapped out the time you selected. While
you may curse yourself for missing the opportunity, at least the data remained consistent. Second, the
transaction needs to ensure that if the insert failed on the golfer_schedule table, that the slot would open
back up on the course_schedule table, and vice versa. You certainty don't want tee times blocked off
without anyone intending on showing up. You also don't want to show up to the golf course to find someone
else chatting it up with the marshal on the first tee at your scheduled time.
That example is the classic credit-debit scenario. While interesting, it is not the only potential source of
inconsistency. Assume that you are working on adding a new golf course to the directory. You spent a good
half hour collecting up all the data for the course and populating the form. You press submit to save your work.
Once again, the action handler is tasked with performing a unit of work. It must save the main information to
the course table, a row for each hole in the hole table, a row for each tee set in the tee set table, and finally a
row for each tee (# of tee sets * # of holes) in the tee table. Assume that somewhere along the line, one of the
inserts choked and the database kicked back an error. If a transaction was not active, or if it failed to do its job
properly, then only half of the course information would end up in the table. If you tried to submit the form
again, it would bail because the course record is already in the table, even though it is not complete. If the
application is smart enough to handle incomplete course data, you may be able to start the form over by editing
what data did get inserted, but even that is risky. You essentially have a disaster on your hands, especially if
thousands of users are encountering the problem at once.
These two scenarios should give you a compelling reason to use transactions when interacting with the
persistence manager. However, even if you are not performing write operations, transactions are still
important. In fact, it's impossible to execute a SQL statement without a database transaction. The database will
open and close a transaction on every operation if explicit transaction boundaries are not set. So, when you
forgo the use of transaction, you are just letting the database handle it for you, one SQL statement at a time.
This repeated processes of opening and closing transactions becomes costly. Therefore, even when you are just
reading data, you should use a transaction. Using a transaction for successive read-only operations guarantees
you the isolation that transactions provide. If the database changes were to change in the middle of the
rendering process, for instance, you wouldn't end up showing some of the old and some of the new data. You
want all of one or the other, and a transaction can guarantee that for you.
Licensed to Jaroslaw Gilewski <[email protected]>
If you use Seam's transaction management, you automatically get at least two transactions per request, one
that covers the action handlers and one that covers the rendering of the response. You can choose to take a
different approach, but it is intended to solve the brunt of your transactions needs.
From the standpoint of the database, a transaction adheres to the ACID criteria. This acronym stands for
atomicity, consistency, isolation, and durability. While each criteria is important, I prefer to merge it into two
main points, that I highlighted in the two scenarios I presented.
z
all logically grouped operations succeed or the database remains untouched
z
other concurrent operations don't intermix data with the data in your operations
I feel that for most applications, these are the two main points you need to keep in mind. If you are
interested in knowing about every last detail of transactions, I encourage you to check out the resources I listed
at the beginning of the chapter or chapter 5 of Martin Fowler's Patterns of Enterprise Architecture (AddisonWesley Professional, 2002), which covers transactions and concurrency in depth.
8.2 Tuning the scope of the entity manager
One of the main challenges in JPA, especially when working in the web environment, is how to retain an
active persistence manager between requests, and how long to hold on to it after that. As discussed, the
persistence manager represents an in-memory cache of entity instances that have been retrieved from the
database. If you don't hold on long enough, you get detached entities prematurely. If you hold on to it too long,
it can result in an over zealous cache and memory leaks. It's important to find the right balance.
8.2.1 Introducing the extended persistence context
Java EE 5 introduced the concept of an extended persistence context when JPA became part of the EJB 3
specification. A stateful session bean (SFSB) is allocated for a single client and continues to maintain state
throughout its lifetime, potentially spanning multiple atomic transactions. This type of session bean is said to
be conversational. This behavior is the way it has always been. The problem is that in order to really be
stateful, the entity manager used by the session bean has to be conversational as well.
When the entity manager is injected into a stateful session bean using the Java EE 5
@PersistenceContext annotation, you have the choice of whether you want to use a transactional or
extended persistence context, defined by the annotation's type attribute.
The transactional persistence context is the default type. It ties the entity manager to the scope of the active
JTA transaction. An extended persistence context keeps the entity manager open for the lifetime of the SFSB,
delaying the call to its close() method until the component is destroyed. An example of an entity manager
resource injection using an extended persistence context is shown here:
@PersistenceContext(unitName = "defaultPU",
type = PersistenceContextType.EXTENDED)
private EntityManager em;
When the extended persistence context flag is applied, transactions may come and go, but the injected
entity manager remains open. There are a couple of problems with how the entity manager is handled in a Java
EE environment, which is one of the main motivating factors for why Seam was developed. Seam provides a
managed persistence mechanism that replaces the use of the @PersistenceContext and containermanaged entity managers that you will learn about in the next chapter. While the idea of the extended
persistence context originated in the JPA spec, it lives on in Seam in a much more capable state.
Let's turn to the motivation for having an extended persistence context to set the stage for the next chapter
which is entirely focused on its use.
Licensed to Jaroslaw Gilewski <[email protected]>
8.2.2 The benefits of an extended persistence context
Why would you want to use an extended persistence context? Simple. To eliminate detached entities. An entity
becomes detached when the entity manager to which it is bound is closed. Don't get me wrong. Detached
entities are useful in that they erase the need for data transfer objects (DTO). But you generally want to avoid
them within the scope of an application transaction.
By keeping the entity manager open beyond the scope of a single transaction, and preventing entities from
become detached, you get the following benefits:
z
Eliminates the need to use merge() to update detached entity instances
z
Allows safe lazy loading of entity associations and uninitialized proxies
z
Ensures only one object reference exists for a given entity identifier
Let's consider each of these in turn.
Say no to merging
As mentioned earlier, the entity manager tracks the state of entity instances bound to it. Outside of the entity
manager's realm, changes to the entity are no longer tracked. When the entity is introduced to another entity
manager, the entity is treated as a stranger. The new entity manager cannot vouch for the state of the entity,
even though the entity claims to be managed, meaning it was assigned a non-null identifier at one time.
If you want to use the new entity manager to synchronize the state of the entity to the database—a process
referred to as flushing—the entity manager's merge() method must be used. Merging is a crude operation and
should be avoided if possible. It works by copying the values from the detached instance onto the properties of
the corresponding instance in the new entity manager's persistence context. No regard is given to whether the
local instance has pending changes. If no local instances has been loaded, the entity manager will first fetch an
instance from the database before performing the merge.
By extending the persistence context, entity instances do not become detached. Therefore, the entity
manager continues to track changes that are made to the entity. When it comes time to synchronize the entity
with the database, calling the entity manager's flush() method will do the trick within the scope of any
transaction.
@Stateful
public class FacilityManagerBean implements FacilityManager {
@PersistenceContext(unitName = "defaultPU"
type = PersistenceContextType.EXTENDED)
private EntityManager em;
public Course findFacility(String id) {
return em.find(Facility.class, id);
}
public void updateFacility(Facility facility) {
em.flush();
}
}
Facility facility = facilityManagerBean.findFacility(1L);
facility.setGreensFee("75.0");
facilityManagerBean.updateFacility(facility);
Licensed to Jaroslaw Gilewski <[email protected]>
As you can see, the updateFacility() method just picks up where the previous operation left off. The
facility instance is already known to the persistence manager, so a flush just makes any changes to it persistent
in the database. Of course, the call to updateFacility() issues an atomic transaction, which wraps the
database operation. This example perfectly demonstrates why SFSB are called "conversational" components.
Each method call moves the state further along.
No more tip-toeing around Hibernate exceptions
Lazy loading of entity associations has unfortunately established a bad reputation. The first thing that comes to
mind in most developers head when you talk about lazy loading is Hibernate's
LazyInitializationException. Once again, the culprit is the detached entity instance.
The associations between entities are represented as an object graph that mimics the relationships of the
corresponding database tables. When you fetch an entity, you typically only want to grab a fraction of the total
graph or risk loading a significant portion of the database. For example, if you retrieve a Course object, it
would be very expensive to load all of the holes, tee sets, and tees associated with that course eagerly. It gets
worse if the same eager load were to occur when performing a query for multiple Course objects.
The alternative is to eager fetching is to mark the associations as lazy. When you traverse the association
on the Java object, the uninitialized object or collection of objects will be loaded transparently. Although you
do need to be aware of when this type of loading is occurring, it is not risky or a bad practice. JPA can be
optimized to perform additional eager fetching when lazy load is triggered to avoid the class n+1 select
problem.
Let's trigger a LazyInitializationException and then fix it using an extended persistence context.
In the example below, a non-transactional method calls a transactional method to lookup a Course by its
identifier. In this case, the persistence context is scoped to the transaction.
@Stateful
public class CourseManagerBean implements CourseManager {
@PersistenceContext(unitName = "defaultPU")
private EntityManager em;
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public void triggerLazyInitializationException(Long id) {
System.out.println(
"Triggering lazy initialization exception");
try {
for (Hole hole : findCourseById(id).getHoles()) {
System.out.println("Lazy-load succeeded");
break;
}
} catch (Exception e) {
System.out.println("Lazy-load failed");
}
}
public Course findCourseById(Long id) {
return em.find(Course.class, id);
}
}
<Annotation #1> TransactionAttributeType.SUPPORTS would also work here
<Annotation #2> Should not make it here
Licensed to Jaroslaw Gilewski <[email protected]>
//1
//2
It's easy to confuse why the lazy loading fails in this example. You might look at that code and assume
that you need an active transaction to traverse a lazy association. That, in fact, is not the case. If a transaction is
not active, it will just run the fetch queries in autocommit mode (single transaction per SQL statement). The
problem stems from the fact that the life cycle of the entity manager is tied to the transaction. The call to
findCourseById() allocates a new entity manager when it begins and closes that entity manager when it
ends. The Course entity returned by that method is detached, so when the getHoles() method is invoked,
it throws an exception because the entity manager is no longer available.
NOTE
In my tests, the lazy initialization exception is only thrown by Hibernate (it is a Hibernate exception after
all). The Toplink Essentials JPA implementation does not exhibit this behavior, likely because it allocates a
new entity manager on demand. Even though you can avoid the exception using an alternate JPA
implementation, it does not mean you have escaped the problem. The persistence context should guarantee
uniqueness for an entity of the same type and identifier. When you violate this assumption, you are asking
for conflicts.
Do I know you?
The final benefit is tough to appreciate until you are knee deep in requirements and strange interactions start to
occur because multiple instances of the same entity are scattered throughout your layers. If you use a
transaction-scoped entity manager, each call to fetch an entity instance is going to return a different object
reference. Let's assume that the findCourseById() method shown earlier is defined on a session bean
(stateful or stateless) that uses a transaction-scoped entity manager. Assuming that no higher level transaction
exists, the following assertion will fail.
assert courseManagerBean.findCourseById(1L) ==
courseManagerBean.findCourseById(1L);
However, if you use an extended persistence context (on a SFSB), then this assertion will succeed. While
defining equals() and hashCode() on your entities is generally good practice, it is very time consuming
and if you can get away with not using them, you are going to be better off. The extended persistence context
allows you to keep that in-memory cache open for the duration of the natural unit of work, which we defined in
the last chapter to be a conversation.
The good news is that Seam makes it extremely simple to work with extended persistence contexts, fixing
several of the downfalls in the Java EE specification that make it difficult to share an extended persistence
context between components. The conversation is also a good fit for the extended persistence context because
it gives it a well defined starting and ending point. In the next chapter you are going to learn how to take
advantage of these boundaries and use the extended persistence context in your application. But first, you must
sit down and make the tough choice, whether to use JPA or Hibernate, or if there is a possibility of using both
in tandem.
8.3 Choosing between JPA and Hibernate
There has been a lot of talk in this section about both JPA and Hibernate, but aside from a few points, I haven't
addressed why you might want to choose one over the other. Seam supports both Hibernate 3 and the Java
Persistence API (JPA) out of the box. Since JPA is a standard, rather than an implementation, it encompasses
an agglomeration of persistence providers, one of which is Hibernate. Other implementations include Toplink
Essentials, BEA Kodo, and OpenJPA (the open source version of BEA Kodo managed by Apache). Seam
Licensed to Jaroslaw Gilewski <[email protected]>
supports any JPA provider, but truth be told, there are advantages to choosing Hibernate as the underlying
provider. If you are strongly in favor of Hibernate, you can forgo the use of JPA altogether and just use
Hibernate. Let's open the discussion about which API to choose by first understanding their differences.
8.3.1 How Hibernate relates to JPA
In order to choose between Hibernate and JPA, you need to know a little about the history. There is a common
misconception that Hibernate and JPA are the same thing. They are not. There are many similarities between
the two though. Hibernate served as one of several references when developing the JPA specification, and
many of the JPA interfaces mimic those in Hibernate, despite having different names. However, there are
many features in Hibernate that did not make it into JPA. Additionally, there are some new concepts that were
added to JPA that are not available in Hibernate. Hibernate has the advantage of being an unconstrained open
source project, whereas JPA has the advantage of leveraging the standard Java EE environment. Seam attempts
to bring the best features of both frameworks to a common set of components, sometimes drawing from
vendor-specific features of Hibernate and other times drawing from JPA.
Hibernate has been around for a lot longer than the JPA specification. It is also developed as an open
source project and is therefore not held back by the, sometimes slow, Java Community Process (JCP). You are
always going to get more features when you use pure Hibernate. The most important, and one that the
Hibernate developer felt should have been in JPA from the start, is manual flushing of the persistence context.
This feature allows changes to be kept out of the database until the conversation has ended, enabling what is
know as an application transaction (or atomic conversation). You will learn about application transactions in
the next chapter. You also have access to Hibernate Search, a recent extension that supports full text searching
using the Lucene search engine. There are also many association mappings that Hibernate supports over JPA,
such as indexed collections. And who can forget Gavin's most revered feature, post-query filters. If you are a
person that thrives on bleeding edge features, then I encourage you to go with Hibernate as the persistence
provider your Seam application.
On the other hand, you can still get the benefits of Hibernate while at the same time protecting your
technology investment by using Hibernate through the JPA standard interfaces. When you select Hibernate as
the JPA provider in Seam, you retain the following Hibernate features without having to cast to Hibernate
interfaces:
z
manual flushing of the persistence context (application transaction)
z
post-query filters (defined in the component descriptor)
z
full text searching (using the Lucene search engine)
The only feature you really lose by adhering to the JPA interfaces and using Hibernate as the JPA provider
rather than using the Hibernate API directly are the advanced association mappings. Granted, when you do
switch JPA providers, the features listed above will no longer work and it may break your application, so if
you are serious about JPA portability, you should steer clear of these features, perhaps with the exception of
manual persistence context flushing. The Hibernate developers are confident this feature will make it into the
next version of the specification and Seam is primed to make that switch.
8.3.2 Getting down to Hibernate
In order to provide the extra Hibernate-specific features listed above, Seam taps into the Hibernate API behind
the scenes, without leaving a trace of the interaction anywhere in your application code. But even when you
are using JPA as the persistence mechanism, you can always get down to the provider interface by calling the
getDelegate() method on the EntityManager object. You have to perform a cast that makes an
assumption about the underlying provider.
Licensed to Jaroslaw Gilewski <[email protected]>
Session session = (Session) entityManager.getDelegate();
Seam can help to make this operation more elegant by pushing out the Session object directly. You first
set up the following alias in the component descriptor:
<factory name="session" value="#{entityManager.delegate}"
scope="stateless" auto-create="true"/>
With that factory in place, you can now use the @In annotation to inject it into your business object:
@Name("courseAction")
public class CourseAction {
@In private Session session;
public void loadCourses() {
session.setCacheMode(CacheMode.IGNORE);
...
}
#1
}
<Annotation #1> Assumes use of the second-level cache
Likely, you will find this feature to be more useful in integration tests than in application code. There, you
don't run the risk of polluting your core API with a mix of providers and can likely get more to the core of
what you are attempting to test.
If you want to take advantage of Hibernate Search, the path to its API is even more subtle. When
Hibernate Search is present on the classpath (comprised of hibernate-search.jar, hibernate-commonsannotations.jar, and lucene-core.jar), the persistence manager used is the one from the Hibernate Search API. If
you are using Hibernate, the Session is actually a FullTextSession and if you are using JPA, the
EntityManager is actually a FullTextEntityManager. You can either downcast to the full-text variant
when you need its features or just inject it directly using the appropriate property type. Here, we search over
golf courses using a Lucene query through Seam's managed JPA EntityManager:
@Name("courseSearch")
public class CourseSearchAction implements {
@In private FullTextEntityManager entityManager;
@Out private List searchResults;
public void search(String searchString) {
org.apache.lucene.query.Query luceneQuery =
buildLuceneQuery(searchString);
javax.persistence.Query query = entityManager
.createFullTextQuery(luceneQuery, Course.class);
searchResults = query.getResultList();
}
}
Keep in mind that to use Hibernate Search, you must be using Seam-managed persistence, which is
covered in the next chapter. You also need to apply the Hibernate Search annotations to your entity classes and
make the following addition to your persistence unit configuration:
Licensed to Jaroslaw Gilewski <[email protected]>
<properties>
...
<property name="hibernate.search.default.directory_provider"
value="org.hibernate.search.store.FSDirectoryProvider"/>
<property name="hibernate.search.default.indexBase"
value="/home/twoputt/indexes/open18-index"/>
</properties>
#1
#2
<Annotation #1> File system-based index
<Annotation #2> Directory where indexes are stored
There is no way to do Hibernate Search justice is the small amount of space. Besides, its core features
don't relate in any way to Seam. Seam just handles the task of managing the persistence manager and
decorating it with the full-text variety. I strongly urge you to pick up a copy of Hibernate Search in Action
when it is released to learn more about this extremely powerful feature of Hibernate.
8.3.3 The verdict
The message from the Seam developers about which framework to use is astoundingly clear in the reference
documentation. They recommend that you use JPA with Hibernate as the provider. By making this pairing,
you get the best of both worlds. It allows allows you to hold off on adding any Hibernate-specific features until
you decide that they are absolutely necessary. Up until that point, you are free to switch JPA providers without
any penalty.
With the knowledge that you have gained from your crash course on Java persistence, you are ready to
start using JPA as a Java EE service. JPA is managed entirely by the Java EE container. To get a reference to
either a persistence manager or persistence manager factory, you must use a resource injection. In the next
section, you will learn how resource injections work and which ones are used specifically for getting a handle
on the persistence APIs.
8.4 Summary
This chapter gave you the background on Java persistence that you need to appreciate Seam's managed
persistence and transactions that are covered in the next chapter. You were given a crash course in Java
persistence, learning about its four main elements, entities, the persistence unit, the persistence manager, and
transactions. You were then prepped on the distinction between a transactional and extended persistence
context and learned the benefits of having an extended persistence context for when you learn about it using
Seam's version in the next chapter.
Before you were ready to make the moved to Seam's persistence architecture, you were presented with
how JPA differs from Hibernate and were given reasons why you would choose one over the other. You were
given the recommendation of using Hibernate as a JPA provider, rather than the native Hibernate API, so that
you are able to take advantage of Java EE standards while still having access to the extensions offered by
Hibernate.
In the next chapter, you are going to learn how Seam supplements Java persistence, doing a better job of
managing the persistence context than what is provided by the Java EE container alone. You are also going to
discover how Seam offers the same declarative transaction behavior for regular JavaBeans that EJB 3 session
beans enjoy. Read on to discover how Seam makes creating transactional applications a truly pleasant
experience.
Licensed to Jaroslaw Gilewski <[email protected]>
9
Seam-managed transactions and
persistence
Managing persistence is Seam's raison d'etre. In the last chapter, you learned how to configure Java persistence
and how it can be used both inside and out of a Java EE container. However, Seam has its own way of
managing persistence that either builds on the container's support or emulates the container's role in its
absence. Either way, the result is a better transaction and persistence strategy.
This chapter continues the lessons from the last chapter on transactions and persistence, demonstrating
further how Seam's conversation context benefits Java persistence by correcting the misuse of the persistence
context. The conversation, which you learned about in chapter 7, compliments persistence to form the core of
Seam's state-management architecture. The examples in this chapter show how the conversation-scoped
persistence context serves as a natural application cache, optimizing persistence operations, retaining object
identity, and enabling atomic conversations.
While conversations have many benefits for developing enterprise business applications, the primary
design goal of the conversation is to propagate the persistence context throughout the duration of an
application transaction (also referred to as an atomic conversation or optimistic transaction). In a purely atomic
application transaction, modifications that are made to objects throughout a page flow are queued in the
persistence context until the page flow ends, at which time they are flushed to the database within the
boundaries of an atomic database transaction. By the chapter's end, you will be familiar with the concept of an
application transaction and the mechanism that Seam uses to facilitate it.
Once you complete this chapter, you will be ready to bring together all the knowledge you have gained so
far from this book and put Seam to the rapid development test, where Seam's managed transactions and
persistence will play a central role. Before you get there, though, you have to understand Seam's managed
persistence strategy, learn how to configure Seam's transaction and persistence components, and discover how
to put application transactions to use in your project.
9.1 Aligning transactions and persistence under Seam
If the Seam developers were forced to agree on which of Seam's goals is the most important, they would likely
say it is to get persistence right. (When I use the term persistence, it encompasses transactions as well). In this
section, we are going to look at why Seam needs a strategy and why this strategy makes working with JPA and
Hibernate so easy.
9.1.1 The challenge of persistence in web applications
The servlet environment, an abstraction of the HTTP protocol, is an atrocious setting for performing
transactional data processing. HTTP requests are stateless, which means that each request starts back at square
one in terms of the server's awareness of the client's state. The lack of state propagation makes it absolutely
necessary to have a framework that can reinstate the resources that were active during the previous request.
Even then, as you learned in previous chapters, if the resources are drawn from the coarsely-grained HTTP
Licensed to Jaroslaw Gilewski <[email protected]>
session, it can easily shatter isolation concerns and step on toes of objects used by other windows or tabs
(concurrent session usage).
A lot of frameworks, especially web frameworks, tend to remain agnostic of persistence. As a result,
developers must resort to solutions that involve servlet filters, or some other request wrapper, that can bind the
persistence manager to the thread servicing the request, manipulating the boundaries of the transactional data
access from the outer rim of the application. When a persistence operation fails—meaning an exception is
thrown—it becomes very difficult for the filter to get a handle on the situation in order to notify the user of the
failure or perform a fall back operation.
Seam offers a strategy for managing transactions and persistence. A better strategy than the ad-hoc
solutions that often appear on blogs, wikis, and in frameworks such as Spring. Better than the Java EE
managed resources. In fact, Seam's strategy builds on the container-specific features of JPA, but also allows
the resource injections to happen outside of a Java EE container. Even if you decide not to adopt Seam on a
future project, you should be able to migrate Seam's persistence strategy to your framework of choice.
Therefore, you can think of Seam as a reference implementation for how Java persistence is intended to be
used, along with a number of prototype enhancements that may land themselves in a future version of the JPA
specification.
9.1.2 Seam's managed persistence strategy
The JSF life cycle serves as an ideal control mechanism for Seam to manage persistence during a request.
Unlike servlet filters, the JSF life cycle allows Seam to monitor the request at each stage. This insight makes
Seam more apt to manipulate transaction boundaries and deal with failures than simple servlet filters. Within
this life cycle, Seam applies the two key aspects of its strategy:
z
scope the persistence context to the conversation
z
wrap each request in two (or three) distinct transactions
Figure 9.1 identifies the parts of the Seam life cycle that are wrapped in transactions by shading each
distinct region.
Figure 9.1 Shows the boundaries of the transactions that are wrapped around the phases of the Seam life cycle by Seam's
transaction management. The RESOURCE_LOCAL transactions is delayed by the start of a conversation.
Conversation-scoped persistence managers are what enable Seam to propagate the persistence context
beyond the boundaries of a single transaction, and even a single request. However, these Seam-managed
Licensed to Jaroslaw Gilewski <[email protected]>
components are only necessary in non-EJB environments. As you learned earlier, the Java EE container
already has a mechanism for extending the persistence context. Instead of allocating a persistence context for
each transaction, the EJB container can scope it to the lifetime of a stateful session bean when the type
attribute of the @PersistenceContext annotation is set to PersistenceContextType.EXTENDED.
Despite this capability, there are benefits to switching to Seam's solution. Seam is more adept at coordinating
propagation of the persistence context between components, a task which is tricky using the Java EE
mechanism. Additionally, Seam allows non-managed JavaBeans to access the extended persistence context,
something that is not possible with Java EE alone.
To support the persistence operations, Seam uses two global transactions per request; three, if page actions
are triggered. The first transaction allows units of work performed by action handlers to complete before
moving on to the view rendering. The initial transaction begins either before the Restore View phase or the
Apply Request Values phase, depending on whether JTA or resource-local transactions are being used,
respectively. The final transaction ensures that database reads occurring in the Render Response phase as a
result of lazy loading and other on-demand fetch operations are consistent as defined by the transaction
isolation level.
FAQ
Do I need a transaction to use lazy loading? No. Lazy loading is made possible by extending the
persistence context for as long as you may need to cross the boundary of an association configured to use a
lazy fetching strategy. However, given that lazy loading may require more than one query, and you could
very well hit more than one lazy association in a given area of code, it's a good idea to use a transaction for
all the reasons a transaction is needed, even if the operations are read-only. In the later case, isolation and
consistency are the greatest concern. Some queries may take a long time to run and thus, regardless of how
many transaction commits occur in the interim, your read-only transaction must always see the state of the
database as if all the queries executed in an instant.
Keep in mind that Seam-managed persistence and transactions are both optional. You are free to stick to
standard container-managed resources. As a word of warning, doing so may allow your application to slip into
the session-per-operation data access pattern, a problem which will be discussed in the next section. You can
also take a hybrid approach, using bijection to inject Seam resources and Java EE resource injections to inject
container-managed resources. If you want to be able to use Seam's extended persistence context but would
rather control the boundaries of the transactions in a more fine-grained manner, you can disable the global
transactions just discussed by using the following configuration in the component descriptor:
<core:init transaction-management-enabled="false"/>
The point to take away is that you can adopt the whole strategy, use one feature without the other, or forgo
the whole strategy entirely. However, by adopting Seam's strategy wholesale, it allows you to avoid a lot of
issues that may have previously eaten up a lot of your development time. Rest assured that by choosing this
strategy, you still have a choice of which persistence framework to use—JPA or Hibernate—and which
transaction type to use—JTA or resource-local. The strategy was developed to solve the most common antipattern in enterprise application development, abusing the persistence context.
9.1.3 The plight of the persistence context
To illustrate how the persistence context can be abused, consider the typical scenario in which you want to
update a database record across a sequence of screens. You present the data overlaid on a form, allow the user
to make modifications to it, then save it back to the database. The actual CRUD (create, read, update delete)
logic, while interesting, is not the focus of this discussion. Rather, the focus is on how the persistence context
Licensed to Jaroslaw Gilewski <[email protected]>
and transactions are handled, or mishandled, throughout the use case. In this example, we will look at updating
the scorecard for a golf course.
Here are the steps involved (assuming everything goes well):
z
A list of golf course records stored in the database is displayed.
z
You select the course you want to modify.
z
An editor form is presented, populated with the course's scorecard information.
z
You make the modifications to the scorecard.
z
You click the Save button.
z
The modified course scorecard is synchronized with the database.
This use case appears simple enough, but because of the stateless nature of the HTTP protocol, there are a
number of underlying complexities that are not obvious unless you have tried to program for this scenario.
While you may be anxious to try out a more complex use case, I assure you that the same difficulty is shared
by simple use cases and more involved page flows. Once you learn to wield the persistence context properly,
you will be ready to take on tougher challenges. Let's begin with the read operation portion.
Crossing lazy associations in the view
When you select a course to edit, the business logic must create a new persistence manager, start a transaction,
retrieve the Course entity instance from the database by identifier, and finally, end the transaction. When
resource-location transactions are in use, that roughly translates to the following JPA code:
EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();
Course course = em.find(Course.class, courseId);
em.getTransaction().commit();
em.close();
The editor form is rendered and populated by reading the information from the Course entity instance
that was retrieved. However, this situation presents a potential problem. If the business logic that created the
persistence manager immediately closes it when the transaction is committed, as is done in the code above,
then the Course entity instance becomes detached. Any associations on the object that were not fetched
eagerly in the action handler are now sitting ducks for a LazyInitializationException. Since reading
the scorecard data from the Course entity instance requires many associations to be crossed, such as the
collection of holes, tee sets and tees, and those collections are configured with a lazy fetching strategy, you are
definitely going to encounter this exception. One such traversal may happen when accessing the holes:
<ui:repeat var="hole" value="#{course.holes}">
<th>#{hole.number}</th>
</ui:repeat>
There are two ways around this lazy loading problem. You can either:
z
preload all the data you need in the view
z
ensure that the persistence manager stays open for the duration of the request
The first solution is short-sighted. You may be able to avoid hitting an uninitialized lazy association for
this particular use case by fetching the entire scorecard in the action handler, but eventually you are going to
encounter a case where this eager fetch strategy puts too many objects in memory, puts too much unnecessary
load on your database, or is simply impossible. Either way, you are going to quickly grow tired of constantly
Licensed to Jaroslaw Gilewski <[email protected]>
trying to tip toe around these association boundaries—I know I have. Lazy associations were designed to be
crossed, so why can't the web framework support crossing them?
With Seam, you can. Seam uses the second solution, which is often referred to as the Open Session in
View pattern. Only, Seam goes a step further by scoping the persistence manager—and in turn, the persistence
context—to the conversation, allowing you to embrace lazy associations throughout the duration of a use case,
or, at the very least, a logical request in the event a long-running conversation is not active. But you still have
the problem of lazy load operations executing in autocommit mode, which opens and closes a transaction for
each query. Not only is this expensive, it lacks the isolation guarantees just discussed. This is fixed by
combining the extended persistence context with Seam's global transactions, which together allow you to
safely use lazy associations wherever you need them, including the view.
Keeping the entity managed and locked
While the Course entity instance is "check out" in the view, the state of that entity needs to be stored
somewhere. You have three options from which to choose. You can:
z
track the entity identifier using a hidden form variable
z
store the entity instance in the session
z
keep the persistence context active for the duration of the use case
The first solution is the sort of the old-fashioned way. On postback, you either use the identifier value to
retrieve a fresh instance of the entity from a new persistence manager or you construct a new entity instance an
manually assign it the identifier value. The request values are then applied to that instance. With the approach,
the changes will be synchronized to the database automatically. If you are using a transient instance, on the
other hand, you have to manually merge it into the existing record in the database using the merge()
operation on the persistence manager.
But, this approach is flawed. By working on a new instance of the entity during postback, you lose out on
the optimistic locking feature of JPA and Hibernate. Both rely on a special property, either a numeric or
timestamp type annotated with @Version, to track which version of the record was "checked out" for editing.
On postback, you want to retain that value so that the persistence manager is capable of detecting if a change
occurred to the record while it was being worked on. This type of locking is termed "optimistic" because it
hopes for the best and only aborts the update if the database record was changed. Pessimistic locking, which is
a formal database lock, prevents anyone else from accessing the record while it is being updated. Holding long
term locks on database resources is a horribly bad idea for performance reasons, especially when it relies on a
user interaction to be released. You are far better off using optimistic locking and presenting the user with the
two records side-by-side when a conflict does occur. If you can justify the use of pessimistic locking, then
consult the vendor reference documentation or Java Persistence with Hibernate for more details on how to
instrument it. You won't find any special locking functionality of the pessimistic variety in Seam.
But what about storing the entity instance in the session? Using the session has the advantage of retaining
the instance that was initially retrieved from the database so that the new request values are applied directly to
that instance. In this case, the optimistic locking feature is available. However, you may still end up with a
detached instance if the persistence manager has closed in the interim. The persistence manager, too, needs to
be stored in a long term scope. If the postback is using a different persistence manager than the one that
originally loaded the entity instance, then not only are the changes to the entity instance not automatically
synchronized to the database, requiring you to use merge(), but merging from one persistence manager to
another
gets
messy,
is
error
prone,
and
basically
just
dangerous.
And
the
LazyInitializationException returns.
Once again, Seam remedies this situation by scoping the persistence manager to the conversation, and
offering up the conversation to store the entity instance along with it. To use this feature, you do need to have a
Licensed to Jaroslaw Gilewski <[email protected]>
long-running conversation that connects the initial request with the postback. In all, a long-running
conversation is started, the entity instance is retrieve from the database, and it is overlaid on the editor form.
When the Save button is activated, a single transaction stretches from the Restore View phase to the end of the
Invoke Application phase, letting JSF perform its model updates and invoke the action handler all with ACID
guarantees. When the transaction completes, the optimistic lock check executes and if it gives the all clear, the
changes are automatically synchronized with the database without any work on your part.
NOTE
If the request values are applied to the entity instance within the confines of an active persistence context,
then the modifications to the object are automatically detected and synchronized with the database on the
next flush event. There is no need to issue an explicit "update" command. Automatic dirty checking, as this
is called, is one of the main benefits of using ORM.
Seam almost makes it difficult not to get persistence right. In fact, I had a hard time trying to "break"
Seam so that I could demonstrate incorrect behavior. The key to Seam's success is the conversation scope and
the transaction management that is weaved into the JSF life cycle.
The rest of the story
There are two more parts of Seam's persistence strategy, while not as crucial as the two just discussed, by no
means insignificant. These enhancements are handled by the Seam container and made possible by the
component model.
z
Match the behavior of Hibernate and JPA
z
Proxies the persistence managers (Hibernate and JPA) to enable the use of EL notation in entity queries
z
Normalize the difference in the signatures of JTA and non-JTA transactions
Regardless of whether you are using Hibernate or JPA as the persistence provider, Seam ensures that they
behave consistently. They both are scoped to the conversation (and can therefore be extended), they both tap
into transactions the same way, and they both support EL notation in entity queries. If you are using Hibernate
as the provider, you can even use Lucene-based full text queries with JPA just as you can with native
Hibernate.
As you may come to expect by now, Seam allows the use of value binding expressions within a JPQL or
HQL query just as it does in JSF messages, log messages, and in just about every other package of Seam. This
feature offers a nice shorthand alternative to using named parameters and even opens the door for parameterize
being populated through a reference to a factory component (factoried parameters).
Finally, Seam shields your code from the transaction environment by providing its own JTA
UserTransaction implementation. Seam delegates to either the local resource transaction or the actual JTA
transaction, depending on how you have configured Seam for you application. Most of the time, you don't even
have to interact with the transaction API directly because you either let Seam begin and end transactions in the
JSF life cycle or you decorate business methods with annotations that specify the boundaries of the transaction
declaratively. The important part is that underneath the covers, Seam behaves consistently whether running in
a JTA or non-JTA environment.
need transition sentence...perhaps about seeing how this is set up
9.2 Setting up a persistence unit in Seam
As you learned in chapter 8, section 8.1.3, the first step to managing entities is to create a persistence unit.
Seam-managed persistence is no different. This step is affected by your choice of JPA or Hibernate, as there
are different built-in components for managing the two frameworks. Once you make that call, you need to
Licensed to Jaroslaw Gilewski <[email protected]>
create the appropriate persistence unit descriptor—META-INF/persistence.xml if you are using JPA or
hibernate.cfg.xml if you are using Hibernate.
Before we go jumping into the Seam configuration for loading a persistence unit, I want to explain how
Seam's managed-persistence fits in with the rest of the world. Setting up a persistence unit is a strongly
defended piece of turf. The reason is because you only want to start up a persistence manager factory one time
for each persistence unit in your application. Not only is loading a persistence unit an expensive operation, it is
also important that it be centralized to allow persistence contexts to be shared amongst components. But, all
the containers—Java EE, Seam, Spring, and so on—want to take control of this task and do it their own way.
The question becomes, "Who is in charge?"
At the forefront of this duty is the standard Java EE 5 container, which will automatically load any
persistence units available on the classpath. Spring also offers a nice mechanism for wiring together a
persistence manager factory from either a persistence unit descriptor or from configuration defined in the
Spring configuration file itself. I don't want to assume that you are going to be content with allowing Seam to
take over this task.
Fortunately, Seam is very flexible in this department. You have a variety of options. You do not have to
use Seam to setup your persistence manager factory. Seam can get a handle on an established persistence
manager factory by tapping into either JNDI or the IoC bridge, the latter being the hook to using a persistence
unit loaded by Spring. You have already seen how to configure a managed persistence unit in standard Java
EE, so let's move on to configuring one in Seam. In chapter 15 you will get a chance to tap into Spring's
persistence unit configuration.
9.2.1 Seam's persistence manager factories
To handle the task of bootstrapping a persistence unit, Seam provides manager components for both JPA and
Hibernate which wrap the runtime configuration objects of the respective framework. For lack of a better term,
these components will be called Seam-managed persistence units.
Once defined, a Seam-managed persistence unit can be injected into the property another Seam component
using the @In annotation. The property's type must correspond to the type of the persistence manager factory.
For JPA, the type is an EntityManagerFactory:
@Name("courseAction")
public class CourseAction {
@In private EntityManagerFactory;
}
For Hibernate, the type is a SessionFactory:
@Name("courseAction")
public class CourseAction {
@In private SessionFactory;
}
Table 9.1 shows the mapping between each Seam-managed persistence unit and the persistence manager
factory that it manages.
Table 9.1 A mapping of the Seam-managed persistence unit to the persistence configuration that it manages.
Persistence framework
Seam component (org.jboss.seam.persistence.*)
Persistence configuration it manages
JPA
EntityManagerFactory
javax.persistence.EntityManagerFactory
Hibernate
HibernateSessionFactory
org.hibernate.SessionFactory
Licensed to Jaroslaw Gilewski <[email protected]>
The manager design allows Seam to tie the life cycle of the native persistence manager factory to the life
cycle events of a Seam component. Each of the two components listed above has a built-in @Create method,
which starts the persistence manager factory, and a built-in @Destroy method, which closes it. These
components are also scoped to the application context and are configured to initialize on application startup, as
defined by the @Scope(ScopeType.APPLICATION) annotation and @Startup annotation, respectively.
When the Seam container is started, the @Create method will be called on this component, which, in turn,
initializes the persistence manager factory.
Loading persistence units when the application starts and storing them for the lifetime of the application is
the recommended practice since persistence manager factory objects are both expensive to create—in terms of
time—and thread-safe. Listing 9.1 shows the relevant portions of the EntityManagerFactory component
just discussed. The HibernateSessionFactory component has an equivalent composition.
Listing 9.1 The Seam component that manages an entity manager factory.
@Scope(ScopeType.APPLICATION)
@BypassInterceptors
@Startup
public class EntityManagerFactory
{
private javax.persistence.EntityManagerFactory
entityManagerFactory;
private String persistenceUnitName;
private Map persistenceUnitProperties;
@Unwrap
public javax.persistence.EntityManagerFactory
getEntityManagerFactory() {
return entityManagerFactory;
}
#3
@Create
#1
public void startup(Component component) throws Exception {
if (persistenceUnitName == null) {
persistenceUnitName = component.getName();
#2
}
entityManagerFactory = createEntityManagerFactory();
}
@Destroy
public void shutdown() {
if (entityManagerFactory != null) {
entityManagerFactory.close();
}
}
// ...
}
<Annotation #1> Create on application startup
<Annotation #2> Uses the component name as fall back
<Annotation #3> Returns the native entity manager factory
<Annotation #4> Close on application shutdown
Licensed to Jaroslaw Gilewski <[email protected]>
#4
As you can see in this listing, the @Unwrap method returns the native persistence manager factory, in this
case a javax.persistence.EntityManagerFactory. If you recall from chapter 6, the @Unwrap
method is what makes the class a manager component. When injected into another Seam component using
@In, the return value of the @Unwrap method, the persistence manager factory, is injected rather than the
component itself. Thus, Seam can support injecting container-managed persistence units just like Java EE,
only now Seam plays the role of container.
Both EntityManagerFactory and HibernateSessionFactory are actually just component
templates, as evidenced by the fact that neither of them have a @Name annotation. In order to actualize them as
Seam components, you have to declare them in the component descriptor. The component definition must
include both a name and a pointer to the persistence unit (either a persistence unit or JNDI name). Only then
will the persistence manager factory be loaded when Seam starts.
Defining an EntityManagerFactory
The way in which the component is configured is dependent on the persistence framework. For JPA, only the
persistence unit name is needed, whereas with Hibernate, there is a much richer set of options. As with all of
the built-in Seam components, an XML namespace is provided to ease the XML configuration burden. These
two components fall under the http://jboss.com/products/seam/persistence, typically
abbreviated as persistence.
Let's first look at the JPA persistence unit configuration:
<persistence:entity-manager-factory name="entityManagerFactory"
persistence-unit-name="open18"/>
That's pretty much all there is to it! In this component definition, the persistence unit name, open18, maps
to a persistence unit defined in META-INF/persistence.xml:
<persistence-unit name="open18" transaction-type="JTA">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<jta-data-source>open18Datasource</jta-data-source>
<properties>
...
</properties>
</persistence-unit>
Inside of the manager component, Seam uses this persistence unit name to create an
EntityManagerFactory using the following method call:
EntityManagerFactory entityManagerFactory =
Persistence.createEntityManagerFactory("open18");
If the persistence-unit-name attribute is excluded, then the name of the component is used as the
persistence unit name, which would be entityManagerFactory in the example above.
NOTE
The data source configuration for the persistence unit shown in the descriptor is external to Seam. You can
either refer to the name of a javax.sql.DataSource in the JNDI registry, which is the configuration
used by seam-gen, or you can define the credentials using the persistence unit properties, which vary by
JPA implementation. The JNDI resource is typically a better choice if abstraction is the goal. If you are
Licensed to Jaroslaw Gilewski <[email protected]>
running the application outside of a Java EE container, and need a local JNDI registry, use of the embedded
JBoss container is recommended.
JPA allows you to provide vendor-specific properties in the form of key-value pairs that are passed to the
method that creates the EntityManagerFactory:
Map properties = new Map();
properties.put("hibernate.hbm2ddl.auto", "validate");
properties.put("hibernate.show_sql", "true");
EntityManagerFactory entityManagerFactory =
Persistence.createEntityManagerFactory("open18", properties);
Typically, these properties are defined inside of the <properties> element in the persistence unit
deployment descriptor. But, you can, instead, choose to define these vendor-specific properties on the manager
component itself using the standard component configuration mechanism:
<persistence:entity-manager-factory name="entityManagerFactory"
persistence-unit-name="open18">
<persistence:persistence-unit-properties>
<key>hibernate.hbm2ddl.auto</key><value>validate</value>
<key>hibernate.show_sql</key><value>@[email protected]</value>
</persistence:persistence-unit-properties>
</persistence:entity-manager-factory>
Notice that the hibernate.show_sql key refers to a tokenized parameter. This token is expected to be
defined in a component.properties file located somewhere on the classpath:
hibernate.show_sql=true
Using Seam's component configuration mechanism to supply the persistence unit properties gives you the
flexibility to tune them for a specific environment. One way is with tokenized properties, the other being value
expressions.
Defining a Hibernate SessionFactory
Hibernate is configured in much the same way, only instead of providing a persistence unit name, you indicate
the location where the Hibernate configuration resides on the classpath. You only have to specify this location
if you are not using following the standard Hibernate convention. Given that Seam merely delegates to
Hibernate to load its SessionFactory, Hibernate automatically looks for hibernate.properties and
hibernate.cfg.xml on the classpath. If you are using the standard file naming convention, the component
definition is trivial:
<persistence:hibernate-session-factory name="sessionFactory"/>
If, on the other hand, you were configuring more than one Hibernate persistence unit, you would need to
indicate any non-standard configuration location and assign different names to the components:
<persistence:hibernate-session-factory name="sessionFactory"/>
<persistence:hibernate-session-factory name="teetimeSessionFactory"
cfg-resource-name="hibernate-teetime.cfg.xml"/>
Licensed to Jaroslaw Gilewski <[email protected]>
The manager component then uses one of two variants of the configure() method on the Hibernate
configuration object to load the SessionFactory:
AnnotationConfiguration configuration = new AnnotationConfiguration();
if (cfgResourceName == null) {
configuration.configure();
#1
} else {
configuration.configure("teetime.cfg.xml");
#2
}
SessionFactory sessionFactory = configuration.buildSessionFactory();
<Annotation #1> Used when cfg-resource-name attribute is not provided
<Annotation #2> Used when cfg-resource-name attribute is provided
The Hibernate manager component gives you the option to specify Hibernate configuration properties 31 in
the component definition. These properties are used to perform programmatic configuration of the Hibernate
SessionFactory:
<persistence:hibernate-session-factory name="sessionFactory">
<persistence:cfg-properties>
<key>hibernate.dialect</key>
<value>org.hibernate.dialect.H2Dialect</value>
<key>hibernate.connection.driver_class</key>
<value>org.h2.Driver</property>
<key>hibernate.connection.username</key>
<value>open18</value>
<key>hibernate.connection.password</key>
<value>tiger</value>
<key>hibernate.connection.url</key>
<value>jdbc:h2:/home/twoputt/databases/open18-db/h2</value>
<key>hibernate.hbm2ddl.auto</key>
<value>validate</value>
<key>hibernate.show_sql</key>
<value>@[email protected]</value>
</persistence:cfg-properties>
</persistence:hibernate-session-factory>
You have to choose between the cfg-resource-name attribute or the <cfg-properties> nested
element when configuring the SessionFactory. If both configuration mechanisms are present, the cfgresource-name will take precedence and <cfg-properties> ignored.
The Hibernate manager component offers a rich set of extended configuration properties for specifying the
location of mapping artifacts. The properties are defined using the <mapping-classes>, <mappingfiles>, <mapping-jars>, <mapping-packages>, and <mapping-resources> elements. Please
consult the Hibernate documentation for how to use these settings.
The manager components are one approach you can take to loading a persistence unit. However, since I
have already pointed out that there may be bicker amongst the children, let's look at how to put the runtime
persistence unit configuration in JNDI so that everyone can share.
31
For a full list of properties that can be configured for Hibernate, either when used natively or as a JPA implementation, please
refer to the Hibernate reference documentation.
Licensed to Jaroslaw Gilewski <[email protected]>
Calling on a persistence manager factory in JNDI
While placing objects in JNDI is not specifically a Seam feature, it will benefit Seam downstream when we get
to setting up the persistence manager. There are two ways to put the entity manager factory in JNDI:
z
define a persistence-unit resource ref
z
bind to the global JNDI
The first option is a standard Java EE 5 configuration and can only be used with JPA. The second option is
only available if you are using Hibernate in any environment where JNDI is available or you are using the
Hibernate JPA implementation and running the application on JBoss AS. Let's start by looking at the standard
implementation.
While a standard Java EE 5 container will load persistence units defined in the METAINF/persistence.xml, the resulting container-managed EntityManagerFactory objects are not placed into
the global JNDI registry by default. To expose the EntityManagerFactory objects through JNDI, you
must set up resource references. The reference can be defined using XML in a deployment descriptor or by
using an annotation. Only the XML configuration will be covered here. The XML configuration is declared in
web.xml for use in web-tier components and ejb-jar.xml for use in EJB 3 components. The XML syntax used
in either descriptor is the same:
<persistence-unit-ref>
<persistence-unit-ref-name>open18/emf</persistence-unit-ref-name>
<persistence-unit-name>open18</persistence-unit-name>
</persistence-unit-ref>
This reference is accessed using the JNDI name java:comp/env/open18/emf against the initial
context. You will not need to worry about performing this lookup, though. Seam can accept a JNDI name in
the configuration of the persistence manager, which we are going to cover in the next section.
When you are using the Hibernate JPA implementation on JBoss AS, you can cheat a bit by using a
Hibernate-specific property in your persistence unit deployment descriptor to tell JBoss to bind the
EntityManagerFactory to JNDI:
<persistence-unit name="open18" transaction-type="JTA">
...
<properties>
<property name="jboss.entity.manager.factory.jndi.name"
value="open18/emf"/>
</properties>
</persistence-unit>
The reason for this blatant hack is that JBoss AS 4.2 does not yet implement the entire Java EE 5 spec,
coming up short in the area of persistence archives. JBoss AS does not support the use of persistence unit
references, so the only way to put the EntityManagerFactory in the JNDI registry of a JBoss AS is to use
the jboss.entity.manager.factory.jndi.name Hibernate property. To make matters worse, you do
not use the java:comp/env namespace prefix when looking up the resource in the InitialContext, but
rather the exact value defined by this property. Another limitation of the persistence support in JBoss AS 4.2 is
that it only deploys standalone persistence archives that are explicitly listed in application.xml as an EJB
module. These are temporary workaround until you are ready to move to JBoss 5.0 AS, which has full support
for persistence archives.
Hibernate has more of an excuse for using non-standard tricks. Hibernate cannot benefit from all of this
fancy container-managed loading of persistence units since it is not part of the Java EE standard. Despite being
Licensed to Jaroslaw Gilewski <[email protected]>
an outsider, Hibernate has always supported placing the SessionFactory into the JNDI registry after
startup. This feature is often overlooked because its configuration is so subtle. You simply add the name
attribute to the session-factory node in the Hibernate configuration, and the value is used as the JNDI
name to which to bind.
<hibernate-configuration>
<session-factory name="open18/SessionFactory">
...
</session-factory>
</hibernate-configuration>
Both the jboss.entity.manager.factory.jndi.name Hibernate property and the Hibernate
session factory name attempt to write to the JNDI registry from the application code. Please see the sidebar on
restrictions imposed in this situation.
Writing to the JNDI registry is not so easy
The naming restrictions imposed when writing to the JNDI registry are extremely variant across
application servers. I wish that I could give you a universal formula, but it just doesn't exist. For
instance, Glassfish does not allow you to write to the java:comp JNDI namespace. JBoss, on the
other hand, supports the use of the java:/ namespace, which is not available on any other server.
Tomcat disables writing to the JNDI registry at runtime entirely. When in doubt, scrap the prefix and
just use a unqualified name for the resource, such as open18/SessionFactory. Please note that
writing to the JNDI registry is only required when you want to expose a Hibernate SessionFactory
or, if you are using JBoss AS 4, an EntityManagerFactory. When using standard persistence unit
references, there is no need to write to the JNDI registry as it is handled automatically by the
application server.
That just about exhausts all the ways that you can set up a persistence manager factory in both Seam and
Java EE 5. Let's move on to the task of configuring and creating a Seam-managed persistence manager.
9.2.2 Seam-managed persistence contexts
While it is certainly possible to inject a persistence manager factory into your Seam components, this would
require you to open and close the persistence manager in the application logic, termed an application-managed
persistence manager. It's far more beneficial to allow Seam to managed the persistence context so that it can be
extended beyond the scope of the current transaction or to just alleviate the burden of managing it yourself.
As an alternative to Java EE container-managed persistence contexts, you can allow Seam to take control
of this resource. Once the Seam-managed persistence context is defined, which will be covered shortly, you
can inject it into the property of another Seam component using the @In annotation. The property's type must
correspond to the persistent manager that is being injected by Seam. When managing the JPA persistence
context, an EntityManger object is injected:
@Name("courseAction")
public class CourseAction {
@In private EntityManager em;
}
When managing the Hibernate session, a Session object is injected:
@Name("courseAction")
public class CourseAction {
Licensed to Jaroslaw Gilewski <[email protected]>
@In private Session session;
}
Seam, again, uses a manager component to control the life cycle of the native persistence manager. Table
9.2 shows the persistence manager that Seam creates for each persistence framework.
Table 9.2 A mapping of the Seam-managed persistence context to the persistence manager that it creates.
Persistence framework
Seam component (org.jboss.seam.persistence.*)
Persistence manager it creates
JPA
ManagedPersistenceContext
javax.persistence.EntityManager
Hibernate
ManagedHibernateSession
org.hibernate.Session
The point in which the Seam-managed persistence context component creates the native resource differs
from that of the Seam-managed persistence unit component. Here, the native resource is allocated when the
component is injected rather than at container startup. If a JTA transaction is active when the @Unwrap
method is invoked, the native persistence manager will be enlisted in the JTA transaction. If the provider is
Hibernate and filters are enabled, they will be applied to the persistence manager at this time.
The Open Session in Conversation pattern
When the Seam-managed persistence context is retrieved, it is stored in the active conversation context—
regardless of whether the conversation is temporary or long-running. The life cycle of the underlying
persistence manager is then bound to the lifetime of the conversation. When the conversation ends, the
close() method is called on the persistence manager to close the persistence context.
This scoping differs from container-managed persistence contexts, which are only stored in the
conversation by way of being bound to a conversation-scoped SFSB. If the owning SFSB were to be closed
prior to the conversation ending, the persistence context would go along with it. By Seam storing the
persistence contexts in the conversation directly, they are equally accessible to all components operating in the
scope of that conversation, and they remain open for the duration of the conversation. This prevents entity
instances from being detached and circumvents the need for the merge() operation as discussed in section
9.1.3. The persistence context also outlives any transactions that are opened and closed during the
conversation, attaching and detaching itself from the transaction as necessary, a feature which is explored in
section 9.3.2.
As a better alternative to the infamous Open Session in View pattern, you now have the Open Session in
Conversation pattern. You can tune the duration of the persistence context simply by adjusting the
conversation boundaries. You should no longer have to fear the exceptions such as the
LazyInitializationException or the NonUniqueObjectException, both of which are both
caused by premature termination of the persistence context.
Defining a managed persistence context
The Seam-managed persistence contexts are also component templates. To make them available to the
application, they must be declared in the component descriptor as well. Of course, to create a persistence
manager, you need to use a persistence manager factory. Thus, when you declare the Seam-managed
persistence context, you must supply a reference to a persistence manager factory either as a value expression
or a JNDI name. The later is the key that hooks Seam up with a container-managed persistence unit loaded by
the application server.
If you configure Seam to load a JPA persistence unit, then you would inject a reference to the
EntityManagerFactory into the ManagedPersistenceContext as a value expression:
Licensed to Jaroslaw Gilewski <[email protected]>
<persistence:managed-persistence-context name="entityManager"
entity-manager-factory="#{entityManagerFactory}"
auto-create="true"/>
Likewise, if you configure Seam to load a Hibernate persistence unit, then you would inject a reference to
the SessionFactory into the ManagedHibernateSession also by using the value expression syntax:
<persistence:managed-hibernate-session name="session"
hibernate-session-factory="#{sessionFactory}"
auto-create="true"/>
In the two previous declarations, the auto-create attribute is set to true. By default, the Seammanaged persistence context components are defined with the auto create feature disabled. In order to refer to
these components using an @In annotation without having to supply the create attribute, the auto create
setting is enabled globally in the component definition.
I have also chosen to name the components entityManager and session, respectively. There are
advantages to using these standard names that save you a couple of keystrokes. The Seam CRUD framework,
which you will learn about in the next chapter, uses these names to locate the managed persistence context if a
component name is not provided in the definition of those components.
In either of the component definitions shown above, you can use a container-managed persistence unit by
specifying a JNDI name, instead of relying on the Seam-managed persistence unit referenced in the value
expression. For a ManagedPersistenceContext, the entity-manager-factory attribute is replaced
with the persistence-unit-jndi-name attribute:
<persistence:managed-persistence-context name="entityManager"
persistence-unit-jndi-name="java:comp/env/open18/emf"
auto-create="true"/>
The JNDI name referenced in this example assumes the use of a standard persistence unit reference. If you
used the JBoss hack discussed earlier to bind the EntityManagerFactory to JNDI, then you need to
remove the java:comp/env/ portion from the value of the persistence-unit-jndi-name attribute in
the example above.
The ManagedHibernateSession uses the value of the session-factory-jndi-name attribute to
lookup the SessionFactory that is bound to the global JNDI registry:
<persistence:managed-hibernate-session name="session"
session-factory-jndi-name="open18/SessionFactory"
auto-create="true"/>
The downside to drawing the persistence manager factory from the JNDI registry is that you don't know
whether or not it is configured correctly when you deploy your application until the first time you attempt to
retrieve it. This opens up a great opportunity to be proactive and validate the configuration when the Seam
container starts.
Validating the persistence context at startup time
To remedy this uncertainty of relying on a JNDI resource, you can register a application-scoped @Startup
component that performs a sanity check at the time the application loads to verify that a managed persistence
context can be successfully created:
Licensed to Jaroslaw Gilewski <[email protected]>
@Name("persistenceContextValidator")
@Scope(ScopeType.APPLICATION)
@Startup
public class PersistenceContextValidator {
private ValueExpression<EntityManager> entityManager;
@Create
public void onStartup() {
if (entityManager != null) {
try {
EntityManager em = entityManager.getValue();
entityManager.setValue(null);
#1
} catch (Exception e) {
throw new RuntimeException("The persistence context "
+ entityManager.getExpressionString()
+ " is not configured properly", e);
}
}
}
public void setEntityManager(
ValueExpression<EntityManager> entityManager) {
this.entityManager = entityManager;
}
}
<Annotation #1> This closes the entity manager
You then register this in the component descriptor as follows:
<component name="persistenceContextValidator">
<property name="entityManager">#{entityManager}</property>
</component>
This validator should start to open your mind with ways that you can take advantage of the Seam
component model to incorporate additional hooks to manage resources controlled by the Seam container.
With the Seam-managed persistence context successfully loaded, you are ready to start using it in your
application. But not without transactions, you won't. Let's see how Seam-managed transactions are configured
and then how you go about implementing an optimistic transaction by delaying the flushing of the persistence
context until all the data has been prepared.
9.3 Coordinating transactions in Seam
Transactions are another area of turf that is tightly guarded. JTA is the standard transaction mechanism in Java
EE containers, the persistence frameworks offer resource-local transactions, and Spring has its platform
transaction manager. Which one do you choose? Seam relegates this decision to a mere implementation detail
by normalizing all of the transaction implementation under its own abstraction layer. But here's the punchline.
Seam bases this abstraction on the standard JTA interfaces. You can take advantage of Seam's transaction
management without tying your code to yet another third party transaction API. Seam is able to accomplish
this by providing its own implementation of JTA that merely wraps the native transaction managers. In
implementing the interface, Seam also adds a couple of its own convenience methods that help ease the task of
Licensed to Jaroslaw Gilewski <[email protected]>
managing the transaction in the application code. Table 9.3 shows the transaction managers that are supported
by Seam's abstraction layer.
Table 9.3 Transaction managers supported by Seam's abstraction layer.
Seam transaction manager
(org.jboss.seam.transaction.*)
How installed
UTtransaction
enabled by default for non-EJB environment
Native transaction manager
application-managed
UserTransaction
EntityTransaction
<transaction:entity-transaction>
JPA EntityTransaction
HibernateTransaction
<transaction:hibernate-transaction>
Hibernate Transaction
CMTTransaction
enabled by default for EJB environment
container-managed
UserTransaction available via
the EJBContext object
NoTransaction
fall back when UserTransaction is not available or
<transaction:no-transaction> is declared
used when no transaction managers
are available
You can only have one of these transaction managers active per application. If you want to use application
managed JTA, as retrieved from the JNDI registry, you don't have to configure anything. Seam uses JTA as the
default transaction manager. If you would rather use resource-local transactions, perhaps in an environment
where JTA is not available, you must configure the one appropriate for your choice of persistence manager
framework as shown in table 9.3. For instance, to switch to the JPA resource-local transaction manager, you
would use the following stanza in the component descriptor:
<transaction:entity-transaction entity-manager="#{entityManager}"/>
Similarly, you can use a Hibernate resource-local transaction manager:
<transaction:hibernate-transaction session="#{session}"/>
If, on the other hand, you are using container-managed transaction in an EJB environment, you may still
want to make Seam aware of transaction synchronization events. To register this transaction synchronization
component, use the following declaration:
<transaction:ejb-transaction/>
Seam uses a stateful session bean that implements the LocalEjbSynchronizations and
SessionSynchronization interfaces to pass these events on to other components in the Seam container.
As such, the jboss-seam.jar must be deployed as part of the EAR to allow itself to register these synchronizes
as
an
EJB3
component.
Seam
passes
on
the
EJB
transaction
events
(org.jboss.seam.beforeTransactionCompletion
and
org.jboss.seam.afterTransactionCompletion) to Seam components using its own internal event
mechanism.
You can define the boundaries of Seam-managed transactions declaratively using annotations or inline in
the business logic using the transaction API. Let's start by looking at the @Transaction annotation.
9.3.1 Controlling Seam-managed transactions
Once again with transactions, you have a choice as to whether you want to use Seam or the application server
to manage the resource. If you choose to go with container-managed transactions (CMT), you don't have to do
Licensed to Jaroslaw Gilewski <[email protected]>
anything different. Seam can treat the application-managed components as Seam components, but allow the
container to handle all of the transaction details.
If you decide to allow Seam to manage your transactions, you have to take over control. From the
viewpoint of the container, you are using application-managed transactions when Seam is in control. However,
you still get all the benefits of container-managed transactions.
Seam provides a @Transactional annotation that can used to control transaction propagation around
methods on JavaBean components, since JavaBean components do not have access to container-managed
transactions. This annotation is equivalent to the @TransactionAttribute annotation in the Java EE API.
Seam uses a custom interceptor that is used to wrap method calls to JavaBean components in transactional
units of work as indicated by the @Transactional annotation. Seam only applies this interceptor to
JavaBean components, leaving the work of managing transactions for Java EE components up to the container.
Table 9.4 shows a summary of this annotation.
Table 9.4 The @Transactional annotation, used to propagate transactions around Seam component methods.
Name:
Transactional
Purpose:
Specifies the transaction propagation that should be used for a JavaBean component, either at the component level
or at the method level. Seam uses a TransactionInterceptor to perform the method invocation within a
transactional unit of work.
Target:
TYPE (class), METHOD
Attribute
Type
Function
value
TransactionPropagationType
Indicates how the transaction should be handled around the method call. The
default is REQUIRED.
The @Transactional annotation can either be applied at the class or method level. When it is applied
at the class level, it takes affect for all public methods unless the method has its own @Transactional
annotation with a different propagation value. The propagation values that are permitted are summarized in
table 9.5. Not that Seam does not support suspending or nesting transactions on JavaBean components.
Table 9.5 The transaction propagation types supported by the @Transactional annotation
Propagation type
Purpose
REQUIRED
Indicates that a transaction is required to execute the method. If a transaction is not active, Seam will
begin a new transaction. This is the default value.
SUPPORTS
Indicates that the method is permitted to execute in the presence of an active transaction, but it will
not begin a transaction if one is not active.
MANDATORY
Indicates that an active transaction is required to execute the method. A runtime exception is thrown
if a transaction is not already in progress.
NEVER
Indicates that a transaction should not be active when this method is called. A runtime exception will
be thrown if a transaction is active.
Consider the case where you want to ensure that all of the public methods on your JavaBean component
are executed within a required transaction. You would define the @Transactional annotation at the class
level:
@Name("courseAction")
@Transactional
public class CourseAction {
public void addCourse(Course course) {
// perform work of saving course
}
}
Licensed to Jaroslaw Gilewski <[email protected]>
The
class-level
annotation
is
inherited
by
all
public
methods
as
if
@Transactional(TransactionPropagationType.REQUIRED) had been applied to them. However,
if you wanted to add a method that should only be executed within the scope of an existing transaction, you
could provide an override for that method:
@Name("courseAction")
@Transactional
public class CourseAction {
public void addCourse(Course course) {
// perform work of saving course
}
@Transactional(TransactionPropagationType.MANDATORY)
public void storeAuditInfo(Course course, Golfer golfer) {
// record audit information about this insert
}
}
The transactions shown above are atomic database transactions. We are now going to look at another type
of transaction, the application transaction, which is more of a concept and design pattern than an actual
resource.
9.3.2 Application transactions
It's been a long haul, but we have finally arrived at the discussion of application transactions. While they
require coordination from a lot of different components, they are without a doubt, the key to ensuring data
consistency in a web-based application.
When the @Begin annotation was first introduced in chapter 7, discussion of the flushMode attribute
was left out because it is an extension that deals with persistence. This attribute dictates to Seam which flush
mode to set on the active persistence manager when the conversation is started. As mentioned earlier, flushing
is the process of synchronizing changes in the persistence context to the database. Within the scope of a
transaction, it doesn't matter much when this sync occurs since the data sent to the database in a write
operation could still be pulled out if something causes the transaction to fail. However, an extended persistence
context often spans many distinct transactions, operating on the database independently. To allow the extended
persistence context to be atomic, we need to use an application transaction.
Holding all changes until the end
An application transaction is one that spans an entire use case, notably those involving multiple requests. You
certainly wouldn't want to hold a database lock for that long. As a compromise, you accumulate pending
changes to the database in the extended persistence context while the use case is in progress. When you are
ready to commit the changes to the database, they are bundled together in a single transaction. The key element
here is preventing the partial changes from being sent to the database any earlier. A premature flush of the
persistence context would result in permanent changes being made to the database prior to the final step. If a
subsequent request encounters a failure or rollback, this could end up leaving the database in an inconsistence
state since the commits that happened in the previous requests would not be undone. One workaround is to
resort to using a compensating transaction, but they are laborious and equally error prone. The premature flush
could also result in other connections seeing the partially complete data. For these reasons, it is important to
ensure that changes to the database are atomic, even when collected from multiple screens.
Licensed to Jaroslaw Gilewski <[email protected]>
Before explaining how to configure the conversation and persistence context to work in tandem to support
an application transaction, I want to explain the difference between how the JPA standard defines an
application transaction and how it is implemented as an extension in Hibernate. I have to warn you that this a
very heated debate surrounding this configuration.
A wrinkle in the specification and Hibernate's solution
The JPA specification only supports two flush modes, AUTO and COMMIT. The specification states that if
you want to perform an application transaction, you should set the flush mode to COMMIT and work nontransactionally until you are ready to commit. At the end of the use case, you call a transactional method to
synchronize the changes to the database. The flush occurs when the transaction commits.
However, taking this proposed approach means completely avoiding the use of transactions in the interim,
which is a very bad design. You are once again tiptoeing through your own application. Even when performing
read operations, you always want to work within well defined transaction boundaries. Therefore, Hibernate
includes a MANUAL flush mode as an extension to the JPA specification. When this flush mode is used, the
persistence context will only be flushed when a call is made to the flush() method on the persistence
manager API. This mode gives you the flexibility to take your persistence context in and out of transactions
without risking a premature flush. If you want to use application transactions in your application, the Hibernate
JPA implementation along with the MANUAL flush mode is highly recommended.
Fortunately, Seam can handle the dirty work of switching the flush mode to MANUAL when the
conversation begins, but it's up to you to flush it when you see fit, typically in the method that ends the
conversation. In the next chapter you discover that the CRUD operations on the Home component from the
Seam application framework handles the flush task for you.
An application transaction in practice
Let's return to the golf course entry wizard example. The CourseWizardAction component, shown in
listing 9.2, now supports both adding a new course and updating an existing one, initiated by the action listener
methods addCourse() and editCourse(), respectively. Notice that these methods switch to a manual
flush mode while at the same time beginning a long-running conversation. This opens a Seam-style application
transaction. Subsequent calls to transaction action listener methods on this class (aside from the save()
method) do not flush the changes to the Course entity to the database. Instead, all changes are sent in an atomic
commit when the save() method is called. At that time, the default flush mode of the persistence manager is
restored.
Listing 9.2 The component that manages the application transaction of adding a new course.
@Name("courseWizardAction")
@Scope(ScopeType.CONVERSATION)
@Transactional
public class CourseWizardAction {
@In private EntityManager entityManager;
@Out private Course course;
@Begin(flushMode = FlushModeType.MANUAL)
public void newCourse() {
course = new Course();
entityManager.persist(course);
}
Licensed to Jaroslaw Gilewski <[email protected]>
// 1
@Begin(flushMode = FlushModeType.MANUAL)
public void editCourse(Long id) {
course = entityManager.find(Course.class, id);
}
// 1
public String submitBasicInfo() {
return "next";
}
// 2
public void addTeeSet() {
TeeSet teeSet = new TeeSet();
course.addTeeSet(teeSet);
}
// 2
public String saveScorecard() {
return "next";
}
// 2
public String uploadCoursePicture() {
return "next";
}
// 2
@End
public String save() {
entityManager.flush();
return "confirm";