Beruflich Dokumente
Kultur Dokumente
Foreword
Acknowledgments
I'd like to thank:
• Helena Lei for proofreading this book.
• Eugenia Chan Peng U for doing book cover and layout design.
4 Essential JSF, Facelets & JBoss Seam
Table of Contents
Foreword.........................................................................................3
How to learn JSF, Facelets and JBoss Seam easily?...............3
Products covered in the book.....................................................3
Target audience and prerequisites.............................................3
Acknowledgments.......................................................................3
Chapter 1 Getting Started with JSF................................................7
What's in this chapter?...............................................................8
Developing a Hello World application with JSF.........................8
Installing Eclipse.........................................................................8
Installing Tomcat........................................................................9
Installing the JSF reference implementation............................10
Creating a Hello Word application............................................11
Generating dynamic content....................................................24
Retrieving data from Java code................................................31
How the JSP file generates the HTML code............................38
Debugging a JSF application...................................................40
Summary..................................................................................41
Chapter 2 Using Forms.................................................................43
What's in this chapter?.............................................................44
Developing a stock quote application.......................................44
Getting the stock quote symbol................................................44
Displaying the stock value........................................................52
Defining the page navigation....................................................54
Using a combo box...................................................................62
Inputting a date.........................................................................65
Handling conversion errors.......................................................70
Marking input as required.........................................................76
Using the calendar component.................................................77
Hooking up the managed beans..............................................80
Summary..................................................................................83
Chapter 3 Validating Input............................................................85
What's in this chapter?.............................................................86
Postage calculator....................................................................86
What if the input is invalid?......................................................93
Null input and validators...........................................................97
Validating the patron code........................................................99
Essential JSF, Facelets & JBoss Seam 5
Chapter 1
Chapter 1 Getting Started with JSF
8 Chapter 1 Getting Started with JSF
Installing Eclipse
You need to make sure you have Eclipse v3.3 (or later) installed and it is the
bundle for Java EE (the bundle for Java SE is NOT enough). If not, go to
http://www.eclipse.org to download the Eclipse IDE for Java EE Developers
(e.g., eclipse-jee-europa-fall-win32.zip). Unzip it into c:\eclipse. Then, create a
shortcut to run "c:\eclipse\eclipse -data c:\workspace". This way, it will store
your projects under the c:\workspace folder. To see if it's working, run it and
make sure you can switch to the Java EE perspective:
BUG ALERT: If you're using Eclipse 3.3.1, there is a serious bug in it: When
visually editing JSF pages Eclipse will frequently crash with an
OutOfMemoryError. To fix it, modify c:\eclipse\eclipse.ini:
Chapter 1 Getting Started with JSF 9
-showsplash
org.eclipse.platform
--launcher.XXMaxPermSize
Delete them
256m
-vmargs This line must be put
-Xms40m after -vmargs
-Xmx256m
-XX:MaxPermSize=256m
Installing Tomcat
Next, you need to install Tomcat. Go to http://tomcat.apache.org to download a
binary package of Tomcat 6.x (or later). Download the zip version instead of the
Windows exe version. Suppose that it is apache-tomcat-6.0.13.zip. Unzip it into
a folder, say c:\tomcat. Note that Tomcat 6.x works with JDK 5 or above.
Before you can run it, make sure the environment variable JAVA_HOME is
defined to point to your JDK folder (e.g., C:\Program Files\Java\jdk1.5.0_02):
If you don't have it, define it now. Now, open a command prompt, change to
c:\tomcat\bin and then run startup.bat. If it is working, you should see:
10 Chapter 1 Getting Started with JSF
Check this
Then while you're still in the Preferences window, choose "Server | Installed
Runtimes":
Click "Finish". Next, right click in the Package Explorer and choose "New |
14 Chapter 1 Getting Started with JSF
Keep clicking "Next" until you see the dialog below. Then choose your
implementation:
16 Chapter 1 Getting Started with JSF
Choose it
Then, if you're on the Internet, Eclipse may try to download some XML files and
may ask you to accept the licenses. Say yes. Finally, you should see the project
structure:
Chapter 1 Getting Started with JSF 17
To make the jstl-1.2.jar file available to it, copy it into the WebContent/WEB-
INF/lib folder. Right click the project and choose "Refresh" so that Eclipse see
the file.
Next, you'll create the web page. To do that, right click the WebContent folder
and choose "New | JSP":
Click "Finish". This will create a hello.jsp file in the WebContent folder with
some initial content:
To edit it visually, right click the hello.jsp file and choose "Open With | Web
Page Editor":
Chapter 1 Getting Started with JSF 19
Next, in the visual editing area, type "Hello world". Note that the source code will
change automatically:
20 Chapter 1 Getting Started with JSF
Alternatively, you could edit the source code and the visual display will change
automatically.
To run your application, you need to create a so-called "Tomcat instance". To
do that, right click the "Servers" tab at the bottom and choose "New | Server":
Choose your Hello project and click "Add". This way it will be added to that
Tomcat instance:
Chapter 1 Getting Started with JSF 23
Click "Finish". Then you should see this Tomcat instance in the "Servers"
window:
To run it, just click here.
Click the icon as shown above to run it (make sure you have indeed stopped the
Tomcat you started from the command prompt!). Then you will see some
messages in the Console window:
24 Chapter 1 Getting Started with JSF
http://localhost:8080/Hello/faces/hello.jsp
WebContent
hello.jsp
...
A palette is
here
Move the mouse over there and the palette will appear:
Expand the "JSF Core" folder. It contains quite some items. Each item is called
a tag and the folder is called a tag library (or tag lib):
26 Chapter 1 Getting Started with JSF
It is a tag lib
Each item is
a tag
Each tag lib contains some tags in it and has a unique URL as its identifier, just
like a Java package contains some classes in it and has a unique package
name:
http://java.sun.com/jsf/core com.foo
class Product {
<view> ...
<param> }
<selectItem> class Order {
...
}
Now, drag the <view> tag and drop it onto the page (either before the "Hello
World" text or after it. It doesn't matter):
Chapter 1 Getting Started with JSF 27
A <view> element will have been created. Why it is <f:view> instead of <view>?
"f" here is used as a short hand (the "prefix") for the URL of the JSF Core tab
lib, so <f:view> means the <view> tag in the JSF Core tag lib:
This is like an import statement in
Java. It makes the tags in a tag lib
available to this JSP file. The URL for the JSF Core tag lib
Note that we said a <f:view> element had been created instead of a <f:view>
tag. Here is the difference between a tag and an element:
28 Chapter 1 Getting Started with JSF
<f:view>
... The whole thing is called an
</f:view> element
This <f:view> element is required whenever you need to use any JSF tag. You
must put JSF tags inside it. Delete the existing "Hello world!" text. In the body of
the <f:view> element, enter "Hello !":
Then on the palette, expand the "JSF HTML" tag lib. It contains JSF tags for
generating HTML code. JSF can generate different markups such as HTML for
normal web browsers or special markups for mobile phones. The JSF HTML
tag lib contains tags that deal with HTML markup while those JSF tags having
nothing to do with any specific markup are put into the JSF Core tag lib.
Anyway, drag the "Output Text" tag and drop it before the exclamation mark:
Chapter 1 Getting Started with JSF 29
Note the "h" prefix. It is the short hand for the URL for the JSF HTML tag lib:
The URL for the JSF HTML tag lib
The <h:outputText> element will output some text. For example, if you like it to
output the string "Paul", in the source code editor, add an attribute named
"value". To do that, you use the auto-complete function in Eclipse:
30 Chapter 1 Getting Started with JSF
Note that the visual display is also updated automatically. Now, save the file, go
to the browser and reload the page. You should see:
Chapter 1 Getting Started with JSF 31
Output Text
2: Tell me the value
of your "subject"
property
3: Output it
To do that, the JSF engine maintains two tables (see the diagram below). Let's
call them "instance table" and "definition table" respectively. Initially the instance
table is empty, meaning that no object has been created yet. The definition table
stores the class name for each object. If you have configured the Output Text
tag to access the "subject" property of the "foo" object, then when it needs to
find the text, it will ask the JSF engine for the "foo" object. The JSF engine will
look up the instance table but can't such an entry for "foo". Then it will look up
the definition table and find the class name (hello.GreetingService) for "foo".
Then it creates a new GreetingService object and add an entry point to the
instance table. Finally it tells the Output Text to use this object:
Instance table
Object name Object instance
... ...
2: Look it up in the table. Not
found.
... ...
... ...
foo
5: Add a new
entry
Definition table
3: Look up the Object name Object class
class name
foo hello.GreetingService
bar ...
... ...
JSF Engine
1: Give me 6: Here is
the object your "foo"
named "foo" object
GreetingService
4: Create it
Output Text
Actually there are many such object tables in a single JSF application: For
example, as shown in the diagram below, suppose that there are two browsers
(client 1 and client 2) accessing the application. Assume that client 1 has sent
totally two requests (request 1 and request 2) to the application and client 2 has
sent one (request 3). Then there will have been an object table for each HTTP
request. In addition, Tomcat will allocate a memory area for each client. Such
an area is called a session. In each session, there is another object table,
Chapter 1 Getting Started with JSF 33
meaning that there is an object table for each client. Finally, there is an object
table in the whole application. Which tables will be used by Output Text?
Suppose that it is serving request 3, it will ask the JSF engine to find the "foo"
object. The JSF engine will look up the object table associated with request 3
first. If it's there fine and it will be returned. Otherwise, it will look up the table for
client 2 (because request 3 came from client 2). If it is still not found, it will look
up the table for the whole application:
Output Text
1: Give me "foo"
1: Look up "foo"
here 3: If still not found,
JSF engine
try here.
Table for request 1 2: If not found,
try here.
Object name Object instance
Table for client 1
... ...
... ... Object name Object instance
... ... ... ...
... ...
... ...
Request 1
Client 2
As mentioned before, if the object is still not found, the JSF engine will check
the definition table to find the class name and then create the object. But which
table should the new object be put into? It is specified in the definition:
34 Chapter 1 Getting Started with JSF
Definition table
Object name Object class Scope
foo hello.GreetingService request
bar ... session It should go into the table
... ... application in the session for the
client (client 2 in this
case)
It should go into the
table for the whole
application
If the object is put into the request, after the request is handled, the table and
that object will be discarded. If the object is put into the session, the table and
that object will be discarded when there is a say 30 minutes of inactivity.
Such named objects are officially called "managed beans" in JSF. They're
called managed because it is JSF that manages (creates and discards) them,
not you. They're called beans because they are Java beans (have a no-
argument constructor and provide properties):
public class GreetingService { No constructor defined. So the Java
compiler will give one automatically.
public String getSubject() { Such a constructor will have no
return "Paul"; argument:
} public class GreetingService {
} public GreetingService() {
Getter for property }
"subject" public String getSubject() {
return "Paul";
}
}
Now, let's implement this idea. To create the bean definition table, double click
the faces-config.xml file:
Choose "request" and click "Add". Browse to choose the GreetingService class:
Go ahead to finish it. Save the faces-config.xml file. This file is just an XML file.
If you'd like to see the XML code, you can choose the "Source" tab at the
bottom of the window:
The bean is
defined here Choose this tab
The next step is tell the Output Text tag to access the "foo" bean. To do that,
edit hello.jsp. Enter "#{}" as the value as shown below. This #{} syntax tells the
Output Text tag that it is not a static string. Instead, there will be a "EL
expression" in it (EL stands for Expression Language):
Chapter 1 Getting Started with JSF 37
While the cursor is inside #{}, use auto-completion and choose the "foo" bean:
Then enter a dot and use auto-completion to choose the "subject" property:
38 Chapter 1 Getting Started with JSF
To get the real value, Output Text will evaluate the EL expression "foo.subject".
It means it will look up the "foo" bean and call getSubject() on it.
Save the file. Now, go to the browser and reload the page. It should work.
Otherwise, run the Tomcat instance again.
create a UI Output component. Finally the exclamation mark is put into another
artificial UI Output component. At this point the JSF component tree is built but
the tree hasn't generated any HTML code. Next, the JSP engine sees normal
HTML again and outputs it:
These are read by the
JSP engine and are not
output at all
Normal HTML code is
output as is
<%@taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<%@taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<%@page language="java"
contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
<f:view>Hello <h:outputText value="#{foo.subject}"></h:outputText>!</f:view>
</body>
</html>
1: Create a "view 2: Plain HTML 3: Create a UI 4: Plain HTML
Normal HTML code is
root" JSF code is put into output code again
output as is
component an artificial UI JSF component
Output JSF
component
View root
UI Output
value: "!"
UI Output
At this point, the HTML output (generated by the JSP engine) is shown below
and the JSP engine has finished its job. Next, the JSF engine will tell the view
root to generate HTML output (tell it to "encode" itself in JSF terms). The view
root will in turn tell its child components to generate HTML output. Their output
will go into the marker left by the <f:view>:
40 Chapter 1 Getting Started with JSF
2: Render
UI Output
3: Render "Paul"
UI Output
Now go to the browser to load the page again. Eclipse will stop at the
breakpoint:
Then you can step through the program, check the variables and whatever. To
stop the debug session, just restart Tomcat.
Summary
In a JSF application, a page is defined by a JSP file and is identified by its view
id, which is the relative path to it from the web content folder.
Each JSP tag belongs to a certain tag lib. A tag lib is identified by a URL. To use
a tag in a JSP file, you need to introduce a short hand (prefix) for the URL and
then use the prefix to qualify the tag name.
To define a JSF component tree in a JSP file, you need to have a <f:view> and
put various JSF tags in it. Each JSF tag is also a JSP tag. When they're
executed by the JSP engine, they will create their respective JSF components.
42 Chapter 1 Getting Started with JSF
The root of the component tree is always the view root. To generate HTML code
from the component tree, the JSF engine will ask the view root to do that, which
in turn will ask its child components to do the same. The process of generating
markup in JSF is called encoding.
The JSF Core tag lib contains JSF tags that have nothing to do with any specific
markup. The JSF HTML tag lib contains JSF tags that knows about the HTML
markup. Usually the former uses a prefix of "f" (standing for faces) while the
latter uses a prefix of "h" (standing for HTML).
The <h:outputText> tag will create an outputText component. That component
will output the value its "value" attribute. That value can be static string or an EL
expression in #{}.
To find the value of a variable appearing in an EL expression, the JSF engine
will try to find a managed bean with that name. It will try to find it in the beans
associated with the request, in the session of the client and in the whole
application, in that order. If it is not found, it will look up the class name and
scope in the WebContent/WEB-INF/faces-config.xml file and create it, before
putting it into the right location.
For a class to be used as a managed bean class, it needs to be a Java bean,
i.e., it has a no-argument constructor and provides getters and/or setters for
certain properties.
43
Chapter 2
Chapter 2 Using Forms
44 Chapter 2 Using Forms
That is, the user can enter the stock id and click OK, then the stock value will be
displayed.
When you dropped the <h:form> tag, it noted that you didn't
have a <f:view> element, so it created one for you.
For the text field, use the <Text Input> tag in the JSF HTML tag lib. Put it inside
the form:
For the OK button, use the <commandButton> tag in the JSF HTML tag lib:
46 Chapter 2 Using Forms
UI View
Root
UI Form
UI Input
UI
Command
To retrieve the symbol entered by the user, you can link the UI Input component
to a property of a Java object:
UI View
Root
UI Form
class QuoteQuery {
String sym;
}
UI Input
UI
Command
Save the file. Then in getquotesymbol.jsp, set the "value" attribute of the
<inputText> tag:
This way, when the form is submitted, the symbol entered by the user will be
stored into the "sym" property of this "quoteQuery" managed bean:
Chapter 2 Using Forms 49
class QuoteQuery {
String sym;
}
class QuoteQuery {
String sym = "IBM";
}
Let's look at the whole render and submit process in details. First, the JSP
engine creates the component tree (see the diagram below). The JSF engine
gets access to the tree and ask it to encode. It will ask its child components to
encode and so on. For the UI Input component, it will try to read the "sym"
property of the "quoteQuery" bean. As the bean doesn't yet exist, the JSF
engine creates the bean. The UI Input component gets the value ("IBM") and
outputs it in the HTML <input> element. In order to be able to handle the form
submission, the JSF engine performs some extra action: It generates a unique
request id, saves the component tree into the session indexed by the request id
and includes this request id into the form as a hidden field. Finally the
"quoteQuery" bean is destroyed along with the request. This is called the
Render Response phase:
50 Chapter 2 Using Forms
JSP
engine
...
When the form is submitted (see the diagram below), the JSF engine will get
the request id from the hidden field and use to look up the component tree and
load it. This is called the Restore View phase:
Chapter 2 Using Forms 51
UI View
Root
UI Form
UI Input
MSFT
123
JSF ...
1: Form submission engine
request arrives 2: Use 123 to retrieve the tree
...
...
Then the JSF engine asks the UI View Root to extract the values from the
request (strings). It will in turn ask each child component to do that and store
the value locally into itself. The purpose is that if later a value is found to be
invalid (e.g., "abc" for an int), that invalid value will still be there and can be
redisplayed to the user. The act of extracting the value and storing it locally is
called decoding. The phase of decoding is called the Apply Request Values
phase:
Next, the JSF engine will ask the UI View Root to set the locally stored values
into the managed beans. For the UI Input component, it will set the locally
stored value into the "sym" property of the "quoteQuery" bean. As the bean
doesn't yet exist, the JSF engine creates the bean. Therefore, this bean is NOT
the same bean used in rendering. The phase of setting the locally stored values
into the managed beans is called the Update Model Values phase:
52 Chapter 2 Using Forms
UI View
Root
2: I need to access
UI Form a bean named
"quoteQuery"
quoteQuery
3: Create it. It is NOT the
same bean used in
rendering!
1: Choose it
2: Choose it
3: Choose it
4: Click here
Now the result page is ready. The only missing question is how to display it after
the form is submitted?
/getquotesymbol.jsp
Each branch is called a The next view id
navigation case
/quoteresult.jsp
If outcome is "ok"
another
If outcome is "..." view id
Usually you'll tell the JSF engine the outcome in a new phase called Invoke
Application phase (see the diagram below), which occurs after the Update
Domain Values phase. Then it will use the current view id and the outcome to
look up the navigation rules to determine the next view id, which will be
rendered in the Render Response phase:
Chapter 2 Using Forms 55
JSF engine
Now, let's define the navigation rules. To do that, open faces-config.xml and
choose the "Navigation Rule" tab at the bottom of the window:
Then click on the white area in the window. It will pop up a window to let you
choose a JSP file (see below). Choose getquotesymbol.jsp:
Click on the getquotesymbol page and then on the quoteresult page. A link will
58 Chapter 2 Using Forms
be created:
To specify the outcome for the link, choose the "Select" tool on the palette (or
just press Escape):
Then choose the link and choose the "Properties" tab. Enter the outcome there:
Chapter 2 Using Forms 59
If you're curious, you can see its XML source in the "Source" tab:
<faces-config ...>
<managed-bean>
<managed-bean-name>quoteQuery</managed-bean-name>
<managed-bean-class>stockquote.QuoteQuery</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
<navigation-rule>
<display-name>getquotesymbol</display-name>
<from-view-id>/getquotesymbol.jsp</from-view-id>
<navigation-case>
<from-outcome>ok</from-outcome>
<to-view-id>/quoteresult.jsp</to-view-id>
</navigation-case>
</navigation-rule>
</faces-config>
Save the file. Finally, modify getquotesymbol.jsp to specify the outcome:
...
<f:view>
<h:form>
<h:inputText value="#{quoteQuery.sym}"></h:inputText>
<h:commandButton value="OK" action="ok"></h:commandButton>
</h:form>
</f:view>
... This is the outcome
If the button is clicked, it will note that in the Apply Request Values phase (see
the diagram below) and will register a listener to be invoked in the Invoke
Application phase. That listener will set the outcome when it is executed:
60 Chapter 2 Using Forms
Request
...
JSF engine
1: Was I
clicked?
2: Schedule a 4: Set the outcome
listener to be
executed
UI Command Listener
3: Execute
Render
response
Why it doesn't simply execute the listener in the Apply Request Values phase?
It is because it would like to update the beans (Update Domain Values) first
before performing other any actions.
Now, you're about to run it. To do that, you need to add this project to the
Tomcat instance. So, choose the "Servers" tab and double click on the Tomcat
instance, you'll see its settings:
Double click on it
Choose the "Modules" tab at the bottom of the window. It will display all the web
applications that it is hosting. Currently it should contain only the Hello project:
Chapter 2 Using Forms 61
http://localhost:8080/StockQuote/faces/getquotesymbol.jsp
Save the file. Now, start the Tomcat instance and go to http://localhost:
8080/StockQuote/faces/getquotesymbol.jsp in a browser. It should work:
62 Chapter 2 Using Forms
This will create a UI Select One component which will allow a single item to be
selected only. It should still link to the "sym" property of the bean. So, set its
"value" attribute just like before:
To specify the available items in the combo box, choose the <selectItems> tag
in the JSF Core tag lib. Note that it is in the JSF Core tag lib instead of the JSF
HTML tag lib because selection items in JSF are generic and have nothing to
do with HTML markup.
You'd like drop the <selectItems> element into the body of the
<selectOneMenu> element, but in the visual editor you can only drop it before
the <selectOneMenu> element or after it but not inside it. Fortunately, you can
do that in the text editing area. Just make sure that you click the <selectItems>
tag and release the mouse right away, then click inside the <selectOneMenu>
element:
64 Chapter 2 Using Forms
The <selectItems> element will retrieve the items from a managed bean (again,
using its "value" attribute). To do that, create a StockService class in the same
package:
...
import java.util.List;
import javax.faces.model.SelectItem;
This class is provided by JSF. It represents
public class StockService { an item for the user's selection.
private List<SelectItem> symbols;
Make a managed bean from it. As it is a global thing, put it into the application
scope:
Chapter 2 Using Forms 65
Inputting a date
Suppose that you'd like to allow the user to query the stock value on a particular
66 Chapter 2 Using Forms
date:
<input
type="text"
value="6/20/2007" ...>
Date
3: The string is "6/20/2007" converter
Year: 2007
4: Output the Month: 6
string into the Day: 20
<input> field
2: Convert the object (a
Date) into a string for me class QuoteQuery {
...
UI Input Date getQuoteDate() {
1: Call getQuoteDate() to get the ...
value (a Date, but it doesn't need to }
know) void setQuoteDate(Date d) {
...
}
}
When the user submits the form (see the diagram below), the JSF engine will
initiate the Apply Request Values phase. As a result, the UI Input component
will store the raw input string stored locally. Before the JSF engine starts the
Update Domain Values phase, it will initiate a new phase called Process
Validations phase. In this phase, it will ask the UI View Root to convert the raw
input string into the desired data type (any Object) and optionally validate the
converted object. For the UI Input component, it will ask the converter to convert
its locally stored raw string to an object (a Date) and store the result locally.
Finally, the JSF engine will initiate the Update Domain Values phase to update
the beans:
Request
quote date: "7/28/2007"
...
UI View 2: Apply
Root request class QuoteQuery {
Date
values ...
converter
UI void setQuoteDate() {
Form ...
}
Raw: "7/28/2007"
UI Text 3: Convert Year: 2007 }
Converted:
the string Month: 7
into an Day: 28
Object 4: Update
1: Restore domain
view values
JSP engine, it will create a Date converter. Then it will ask its parent tag
(<inputText>) to find out the JSF component its parent has created (here, it's
the UI Input component). Then it will tell the UI Input component to use that
Date converter:
2: What is the JSF component
<f:view> that you created? Oh, it's that
<h:form> UI Input.
<h:selectOneMenu value="#{quoteQuery.sym}">
<f:selectItems value="#{stockService.stockSymbols}"/>
</h:selectOneMenu>
on <h:inputText value="#{quoteQuery.quoteDate}">
<f:convertDateTime/>
</h:inputText>
<h:commandButton value="OK" action="ok"></h:commandButton>
</h:form>
</f:view>
1: Create a Date converter
Date converter
Why it shows "Jan 19, 2008" instead of say 1/19/2008 or 19/1/2008? This is
controlled by two factors: the most preferred language set in the browser and
the style used by the converter. Here are some examples:
To change the most preferred language, you can change it in the browser. For
example, in Firefox, it is set in "Tools | Options | Advanced":
Click "Choose":
70 Chapter 2 Using Forms
Request
abc 2: Log an error Message list
message
abc is invalid
Date
converter
UI Text
Raw: "abc"
1: Try to convert it
to a Date but fails
Invoke
Render application
response 3: Jump to the render response
phase directly
Chapter 2 Using Forms 71
This is good, because if anything is wrong, you don't want to update the beans
(Update Domain Values) and don't want to change the view id (Invoke
Application) so that the original page is redisplayed. What else would you like to
do? To display an error message in the original page. To do that, modify
getquotesymbol.jsp:
<f:view>
<h:messages></h:messages>
<h:form>
<h:selectOneMenu value="#{quoteQuery.sym}">
<f:selectItems value="#{stockService.stockSymbols}"/>
</h:selectOneMenu>
on <h:inputText value="#{quoteQuery.quoteDate}">
<f:convertDateTime dateStyle="short" />
</h:inputText>
<h:commandButton value="OK" action="ok"></h:commandButton>
</h:form>
</f:view>
It will create a
UI Messages
UI View component
Root
UI
Messages
UI Form
...
UI Input
...
The UI Messages component will display all the messages in the message list
(if there is no message, it will render nothing). Now, run the application, enter
"abc" as the date and click OK, you'll see:
72 Chapter 2 Using Forms
The id of the
Form UI Input
component
...
UI Input
...
The client id is mainly used as the value of the id or name attribute of the HTML
element generated. If you view the source of the HTML page, you'll see how
various client ids are used:
Chapter 2 Using Forms 73
Anyway, displaying the client id is quite confusing to users. Instead, you should
display a user friend description for the text field. To do that, modify
getquotesymbol.jsp:
<f:view>
<h:messages></h:messages>
<h:form>
<h:selectOneMenu value="#{quoteQuery.sym}">
<f:selectItems value="#{stockService.stockSymbols}"/>
</h:selectOneMenu>
on <h:inputText value="#{quoteQuery.quoteDate}" label="quote date">
<f:convertDateTime dateStyle="short" />
</h:inputText>
<h:commandButton value="OK" action="ok"></h:commandButton>
</h:form>
</f:view>
Run it again and it will display label instead of the client id:
If you don't like this error message, you can provide your own. To do that,
create a text file named messages.properties in the stockquote package (the
name is not really significant as long as it has a .properties extension):
74 Chapter 2 Using Forms
You may specify TIME here When the line is too long, you
when you use the converter it can use a backslash to tell
This is called the resource key Java to continue to the next
to convert a time
line.
Then open faces-config.xml, choose the "Others" tab, click "Message Bundle"
and then click "Add":
3: Click here
2: Click here
1: Click here
BUG ALERT: Due to a bug in Eclipse the screen may not be updated even
though the code has been modified. In the source, you should see:
<faces-config ...>
<application>
<message-bundle>stockquote.messages</message-bundle>
</application>
<managed-bean>
<managed-bean-name>quoteQuery</managed-bean-name>
<managed-bean-class>stockquote.QuoteQuery</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
<managed-bean>
<managed-bean-name>stockService</managed-bean-name>
<managed-bean-class>stockquote.StockService</managed-bean-class>
<managed-bean-scope>application</managed-bean-scope>
</managed-bean>
<navigation-rule>
<display-name>getquotesymbol</display-name>
<from-view-id>/getquotesymbol.jsp</from-view-id>
<navigation-case>
<from-outcome>ok</from-outcome>
<to-view-id>/quoteresult.jsp</to-view-id>
</navigation-case>
</navigation-rule>
</faces-config>
Now the JSF engine will load messages from this file and use them to override
the default messages. Run the application and it should work:
76 Chapter 2 Using Forms
If you'd like to specify the error message for that UI Input component only, you
can do it this way:
<f:view>
<h:messages></h:messages>
<h:form>
<h:selectOneMenu value="#{quoteQuery.sym}">
<f:selectItems value="#{stockService.stockSymbols}" />
</h:selectOneMenu>
on <h:inputText value="#{quoteQuery.quoteDate}" label="quote date"
converterMessage="the quote date is invalid">
<f:convertDateTime dateStyle="short" />
</h:inputText>
<h:commandButton value="OK" action="ok"></h:commandButton>
</h:form>
</f:view> This will override the message provided
by the converter. As it is the converter
that is providing values for {0}, {1} and
{2}, you can't use such placeholders in
this string.
</f:view>
Now, run it while setting the date to empty, you'll see:
Again, if you don't like the error message, you can override it in the
messages.properties file:
javax.faces.converter.DateTimeConverter.DATE={0} is an invalid {2}. \
Enter something like {1}
javax.faces.component.UIInput.REQUIRED=You must input {0}!
The label
If you'd like to set it just for this UI Input component, you can do it this way:
<f:view>
<h:messages></h:messages>
<h:form>
<h:selectOneMenu value="#{quoteQuery.sym}">
<f:selectItems value="#{stockService.stockSymbols}" />
</h:selectOneMenu>
on <h:inputText value="#{quoteQuery.quoteDate}" label="quote date"
converterMessage="the quote date is invalid"
required="true"
requiredMessage="Input is missing!">
<f:convertDateTime dateStyle="short" />
</h:inputText>
<h:commandButton value="OK" action="ok"></h:commandButton>
</h:form>
</f:view>
Download at WoweBook.com
78 Chapter 2 Using Forms
To do that, you can use a JSF component library called RichFaces from JBoss.
Go to http://labs.jboss.com/jbossrichfaces to download it. Suppose that it is
richfaces-ui-3.1.3.GA-bin.zip. Unzip it into say c:\richfaces-ui. To use it, copy all
the jar files in c:\richfaces-ui\lib into your WEB-INF/lib. RichFaces in turn needs
a few third party jar files. You can go to
http://agileskills2.org/EssentialJSF/richfaces to download them and put them
into WEB/lib. Finally refresh the project in Eclipse.
Next, modify WEB-INF/web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app ...>
<display-name>StockQuote</display-name>
<welcome-file-list>
...
</welcome-file-list>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
Chapter 2 Using Forms 79
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>/faces/*</url-pattern>
</servlet-mapping>
<context-param>
<param-name>org.richfaces.SKIN</param-name>
<param-value>blueSky</param-value>
</context-param>
<filter>
<display-name>RichFaces Filter</display-name>
<filter-name>richfaces</filter-name>
<filter-class>org.ajax4jsf.Filter</filter-class>
</filter>
<filter-mapping>
<filter-name>richfaces</filter-name>
<servlet-name>Faces Servlet</servlet-name>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
</filter-mapping>
</web-app>
You don't need to know the exact meaning of this code. Basically it is used to
enable the RichFaces engine to intercept requests so that it can deliver
Javascript to the browser. Close the getquotesymbol.jsp and open it again.
Then you should see some new tag libs such as RichFaces available on the
palette:
Next, choose the <calendar> tag from the RichFaces tag lib and drop it into the
getquotesymbol.jsp file and modify the file like this:
80 Chapter 2 Using Forms
<f:view>
<h:messages></h:messages>
<h:form>
<h:selectOneMenu value="#{quoteQuery.sym}">
<f:selectItems value="#{stockService.stockSymbols}" />
</h:selectOneMenu>
on <h:inputText value="#{quoteQuery.quoteDate}" label="quote date"
converterMessage="the quote date is invalid"
required="true"
requiredMessage="Input is missing!">
<f:convertDateTime dateStyle="short" />
</h:inputText>
<rich:calendar
value="#{quoteQuery.quoteDate}" The same attributes
converterMessage="the quote date is invalid" are supported
required="true" except the "label"
requiredMessage="Input is missing!">
</rich:calendar>
<h:commandButton value="OK" action="ok"></h:commandButton>
</h:form>
</f:view>
}
public String getSym() {
return sym;
}
public void setSym(String sym) {
this.sym = sym;
}
public int getStockValue() {
return (sym + quoteDate.toString()).hashCode() % 100;
}
}
In a real implementation, you will need to look up a database or connect to a
network service provider to get the stock value. This kind of work is best done in
the StockService class. So, to make the code more realistic, let move the
calculation logic into the StockService class:
public class StockService {
private List<SelectItem> symbols;
public StockService() {
symbols = new ArrayList<SelectItem>();
symbols.add(new SelectItem("MSFT", "Microsoft"));
symbols.add(new SelectItem("IBM", "IBM"));
symbols.add(new SelectItem("RHAT", "Red Hat"));
}
public List<SelectItem> getStockSymbols() {
return symbols;
}
public int getStockValue(QuoteQuery q) {
return (q.getSym() + q.getQuoteDate().toString()).hashCode() % 100;
}
}
Then the code in the QuoteQuery class should call the StockService to get the
stock value. But how to get access to it?
public class QuoteQuery {
private String sym;
private Date quoteDate = new Date();
To let the "quoteQuery" bean get access to the "stockService" bean, you can
say so in the bean definition table. Each row in the definition table can refer to a
property initialization table (see the diagram below). When the JSF engine
creates the "quoteQuery" bean, it will check its property initialization table. It
notes that it needs to initialize the "stkSrv" property of the "quoteQuery" bean.
82 Chapter 2 Using Forms
Property initialization
table
Click here
to add an
entry
You also specify the property class so that JSF engine can double check the
data type. Save the file. If you're curious, you can check the source:
<faces-config ...>
<application>
<message-bundle>stockquote.messages</message-bundle>
</application>
<managed-bean>
<managed-bean-name>quoteQuery</managed-bean-name>
<managed-bean-class>stockquote.QuoteQuery</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
<managed-property>
<property-name>stkSrv</property-name>
<property-class>stockquote.StockService</property-class>
<value>#{stockService}</value>
</managed-property>
</managed-bean>
<managed-bean>
<managed-bean-name>stockService</managed-bean-name>
<managed-bean-class>stockquote.StockService</managed-bean-class>
<managed-bean-scope>application</managed-bean-scope>
</managed-bean>
<navigation-rule>
<display-name>getquotesymbol</display-name>
<from-view-id>/getquotesymbol.jsp</from-view-id>
<navigation-case>
<from-outcome>ok</from-outcome>
<to-view-id>/quoteresult.jsp</to-view-id>
</navigation-case>
</navigation-rule>
</faces-config>
You can see that such a property is called a managed property because it is the
JSF engine that initializes it. Anyway, run the application and it should continue
to work.
Note that you can let the "quoteQuery" bean access the "stockService" bean but
not vice versa because the scope of the former (request) is shorter (the same is
also fine) than the latter (application). In the life time of the "stockService" bean,
there will be multiple "quoteQuery" beans (one for each request). It just doesn't
know which one to refer to.
Summary
To handle a form submission, the JSF engine will go through the following
84 Chapter 2 Using Forms
Chapter 3
Chapter 3 Validating Input
86 Chapter 3 Validating Input
Postage calculator
Suppose that you'd like to develop an application to calculate the postage for
sending a package from some place to another. The user will enter the weight
of the package in kg (check the screen shots below). Optionally, he can enter a
"patron code" identifying himself as a patron to get a certain discount. After
clicking OK, it will display the postage:
It will output an
HTML <table> There are 2 columns per
UI Panel row. It means that the 3rd
child component will go
into the next row.
A <panelGrid>
will create a UI
Panel component
Why use a <panelGrid> instead of the plain <table>? You could use a <table>
just fine:
<f:view>
<table>
<tr>
<td><h:outputText value="item1"></h:outputText></td>
<td><h:outputText value="item2"></h:outputText></td>
</tr>
<tr>
<td><h:outputText value="item3"></h:outputText></td>
<td><h:outputText value="item4"></h:outputText></td>
</tr>
</table>
</f:view>
A major difference is that the UI Panel will work fine with HTML or non-HTML
markup. When the <panelGrid> tag creates the UI Panel, it will assign an HTML
Panel renderer to it so that it will generate HTML markup (see the diagram
below). But if you assign another renderer to it, it will be able to generate some
other type of markup:
Output HTML
Render markup <table>
HTML Panel
me ...
renderer
</table>
UI Panel
...
Some other
renderer Output some
Render
me other type of
markup
Select item2 and press the Del key. Then the components following item2 will
flow forward to fill its place:
Then put an <inputText> into the original location of item2 (i.e., after the
"Weight:" label). Then the following components will flow back to their original
locations:
Chapter 3 Validating Input 89
Delete item4 and put in another <inputText>. You could create the <inputText>
as usual, alternatively, you could copy the existing <inputText> by Ctrl-dragging
it:
90 Chapter 3 Validating Input
Ctrl-drag it to copy it
Create a Request class in the postage package to act as the bean to be edited:
Chapter 3 Validating Input 91
public PostageService() {
patronCodeToDiscount = new HashMap<String, Integer>();
patronCodeToDiscount.put("p1", 90);
patronCodeToDiscount.put("p2", 95);
}
public int getPostage(Request r) {
Integer discount = (Integer) patronCodeToDiscount.get(r.getPatronCode());
int postagePerKg = 10;
int postage = r.getWeight() * postagePerKg;
if (discount != null) {
postage = postage * discount.intValue() / 100;
}
return postage; Assume that the postage is
} 10 dollar per kg
}
Create the bean definitions for them and hook them up:
92 Chapter 3 Validating Input
<faces-config ...>
<managed-bean>
<managed-bean-name>postageService</managed-bean-name>
<managed-bean-class>postage.PostageService</managed-bean-class>
<managed-bean-scope>application</managed-bean-scope>
</managed-bean> Note that you must NOT
<managed-bean> call it "request" because
<managed-bean-name>req</managed-bean-name> there is a pre-defined bean
<managed-bean-class>postage.Request</managed-bean-class> named "request"
representing the HTTP
<managed-bean-scope>request</managed-bean-scope> request.
<managed-property>
<property-name>postageService</property-name>
<property-class>postage.PostageService</property-class>
<value>#{postageService}</value>
</managed-property>
</managed-bean>
</faces-config>
<faces-config ...>
<managed-bean>
<managed-bean-name>postageService</managed-bean-name>
<managed-bean-class>postage.PostageService</managed-bean-class>
<managed-bean-scope>application</managed-bean-scope>
</managed-bean>
<managed-bean>
<managed-bean-name>req</managed-bean-name>
<managed-bean-class>postage.Request</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
<managed-property>
<property-name>postageService</property-name>
<property-class>postage.PostageService</property-class>
<value>#{postageService}</value>
</managed-property>
</managed-bean>
<navigation-rule>
<display-name>getrequest</display-name>
<from-view-id>/getrequest.jsp</from-view-id>
<navigation-case>
<from-outcome>ok</from-outcome>
<to-view-id>/showpostage.jsp</to-view-id>
</navigation-case>
</navigation-rule>
</faces-config>
Double click the Tomcat instance and add the project as a module. Then, run
the application by going to http://localhost:8080/Postage/faces/getrequest.jsp. It
should work:
This is no good. Instead, you'd like the application to tell the user that the weight
is invalid:
Similarly, it should also check if the patron code is valid or not. For example, if
the user enters "p3", it should tell him that this code is not found:
Note that as the patron code is optional, if he doesn't enter anything, it should
NOT be treated as an error. In order to validate the user input, you can add one
or more validator objects to a UI Input component (see the diagram below).
When the form is submitted, as mentioned before, in the Apply Request Values
the UI Input component will store the raw input string ("-5") locally. In the
Process Validations phase, it will convert it into an Object (an int -5 here). Then
Chapter 3 Validating Input 95
it will ask each of its validators (if any) in turn to validate the converted value (-5
here). If a validator fails, it will log an error message and will tell the JSF engine
to jump to the Render Response phase directly:
Message list
...
... 3: Log an
error
message
weight: "-5"
Validator 1
Read it
and store
2: Validate
it locally
UI Input UI Input the converted
value (-5)
raw: "-5" raw: "-5"
converted: -5
1: Convert
Restore View Apply Request Values Process Validations Update Domain Values
Drop it into the body of the <inputText> element and the modify the code as
shown below:
96 Chapter 3 Validating Input
Again, you can customize the error message using a message bundle. For
example, create Postage.properties in the postage package:
The value is less than the
minimum value
Make sure the application is reloaded. Then run it and it should work:
In addition to this validator, there are similar ones for checking doubles and
strings:
The length of the string
should be between 3
and 20
The application will display an error because the UI Input can't store a null into
an int property (It could if it was an Integer property):
In this case, you can simply solve the problem by marking the weight as
required:
<f:view>
<h:messages></h:messages>
<h:form>
<h:panelGrid border="1" columns="2">
<h:outputText value="Weight:"></h:outputText>
<h:inputText label="weight" value="#{req.weight}" required="true">
<f:validateLength></f:validateLength>
<f:validateLongRange minimum="0"></f:validateLongRange>
</h:inputText>
<h:outputText value="Patron code:"></h:outputText>
<h:inputText value="#{req.patronCode}"></h:inputText>
<h:outputText></h:outputText>
<h:commandButton value="Submit" action="ok"></h:commandButton>
</h:panelGrid>
</h:form>
</f:view>
Then the user will see:
Chapter 3 Validating Input 99
public PostageService() {
patronCodeToDiscount = new HashMap<String, Integer>();
patronCodeToDiscount.put("p1", 90);
patronCodeToDiscount.put("p2", 95);
}
public int getPostage(Request r) {
Integer discount = (Integer) patronCodeToDiscount
.get(r.getPatronCode());
int postagePerKg = 10;
int postage = r.getWeight() * postagePerKg;
if (discount != null) {
postage = postage * discount.intValue() / 100;
}
return postage;
}
public boolean patronExists(String patronCode) {
return patronCodeToDiscount.containsKey(patronCode);
}
}
Now run it and it should work:
However, the Request class is a domain class and thus should not know about
JSF stuff such as FacesContext, UIComponent and ValidatorException. To
Chapter 3 Validating Input 101
solve this problem, you can create a new bean to do this work. For example,
let's create a PatronCodeValidatingBean class in the same package and move
the validatePatron() method into there:
public class PatronCodeValidatingBean {
private PostageService postageService;
validator="#{PatronCodeValidatingBean.validatePatron}"></h:inputText>
<h:outputText></h:outputText>
<h:commandButton value="Submit" action="ok"></h:commandButton>
</h:panelGrid>
</h:form>
</f:view>
Run it and it should continue to work.
To do that, open getrequest.jsp, choose the <message> tag (NOT the plural
<messages> tag!) in the JSF HTML tag lib:
104 Chapter 3 Validating Input
Drop it after the first <inputText> element. However, the page will look like:
This is due to the way the UI Panel lays out its child components. To solve this
problem, you can put the UI Input component and the UI Message component
into another UI Panel (see the diagram below). It is important that the new UI
Panel render its child components one by one without any extra markup. To do
that, just let it use a group renderer, while the original UI Panel is using a grid
renderer:
Chapter 3 Validating Input 105
<table>
<tr>
<td>[MARKUP OF CHILD1]</td>
<td>[MARKUP OF CHILD2]</td>
...
Grid
renderer
... ...
Group
renderer
UI Input UI Panel
UI
Message
UI Input
UI
Message
To implement this idea, choose the <panelGroup> tag in the JSF HTML tag lib:
Then move the <inputText> element and the <message> element into its body:
<f:view>
<h:messages errorClass="err"></h:messages>
<h:form>
<h:panelGrid border="1" columns="2">
<h:outputText value="Weight:"></h:outputText>
<h:panelGroup>
<h:inputText label="weight" value="#{req.weight}"
required="true">
<f:validateLongRange minimum="0"></f:validateLongRange>
</h:inputText>
<h:message></h:message>
</h:panelGroup>
<h:outputText value="Patron code:"></h:outputText>
<h:inputText value="#{req.patronCode}"
106 Chapter 3 Validating Input
validator="#{PatronCodeValidatingBean.validatePatron}"></h:inputText>
<h:outputText></h:outputText>
<h:commandButton value="Submit" action="ok"></h:commandButton>
</h:panelGrid>
</h:form>
</f:view>
Then the page will look fine again:
It is not really required to set the id of the <form>. It is set just to show you what
the client id looks it. Run it and it should work fine:
Chapter 3 Validating Input 107
Why is the detail message the same as the summary message? This is
because you are not providing the detail message in the message bundle. To
provide it, modify Postage.properties:
javax.faces.validator.LongRangeValidator.MINIMUM={1} must be at least {0}!
javax.faces.validator.LongRangeValidator.MINIMUM_detail={1} is invalid. It must \
be at least {0}!
To make the message appear in red, just set its "errorClass" attribute:
108 Chapter 3 Validating Input
...
<style type="text/css">
li.err {
color: red
}
span.err { The UI Message
color: red component will
} output a <span>
</style>
...
<f:view>
<h:messages errorClass="err"></h:messages>
<h:form>
<h:panelGrid border="1" columns="2">
<h:outputText value="Weight:"></h:outputText>
<h:panelGroup>
<h:inputText id="w" label="weight" value="#{req.weight}"
required="true">
<f:validateLongRange minimum="0"></f:validateLongRange>
</h:inputText>
<h:message for="w" errorClass="err"></h:message>
</h:panelGroup>
<h:outputText value="Patron code:"></h:outputText>
<h:inputText value="#{req.patronCode}"
validator="#{PatronCodeValidatingBean.validatePatron}"></h:inputText>
<h:outputText></h:outputText>
<h:commandButton value="Submit" action="ok"></h:commandButton>
</h:panelGrid>
</h:form>
</f:view>
Drop it into the body of the <commandButton> element and then modify it:
<f:view>
<h:messages errorClass="err"></h:messages>
<h:form>
<h:panelGrid border="1" columns="2">
<h:outputText value="Weight:"></h:outputText>
<h:panelGroup>
<h:inputText id="w" label="weight" value="#{req.weight}"
required="true">
<f:validateLongRange minimum="0"></f:validateLongRange>
</h:inputText>
<h:message for="w" errorClass="err"></h:message>
</h:panelGroup>
<h:outputText value="Patron code:"></h:outputText>
<h:inputText value="#{req.patronCode}"
validator="#{PatronCodeValidatingBean.validatePatron}"></h:inputText>
<h:outputText></h:outputText>
<h:commandButton value="Submit" action="ok">
<f:actionListener type="postage.RequestValidatingListener" />
</h:commandButton>
</h:panelGrid>
</h:form> This is the class of the
</f:view> action listener. You'll
create this class next.
Note that its properties will have been updated in the Update Domain Values
phase while the action listener is executed in the Invoke Application phase.
Now, run it and it should work:
Summary
A UI Panel component lays out its child components according to its renderer. It
can lay them out in a table (<panelGrid>) or just arrange them one by one
(<panelGroup>).
To validate the user input in a single UI Input component, you can add one or
more validator to it. They will be invoked one by one in the Process Validations
phase to check the converted value. If any one fails, it will log an error for that
component (or rather, for its client id) and tell the JSF engine to jump to the
Render Response phase directly.
There are a few pre-defined validators coming with JSF for checking the range
of an int, a double or the length of a string. To customize their error messages,
use a message bundle.
To perform custom validation, you can do it in a method of a bean and use the
"validate" attribute of the tag to refer to it. It will be treated like a normal
validator. If the validation involves two or more components, you can add an
action listener to a UI Command. It will be executed in the Invoke Application
phase. As such the beans will have been updated so you can check their
properties.
A JSF message contains a severity level, a summary and a detail. Usually you
will display the summary using a UI Messages component, while display the
detail using a UI Message component along with each UI Input component.
To customize the appearance of the HTML output, you can define CSS style
classes and let the components to refer to them.
113
Chapter 4
Chapter 4 Creating an e-Shop
114 Chapter 4 Creating an e-Shop
Creating an e-shop
Suppose that you'd like to create an e-shop. The front page lists all the
products:
As you can see, a product has an id, a name and a price. For example, the first
product's id is "p01", its name is "Pencil" and its price is $1.2. If the user clicks
on a product say "Eraser", he will see a detailed description of Eraser:
For simplicity, you will be using strings like "a", "b" and "c" as the detailed
descriptions for the products.
Chapter 4 Creating an e-Shop 115
Right click the heading and choose "Style | Paragraph Format | Heading 1":
To list the products in a table, you can't use the <panelGrid> as it requires you
to add the child components at design time. Instead, now you need to query the
database at runtime to determine how many rows to have (one for each
product). For this purpose, use the <dataTable> tag in the JSF HTML tag lib:
Drop it into the page after the <h1> element in the source pane:
Chapter 4 Creating an e-Shop 117
Here, the UI
Column will
render its header
facet if it is the
first row only
UI Column
(header row)
header
UI Output
3: Add the component to the
parent as a special kind of
It will create a UI Column which child called a facet. How a
represents the whole column facet is used is entirely up to
the parent to decide.
2: Look, a facet name 1: Create
("header") is set
It will create a
UI Data
component
which will
render as the
table
You can put other components here.
The UI Column will render them if it is
a subsequent row (data row).
Another
column
To add the price column, choose the <column> tag in the JSF HTML tag lib:
Chapter 4 Creating an e-Shop 119
It has no facet. To add the facet, choose the <facet> tag in the JSF Core tag lib
(non-HTML markup may also use facets):
To provide the data items, create a Catalog class in the shop package:
Chapter 4 Creating an e-Shop 121
package shop;
public Catalog() {
products = new ArrayList<Product>();
products.add(new Product("p01", "Pencil", "a", 1.20));
products.add(new Product("p02", "Eraser", "b", 2.00));
products.add(new Product("p03", "Ball pen", "c", 3.50));
}
public List<Product> getProducts() {
return products;
}
}
Define the Product class in the same package:
package shop;
How can the columns access the current Product? You can do it this way:
Chapter 4 Creating an e-Shop 123
UI Data
Request 1
1: "p" is the name of an "attribute". There is a table for
each request like this. It looks like the table for request-
3: An EL expression can refer to scoped managed beans but they aren't the same thing.
attributes ("p" here). The JSF This table can function without a bean definition table. It
engine will try to find a bean named only associates an object with a name but won't create
"p" in all three scopes first. If not objects. Each entry is called an attribute.
found, it will try to find an attribute.
Drag the <outputText> for the product name and drop it into the body of the
<commandLink>:
Chapter 4 Creating an e-Shop 125
UI Command
<input type="button" ...>
Button
renderer Output markup
Render
me
the outcome:
<h:column id="column2">
<h:form>
<h:commandLink action="detail">
<h:outputText value="#{p.name}"></h:outputText>
</h:commandLink>
</h:form>
<f:facet name="header">
<h:outputText value="Name"></h:outputText>
</f:facet>
</h:column>
For it to work, create a productdetails.jsp page:
public Product() {
Chapter 4 Creating an e-Shop 127
}
public Product(String id, String name, String desc, double price) {
this.id = id;
this.name = name;
this.desc = desc;
this.price = price;
}
public String getDesc() {
return desc;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
}
Create a navigation case for it:
However, there is still a question: Who will set the "product" bean to the
selected Product object? You can do it this way (see the diagram below). The
<setPropertyActionListener> tag will create a "Set Property" action listener and
add it to the component created by its parent tag (here, the UI Command). For
some unknown reason, this tag is unavailable on the palette, so you have to
type it in manually. When the form is submitted, the UI Data will be asked to
apply the request values (decode). It will loop through the list of Product objects.
For each Product object, it will ask its child components to decode. For the UI
Command, it will check if it was clicked. If no, nothing happens. If yes, it usually
will schedule the Set Property action listener to be executed in the Invoke
Application phase. However, here the "immediate" attribute of the UI Command
is set to true. In that case it will call all its action listeners immediately in the
Apply Request Values phase. Here, the Set Property action listener will evaluate
#{p.id} and get the current Product id and then pass it to the setId() method of
the "product" bean:
128 Chapter 4 Creating an e-Shop
<h:column id="column2">
<h:form>
<h:commandLink action="detail" immediate="true">
<h:outputText value="#{p.name}"></h:outputText>
<f:setPropertyActionListener
value="#{p.id}" target="#{product.id}" />
</h:commandLink> Create
</h:form>
<f:facet name="header">
<h:outputText value="Name"></h:outputText>
</f:facet>
</h:column> Set Property
Action Listener
2: Apply request
UI Data values
... UI Command
3: Was I clicked? If yes, call the special action
listener. Usually, it is called in the Invoke
Application phase. But here, "immediate" is
p01 p02 p03 true, so call it in the Apply Request Values
phase (now).
1: Point to it
4: The listener evaluates
Attribute table Bean table #{p.id} and stores the
Object name Object instance Object name Object instance value into #{product.id}
p product
... ... ... ...
... ... ... ...
Product
What would happen if "immediate" is not set to true? Then the "p" attribute will
change in each turn of the loop. At the end of the loop it will be deleted
altogether. These will happen in the Apply Request Values phase. When it
reaches the Invoke Application phase, the Set Property action listener is called,
but then the "p" attribute no longer exists and #{p} will evaluate to null (Yes, it
will return null if it can't find a managed bean or attribute with that name).
Now, define the setId() method to update the other fields:
public class Product {
private String id;
private String name;
private String desc;
private double price;
private Catalog catalog;
public Product() {
}
public Product(String id, String name, String desc, double price) {
this.id = id;
this.name = name;
this.desc = desc;
this.price = price;
}
public String getDesc() {
Chapter 4 Creating an e-Shop 129
return desc;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
public void setId(String pid) {
Product p = catalog.getProduct(pid);
id = p.id;
name = p.name;
desc = p.desc;
price = p.price;
}
public void setCatalog(Catalog catalog) {
this.catalog = catalog;
}
}
Create the getProduct() method in the Catalog class:
public class Catalog {
private List<Product> products;
public Catalog() {
products = new ArrayList<Product>();
products.add(new Product("p01", "Pencil", "a", 1.20));
products.add(new Product("p02", "Eraser", "b", 2.00));
products.add(new Product("p03", "Ball pen", "c", 3.50));
}
public List<Product> getProducts() {
return products;
}
public Product getProduct(String pid) {
for (Product p : products) {
if (p.getId().equals(pid)) {
return p;
}
}
return null;
}
}
Inject the "catalog" bean into the "product" bean:
<faces-config ...>
<managed-bean>
<managed-bean-name>catalog</managed-bean-name>
<managed-bean-class>shop.Catalog</managed-bean-class>
<managed-bean-scope>application</managed-bean-scope>
</managed-bean>
<managed-bean>
<managed-bean-name>product</managed-bean-name>
<managed-bean-class>shop.Product</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
<managed-property>
<property-name>catalog</property-name>
<property-class>shop.Catalog</property-class>
<value>#{catalog}</value>
</managed-property>
</managed-bean>
<navigation-rule>
<display-name>showcatalog</display-name>
<from-view-id>/showcatalog.jsp</from-view-id>
<navigation-case>
<from-outcome>detail</from-outcome>
<to-view-id>/productdetails.jsp</to-view-id>
130 Chapter 4 Creating an e-Shop
</navigation-case>
</navigation-rule>
</faces-config>
Note that the UI Command was actually rendered three times and generated
three HTML <input> elements. How can it tell which one was clicked? It works
like this (see the diagram below): When the UI Data renders each row, the UI
Command will try to get a client id for itself. It will ask the UI Data for its client id.
Assuming that the id of the UI Data is "d". It will append the current row index (0
here) and return "d:0" as its client id. Assuming that the id of the UI Command is
"c", then its client id will be "d:0:c" and will use it to identify the <a> element. For
the second row, the client id will be "d:1:c" and etc.:
2: What's your client id?
Suppose that the second product was clicked (so the HTML id is "d:1:c") and
the form is submitted. The UI Data will again loop through each Product object.
For each Product object, it will ask the UI Command to decode. The UI
Command will determine its client id first and then check if it was the link that
was clicked:
4: Equal to
3: Decode my client id?
clicked=d:1:c
... UI Data
"d"
... UI Command
"c"
1: The second Product
was clicked. The
request arrives. 2: Current item
If the user clicks "Continue shopping", the catalog page will be displayed:
If the user clicks "Add to cart", a single piece of the product is added to his
shopping cart, then the contents of his shopping cart will be displayed:
From there the user should be able to continue shopping or checkout. Now, let's
do it. First, add the two buttons to productdetails.jsp:
132 Chapter 4 Creating an e-Shop
<f:view>
<h1><h:outputText value="#{product.name}"></h:outputText></h1>
<h:outputText value="#{product.desc}"></h:outputText>
<h:form>
<h:commandButton value="Add to cart" action="#{product.addToCart}">
</h:commandButton>
<h:commandButton value="Continue shopping" action="catalog">
</h:commandButton>
</h:form>
Usually it is just the outcome, but here
</f:view> you specify a method. It should take no
argument and return a string which is
the outcome.
You need to have the shopping cart as a managed bean. What scope it should
be in? For each user to have his own shopping cart, it should be in the session
scope. So, let's create the Cart class:
Chapter 4 Creating an e-Shop 133
public Cart() {
productIds = new ArrayList<String>();
}
public void add(String pid) {
productIds.add(pid);
}
public List<String> getProductIds() {
return productIds;
}
}
Define a bean for the shopping cart and inject it into the "product" bean:
<faces-config ...>
<managed-bean>
<managed-bean-name>catalog</managed-bean-name>
<managed-bean-class>shop.Catalog</managed-bean-class>
<managed-bean-scope>application</managed-bean-scope>
</managed-bean>
<managed-bean>
<managed-bean-name>product</managed-bean-name>
<managed-bean-class>shop.Product</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
<managed-property>
<property-name>catalog</property-name>
<property-class>shop.Catalog</property-class>
<value>#{catalog}</value>
</managed-property>
<managed-property> Inject the "cart" bean into the
<property-name>cart</property-name> "product" bean
<property-class>shop.Cart</property-class>
<value>#{cart}</value>
</managed-property>
</managed-bean>
<managed-bean>
<managed-bean-name>cart</managed-bean-name>
<managed-bean-class>shop.Cart</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean> in the session scope
<navigation-rule>
<display-name>showcatalog</display-name>
<from-view-id>/showcatalog.jsp</from-view-id>
<navigation-case>
<from-outcome>detail</from-outcome>
<to-view-id>/productdetails.jsp</to-view-id>
</navigation-case>
</navigation-rule>
</faces-config>
Next, create a showcart.jsp page to display the shopping cart as below. Note
that it doesn't specify any "header" facet so the columns will not have any
header:
134 Chapter 4 Creating an e-Shop
To solve this problem, you can't just provide a list of product ids to the UI Data.
You need to provide a list of Product objects. To do that, you may try to modify
the Cart class:
When a shopping cart is created, the "catalog" will be
injected into it. Therefore there is no need to drag in
the catalog during serialization, not to mention that it is
also not serializable.
public class Cart implements Serializable {
private List<String> productIds;
private transient Catalog catalog; You'll inject the catalog into
here
public Cart() {
productIds = new ArrayList<String>();
}
public void add(String pid) {
productIds.add(pid);
}
public List<String> getProductIds() {
return productIds;
}
public void setCatalog(Catalog catalog) {
this.catalog = catalog; Return a list of Product
} objects
public List<Product> getProducts() {
List<Product> products = new ArrayList<Product>();
for (String pid : productIds) {
products.add(catalog.getProduct(pid));
}
return products;
}
}
However, do NOT do that. When the "cart" bean is serialized and saved into the
hard disk (see the diagram below), the "catalog" bean is not included due to the
effect of the transient keyword. Later, when it is loaded from the hard disk and
deserialized, its "catalog" field will be null. The JSF engine is not involved and
won't inject the "catalog" bean into it at all:
Chapter 4 Creating an e-Shop 135
transient null
cart catalog cart
The take home message is that you should never let a session scoped bean
refer to any other managed bean. But how to solve the problem? You can
create an extra request scoped bean to retrieve product ids from the "cart" bean
and then translate ids into Product objects using the "catalog" bean:
showCart
Helper 2: Give me the Product
object for p01 and etc.
1: Give me the
product ids
cart catalog
</managed-property>
<managed-property>
<property-name>catalog</property-name>
<property-class>shop.Catalog</property-class>
<value>#{catalog}</value>
</managed-property>
</managed-bean>
...
</faces-config>
Now, you can easily display the product name and price in showcart.jsp:
<f:view>
<h1>Shopping cart</h1>
<h:dataTable border="1" value="#{showCartHelper.products}" var="p">
<h:column id="column1">
<h:outputText value="#{p.id}"></h:outputText>
</h:column>
<h:column id="column2">
<h:outputText value="#{p.name}"></h:outputText>
</h:column>
<h:column id="column3">
<h:outputText value="#{p.price}"></h:outputText>
</h:column>
</h:dataTable>
<h:form>
<h:commandButton value="Checkout" action="checkout"></h:commandButton>
<h:commandButton value="Continue shopping" action="catalog"></h:commandButton>
</h:form>
</f:view>
Next, define the navigation cases:
Now run it. Unfortunately, if you try to add a product to the shopping cart, the
product information will still not be displayed:
Chapter 4 Creating an e-Shop 137
This is because when the click "Add to cart", a new "product" bean will be
created by the UI Command component:
<f:view>
<h1><h:outputText value="#{product.name}"></h:outputText></h1>
<h:outputText value="#{product.desc}"></h:outputText>
<h:form>
<h:commandButton value="Add to cart" action="#{product.addToCart}">
</h:commandButton>
<h:commandButton value="Continue shopping" action="catalog">
</h:commandButton>
</h:form>
</f:view>
Obviously the id in that new Product object is not set at all. In order to
remember the product id, choose the <inputHidden> tag from the JSF HTML
tag lib and drop it into the <form>:
It will create a UI Input component just like <inputText>. The difference is that
138 Chapter 4 Creating an e-Shop
that it will use an HTML Hidden renderer. When it's rendering (see the diagram
below), it will get the id of the "product" bean and output it into an HTML hidden
field through the HTML Hidden renderer:
"product"
value: p02
...
3: Generate an HTML hidden
Hidden field <input type="hidden"
renderer
2: Read the value name="client-id"
value="p02">
...
When the form is submitted (see the diagram below), the value of the HTML
hidden field (p02) is included in the request. The UI Input component will
retrieve the value (Apply Request Values) and store it into the id of the "product"
bean (Update Domain Values). Then your setId() method will update the other
fields:
Of course, if you have added more products you will see all of them listed here.
This is because the session is accumulating the product ids, and even after the
web application is reloaded, the session is not affected at all. In fact, even if you
restart Tomcat, the session is still there because Tomcat will save it to disk and
load it back later. To get rid of the old session and get a new one, you may wait
say 30 minutes, but an easier way is to close the browser and open a new one.
But how does it work? To understand it, you need to understand how Tomcat
Chapter 4 Creating an e-Shop 139
Click "Privacy" on the top, click "Show Cookies". Locate the site "localhost" and
you'll find a cookie whose name is "JSESSIONID". This is how Tomcat and the
browser maintain the session:
140 Chapter 4 Creating an e-Shop
When a user first accesses a web application (see the diagram below), Tomcat
will generate a random number called "session id" and use it to identify the
session. Then it sends this session id back to the browser and tell it to save the
session id in a cookie named "JSESSIONID". In addition, Tomcat tells the
browser to associate the cookie with the host "localhost" and with the
path /Shop. Later when the browser accesses any page of the application (e.g.,
http://localhost/Shop/faces/foo.jsp), the browser finds that there is a cookie
associated with this host ("localhost") and that the path /Shop/faces/foo.jsp
being accessed is somewhere under the path associated with the cookie
(/Shop), so it will send the content of the cookie (the session id) to the server.
When Tomcat receives the session id, it can find out which session to use with
the id:
Chapter 4 Creating an e-Shop 141
5: Access
http://localhost/Shop/faces/ 1: Access http://localhost/Shop
foo.jsp 2: Create it
Browser 1 Tomcat
7: Send JSESSIONID=111333
to Tomcat
Session 111333
4: Save it along 6: Look, there is a cookie
with the host and for localhost and /Shop
path
Session 123456
It means that if you delete this cookie and then access the application again,
Tomcat will treat you as a new user. But why restarting the browser also works?
The cookies are stored on disk and so they are persistent. So, usually restarting
the browser will not delete them. However, each cookie has a maximum age (in
seconds). For example, see the "Expires" field of another cookie shown below:
If that age is set to -1, it means the browser should delete the cookie when the
browser is closed. As shown in the screen shot below, this is exactly the case
with your JSESSIONID cookie ("at end of session"):
142 Chapter 4 Creating an e-Shop
After logging in, the catalog page is displayed again. When a user tries to
Chapter 4 Creating an e-Shop 143
But if he hasn't logged in when trying to checkout, he will be asked to login first,
then he will see confirm page:
144 Chapter 4 Creating an e-Shop
<f:view>
<h1>Login</h1>
<h:form>
<h:panelGrid border="1" columns="2">
<h:outputText value="Email:"></h:outputText>
<h:inputText></h:inputText>
<h:outputText value="Password:"></h:outputText>
<h:inputSecret></h:inputSecret>
</h:panelGrid>
</h:form>
</f:view> It is just like <inputText> in that it will
create a UI Input. The only difference is
that the user input will appear as stars.
Again, this is done using a different
renderer.
<h1>Login</h1>
<h:form>
<h:panelGrid border="1" columns="2">
<h:outputText value="Email:"></h:outputText>
<h:inputText value="#{loginRequest.email}"></h:inputText>
<h:outputText value="Password:"></h:outputText>
<h:inputSecret value="#{loginRequest.password}"></h:inputSecret>
<h:outputText></h:outputText>
<h:commandButton value="Login"></h:commandButton>
</h:panelGrid>
</h:form>
</f:view>
To handle the form submission, you need a business action method:
<f:view>
<h1>Login</h1>
<h:form>
<h:panelGrid border="1" columns="2">
<h:outputText value="Email:"></h:outputText>
<h:inputText value="#{loginRequest.email}"></h:inputText>
<h:outputText value="Password:"></h:outputText>
<h:inputSecret value="#{loginRequest.password}"></h:inputSecret>
<h:outputText></h:outputText>
<h:commandButton value="Login" action="#{loginRequest.login}">
</h:commandButton>
</h:panelGrid>
</h:form>
</f:view>
Define the login() method:
Chapter 4 Creating an e-Shop 147
public UserService() {
148 Chapter 4 Creating an e-Shop
<managed-property>
<property-name>loginSession</property-name>
<property-class>shop.LoginSession</property-class>
<value>#{loginSession}</value>
</managed-property>
</managed-bean>
<managed-bean>
<managed-bean-name>loginSession</managed-bean-name>
<managed-bean-class>shop.LoginSession</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
<managed-bean>
<managed-bean-name>userService</managed-bean-name>
<managed-bean-class>shop.UserService</managed-bean-class>
<managed-bean-scope>application</managed-bean-scope>
</managed-bean>
...
</faces-config>
As the "loginSession" bean is in the session scope, all its fields must implement
Serializable:
public class LoginSession {
private User user;
Now, run the application and try to login using a valid account or an invalid
account. It should work.
There is a minor issue though: As shown below, the code to log an error
message is using JSF specific classes such as FacesContext and is thus
polluting this class:
public class LoginRequest {
private String email;
private String password;
private UserService userService;
private LoginSession loginSession;
...
public String login() {
try {
User user = userService.findMatchingUser(this);
loginSession.setUser(user);
return "loggedIn";
} catch (UserNotFoundException e) {
FacesContext context = FacesContext.getCurrentInstance();
context.addMessage(null, new FacesMessage(
FacesMessage.SEVERITY_ERROR, "Login failed", null));
return null;
}
}
}
You may put this code into an action listener like this:
public class LoginValidatingListener implements ActionListener {
private UserService userService;
private LoginRequest loginRequest;
...
public void processAction(ActionEvent event)
throws AbortProcessingException {
try {
userService.findMatchingUser(loginRequest);
} catch (UserNotFoundException e) {
FacesContext context = FacesContext.getCurrentInstance();
context.addMessage(null, new FacesMessage(
FacesMessage.SEVERITY_ERROR, "Login failed", null));
throw new AbortProcessingException();
}
}
}
<f:view>
<h1>Login</h1>
Chapter 4 Creating an e-Shop 151
<h:form>
<h:panelGrid border="1" columns="2">
<h:outputText value="Email:"></h:outputText>
<h:inputText value="#{loginRequest.email}"></h:inputText>
<h:outputText value="Password:"></h:outputText>
<h:inputSecret value="#{loginRequest.password}"></h:inputSecret>
<h:outputText></h:outputText>
<h:commandButton value="Login" action="#{loginRequest.login}">
<f:actionListener type="shop.LoginValidatingListener"/>
</h:commandButton>
</h:panelGrid>
</h:form>
</f:view>
However, on one hand this is quite some extra work. On the other hand,
sometimes errors will occur only when you try to perform a business action. For
example, when withdrawing from an account, checking if the balance is enough
beforehand is useless due to concurrent accesses. Anyway, in this case you'll
simply leave it as is without using a separate action listener.
Create CheckoutService.java:
public class CheckoutService {
private Cart cart;
private Catalog catalog;
}
Define the "checkoutService" bean and hook up the beans:
<faces-config ...>
...
<managed-bean>
<managed-bean-name>checkoutService</managed-bean-name>
<managed-bean-class>shop.CheckoutService</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
<managed-property>
<property-name>cart</property-name>
<property-class>shop.Cart</property-class>
<value>#{cart}</value>
</managed-property>
<managed-property>
<property-name>catalog</property-name>
<property-class>shop.Catalog</property-class>
<value>#{catalog}</value>
</managed-property>
</managed-bean>
...
</faces-config>
Create the thankyou.jsp file:
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
Thank you for your order!
</body>
</html>
Define the navigation:
Chapter 4 Creating an e-Shop 153
Now run it. Login, add some products, checkout and confirm. It should work
fine:
154 Chapter 4 Creating an e-Shop
What if the user hasn't logged in yet? In that case, you should send him to the
login page. After logging in, he should be returned to the confirm page
automatically. To do that, create a ForceLoginPhaseListener class:
Chapter 4 Creating an e-Shop 155
You need to register this phase listener with the JSF engine. To do that, open
faces-config.xml, choose the "Others" tab and click "Phase Listener":
Click here
156 Chapter 4 Creating an e-Shop
1: Try to render a
protected page
edit ...
confirm
profile
login
If you use the navigation system in JSF, you'll have to add one navigation case
for each protected page:
edit
confirm ...
profile
editProfile
checkout ...
login
It means whenever you have one more protected page, you'll have to add one
more navigation case, meaning that the login page will never become stable. To
solve this problem, you can remember the originally requested page in the
phase listener:
public class ForceLoginPhaseListener implements PhaseListener {
public PhaseId getPhaseId() {
return PhaseId.RENDER_RESPONSE;
}
public void beforePhase(PhaseEvent event) {
FacesContext context = FacesContext.getCurrentInstance();
String viewId = context.getViewRoot().getViewId();
Chapter 4 Creating an e-Shop 157
if (viewId.equals("/confirm.jsp")) {
Application app = context.getApplication();
LoginSession loginSession = (LoginSession) app
.evaluateExpressionGet(context, "#{loginSession}", LoginSession.class);
if (loginSession.getUser() == null) {
loginSession.setOriginalViewId(viewId);
ViewHandler viewHandler = app.getViewHandler();
UIViewRoot viewRoot = viewHandler.createView(context, "/login.jsp");
context.setViewRoot(viewRoot);
}
}
}
public void afterPhase(PhaseEvent event) {
}
}
Add this field in the LoginSession class:
public class LoginSession {
private User user;
private String originalViewId;
Now restart the browser to get rid of the session. Then run the application
again. Try to checkout without logging in. It should ask you to login and then
return you to the confirm page:
Implementing logout
Suppose that you'd like to allow the user to logout:
Download at WoweBook.com
Chapter 4 Creating an e-Shop 159
The minimum that you need to do is to remove the User object from the
"loginSession" bean. However, a better way is to delete the session altogether
(including the shopping cart, for example). To do that, modify showcatalog.jsp:
<f:view>
<h1>Product listing</h1>
<h:dataTable border="1" value="#{catalog.products}" var="p">
... It stands for non-breaking space. That is, a space
</h:dataTable> between the two links.
<h:form>
<h:commandLink action="login">Login</h:commandLink>
<h:commandLink>
<h:outputText value="Logout"></h:outputText>
<f:actionListener type="shop.LogoutActionListener"/>
</h:commandLink>
</h:form> Remove the session in this action listener. Why
</f:view> not specify a method in the "action" attribute? You
could do that but removing the session is a UI
Do not set the outcome ("action")
specific task, not a business task. So an action
so that it remains on the catalog
listener is better.
page
Run it. Login and then logout. Then try to checkout and it should ask you to
login again.
Summary
A facet is a child component that is subjected to special processing by its
parent.
A request has a table of attributes. Each attribute has a name and a value
(Object). It allows you to give a name to an object. It is like request-scoped
managed bean except that it doesn't create the object; it only associates it with
a name.
To loop some items in a table, if the number of items can only be determined at
runtime, use the UI Data component. You provide a List of items to it and it will
loop through it for each item. Its children must be UI Column components. Each
UI Column represents a column in the table. For each item, the UI Data will
store the item into an attribute and ask each UI Column to render for it. Each UI
Column will render its own child components (excluding the "header" facet). A
UI Column can optionally have a facet named "header". In that case, the UI
Data will ask the UI Columns to render an extra header row before it starts to
process the data rows.
On form submission, the UI Data will loop through the items again, giving each
component inside an opportunity to apply request values, process validations,
update domain values and invoke application in each respective phase.
As one component inside the UI Data will render multiple times and generate
multiple HTML elements, the UI Data will pretend to have a different client id for
each item so that the component will have a unique client id for each item.
Because the current item is stored into an attribute, not a bean, if you need to
Chapter 4 Creating an e-Shop 161
pass it onto the next page, most likely you'll want to use the Set Property action
listener.
To create a link, use the UI Command component with a link renderer. In terms
of behavior, it is just like a UI Command component with a button renderer.
For a UI Command, in addition to setting an outcome in its "action" attribute,
you can also specify a method. This is useful when you need to perform a
business action. That method should return a string indicating the outcome. If
you need to perform a UI specific action, it's better to add an action listener to
the UI Command.
A UI Input can be rendered such that user input is echoed as stars. This is good
for password input.
If you display the properties of a request-scoped bean using a page, you must
be careful when the form is submitted because a new request-scoped bean will
be created and its id will not have been set. Usually you will use a UI Input
component along with an HTML Hidden renderer to store the id in the browser
as a hidden field. You need to load the other fields when the id is set.
The JSF engine uses a view handler to create the JSF component tree from a
specified view id. By default the JSP engine is the view handler. You need to
use a view handler when you want to by-pass the JSF navigation system: You
need to load a view and set it as the one to be render.
A session is identified by the session stored in a cookie in the browser. To start
a new session, either restart the browser or delete that cookie. To remove the
session on the server, call invalidate() on the session. This is commonly done
when logging out. All session scoped managed beans must implement
Serializable. Injected fields should be marked as transient.
You should never inject any managed bean into a session scoped bean, as
after deseralization the field will become null.
A phase listener will be notified before entering a phase or after exiting from a
phase. You can use it to make sure the user has logged in before rendering a
certain pages.
The external context means the platform on which the JSF engine is running. In
your case it is Tomcat. JSF assumes this platform is responsible for maintaining
the session.
163
Chapter 5
Chapter 5 Building Interactive Pages
with AJAX
164 Chapter 5 Building Interactive Pages with AJAX
If the user clicks on a question, its answer will be shown instantly, while the rest
of page is not refreshed:
If he clicks that question again, the answer will be hidden again. As a first step,
you'll show a single question only. To do that, create a Dynamic Web Project
named FAQ with JSF enabled. Create a listfaq.jsp page:
Chapter 5 Building Interactive Pages with AJAX 165
...
<f:view> You'll trigger the display of the answer
<div> in this method
<h:form>
<h:commandLink action="#{faqService.trigger}">
<h:outputText value="#{faqService.questionText}">
</h:outputText>
</h:commandLink>
</h:form>
</div>
<div>
<h:outputText
value="#{faqService.answerText}"
rendered="#{faqService.expanded}">
</h:outputText>
</div> You'll create an isExpanded() method. If it returns
</f:view> true, the component will render itself, otherwise it
won't. If the "rendered" attribute is not set, it is
... assumed to be true.
Create a bean for it. As it must remember the expanded status, you need to use
the session scope:
<faces-config ...>
<managed-bean>
<managed-bean-name>faqService</managed-bean-name>
<managed-bean-class>faq.FAQService</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
</faces-config>
Therefore, the class must implement Serializable:
public class FAQService implements Serializable {
...
}
Add the project as a module to the Tomcat instance and then run it. It should
work:
166 Chapter 5 Building Interactive Pages with AJAX
To implement this idea, you need to use the JBoss RichFaces components. So,
modify web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app ...>
<display-name>FAQ</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>/faces/*</url-pattern>
</servlet-mapping>
<context-param>
<param-name>org.richfaces.SKIN</param-name>
<param-value>blueSky</param-value>
</context-param>
<filter>
<display-name>RichFaces Filter</display-name>
<filter-name>richfaces</filter-name>
<filter-class>org.ajax4jsf.Filter</filter-class>
</filter>
<filter-mapping>
<filter-name>richfaces</filter-name>
<servlet-name>Faces Servlet</servlet-name>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
168 Chapter 5 Building Interactive Pages with AJAX
<dispatcher>INCLUDE</dispatcher>
</filter-mapping>
</web-app>
Copy the required jars files into WEB-INF/lib:
Modify listfaq.jsp:
Use the <commandLink> <a onclick="some Javascript">How to...</a>
in the Ajax4Jsf tag lib 3: Render this "qa"
component only
Again, you don't want to refresh the whole page, but just the relevant parts. This
is just like the <a4j:commandLink>, but now you need a button. This is done by
using an <a4j:commandButton>:
170 Chapter 5 Building Interactive Pages with AJAX
<f:view>
<div> Put the form to the right
<h:form>
<a4j:commandLink action="#{faqService.trigger}" reRender="answerPanel">
<h:outputText
value="#{faqService.questionText} (#{faqService.averageRating})"
id="qt">
</h:outputText> This part is just a literal string Literal string again
</a4j:commandLink> which will be output as is
</h:form>
<h:form style="float:right">
<h:inputText size="2" value="#{faqService.rating}"></h:inputText>
<a4j:commandButton
value="Rate" Refresh this component
action="#{faqService.rate}"
reRender="qt">
</a4j:commandButton>
</h:form> <commandButton> from the Ajax4Jsf tag lib. It will
</div> put Javascript into the onclick handler of the HTML
<div> submit button.
<h:panelGroup id="answerPanel">
<h:outputText
value="#{faqService.answerText}"
rendered="#{faqService.expanded}">
</h:outputText>
</h:panelGroup>
</div>
</f:view>
}
Now run it and it should work:
What if the user enters something invalid such as "abc" as the rating? Then it
should display an error. To do that, modify listfaq.jsp:
<messages> will render nothing if there is no error.
Then later you'll be unable to refresh the HTML
element. So, put it inside a <panelGroup>.
<f:view>
<h:panelGroup id="msgs">
<h:messages></h:messages>
</h:panelGroup>
<div>
<h:form>
<a4j:commandLink action="#{faqService.trigger}" reRender="answerPanel">
<h:outputText
value="#{faqService.questionText} (#{faqService.averageRating})"
id="qt">
</h:outputText>
</a4j:commandLink>
</h:form> Make the error message look better
<h:form style="float:right">
<h:inputText
label="rating" size="2" value="#{faqService.rating}"></h:inputText>
<a4j:commandButton
value="Rate"
action="#{faqService.rate}"
reRender="qt,msgs">
</a4j:commandButton>
Refresh both the question text and
</h:form>
the error messages
</div>
<div>
<h:panelGroup id="answerPanel">
<h:outputText
value="#{faqService.answerText}"
rendered="#{faqService.expanded}">
</h:outputText>
</h:panelGroup>
</div>
</f:view>
What if you'd like to submit the form when the user moves the mouse over the
Rate button (even without clicking it)? You can do it this way:
<f:view>
<h:panelGroup id="msgs">
<h:messages></h:messages>
</h:panelGroup>
<div>
<h:form>
<a4j:commandLink action="#{faqService.trigger}" reRender="answerPanel">
<h:outputText
value="#{faqService.questionText} (#{faqService.averageRating})"
id="qt">
</h:outputText>
</a4j:commandLink>
</h:form> No need for any
<h:form style="float:right"> behavior
<h:inputText
label="rating" size="2" value="#{faqService.rating}"></h:inputText>
<h:commandButton value="Rate" action="..." reRender="...">
<a4j:support
Use a normal JSF event="onmouseover" 1: Generate the <input> element
commandButton action="#{faqService.rate}"
reRender="qt,msgs"> <input
</a4j:support> type="submit"
</h:commandButton> onmouseover="some Javascript">
</h:form> 4: Refresh the components
</div>
2: Set the onmouseover event
<div>
handler
<h:panelGroup id="answerPanel">
<h:outputText
value="#{faqService.answerText}" 3: When the mouse is over the
rendered="#{faqService.expanded}"> submit button, send a request to
</h:outputText> your application calling this
</h:panelGroup> method.
</div>
</f:view>
like this (see the screen shot below): The user can click a "Rate" link. Then it
will open a modal panel to show the form to allow him to enter the rating:
After closing the form, the average rating for the question will be refreshed.
Now, let's do it. Modify listfaq.jsp:
174 Chapter 5 Building Interactive Pages with AJAX
Now run it and it should work. For the moment, if the user enters some
garbage, it will close the modal panel and display the error in the main page.
What if you'd like to display the error in the modal panel and not close it as
shown below?
Chapter 5 Building Interactive Pages with AJAX 175
Finally, the modal panel may be a bit too large. You can set its initial size to say
200 pixels x 100 pixels:
<rich:modalPanel id="mp" width="200" height="140">
...
</rich:modalPanel>
Run it and the panel should be smaller.
foo {
font-family: Arial;
color: ...;
}
<div class="foo">
<span...>
<form...>
<input...>
</div>
Now run it again and you should notice that font has changed:
Summary
AJAX means that when a certain event occurs in the browser, a request is sent
to the application so that it can perform some action and then only parts of a
page are refreshed. You can use an <a4j:commandLink> for a link, an
<a4j:commandButton> for a button or an <a4j:support> for any other events. In
these tags, you also specify an action method to execute in the application and
a list of component ids which are to be refreshed.
A component can be excluded from rendering. In that case, normally it will
generate nothing. If you need to show it using AJAX, you can put it inside a
panel. Similarly, some components such as the UI Messages may output
nothing in normal use. To update them using AJAX, put them inside a panel.
178 Chapter 5 Building Interactive Pages with AJAX
Chapter 6
Chapter 6 Using Facelets
180 Chapter 6 Using Facelets
Click Next. It will suggest to add this project as a module to the Tomcat server
instance:
Chapter 6 Using Facelets 183
This is what you want. Click Finish. It will suggest switching to the Web
Development perspective (provided by the JBoss IDE Tools). Say Yes. Now the
project is created. Copy the JSF RI jar files and those of RichFaces into WEB-
INF/lib:
Next, right click the WebContent folder and choose "New | XHTML File". Enter
listfaq as the name:
184 Chapter 6 Using Facelets
Click Next. It will then asks you what tag libs you'd like to use in this page.
Choose as shown below:
Click Finish. Then you'll see a visual page editor very much like the one you've
been using:
Chapter 6 Using Facelets 185
The palette
In addition, the file is a strict XML file (a JSP file is not XML) and a tag lib is
used as an XML namespace:
186 Chapter 6 Using Facelets
listfaq.xhtml
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:a4j="http://richfaces.org/a4j">
</html>
Why is being strict XML significant? It means you can use the many XML
editors to edit the file.
Now modify the file either by entering the code or using drag and drop. Content
assist will work as usual:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:a4j="http://richfaces.org/a4j">
<body>
<div>
<h:form>
<h:commandLink
value="#{faqService.questionText}" action="#{faqService.trigger}">
</h:commandLink>
</h:form>
</div>
<div>
<h:outputText
value="#{faqService.answerText}" rendered="#{faqService.expanded}">
</h:outputText>
</div>
</body>
</html>
Copy the FAQService class from the previous chapter. Create the bean
definition for it. Double click on the faces-config.xml file, the JBoss XML Editor
will open:
Chapter 6 Using Facelets 187
Choose "Managed Beans" on the left and then click "Add" to create a session
scoped bean:
Go ahead to finish it. Start the Tomcat instance and try to access it. Here is the
URL:
188 Chapter 6 Using Facelets
2: You handle it
Tomcat JSF engine
4: Replace the
1: Look, jsf extension. extension with
Context path, as usual. xhtml so it
gets /listfaq.xhtml
as the view id. Ask
http://localhost:8080/FAQFacelets/listfaq.jsf the view handler to
load the view.
WebContent
listfaq.xhtml
5: Use the view id as a
... relative path to read the
file and create a
component tree
Run it and it should work. However, you may ask, so what? It is not that
different from using JSP. Read on.
To create such a Facelet tag (and the tag lib), create a META-INF folder in your
Java source folder and then create a file foo.taglib.xml in it (The file name is
unimportant as long as it ends with .taglib.xml). The content is like:
Chapter 6 Using Facelets 189
Nam e Value
q q1
...
...
<html
xmlns="http://www.w3.org/1999/xhtml"
xmlns:foo="http://foo.com">
<body>
<span jsfc="foo:qa" q="#{faqService.q1}" />
</body>
</html>
Run it and it will continue to work.
If you view the HTML source code in the browser, you'll see:
It means the source code of your web pages is being exposed! This is a serious
security problem. To fix it, modify web.xml:
194 Chapter 6 Using Facelets
<web-app ...>
...
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
It represents some "protected"
<servlet-name>Faces Servlet</servlet-name> files. That is, those files can be
<url-pattern>*.jsf</url-pattern> accessed by a certain users
</servlet-mapping> only.
<security-constraint>
<display-name>Block browser access to xhtml</display-name>
<web-resource-collection>
<web-resource-name>xhtml files</web-resource-name>
<url-pattern>*.xhtml</url-pattern>
</web-resource-collection>
<auth-constraint> Here, all *.xhtml are
</auth-constraint> protected.
</security-constraint>
...
</web-app> Usually here you would list the user
groups, but in this you don't want
anyone to have access, so leave it
empty.
Restart the application and try to access the file again. This time you will get an
error:
Summary
Facelets is a view handler that can replace the JSP engine in a JSF application.
It allows you to easily create new tags using existing tags. In addition, it allows
you to hide JSF and Facelets tags into normal HTML tags so that you can work
on the pages using web authoring tools such as Dreamweaver.
By default users can retrieve your xhtml files in the browser. This is a security
problem. You should block such direct accesses using web.xml.
Facelets is a recommended technology by many JSF developers.
As Facelets is not in the JSF standard, JSF component libraries may not
support it.
197
Chapter 7
Chapter 7 Providing a Common Layout
with Facelets
198 Chapter 7 Providing a Common Layout with Facelets
To do that, create a new Facelets project named "Layout". Copy the two JSF jar
files into WEB-INF/lib. Create three pages: home.xhtml, products.xhtml and
contact.xhtml. home.xhtml and products.xhtml may be like:
Chapter 7 Providing a Common Layout with Facelets 199
home.xhtml products.xhtml
<html ...> <html ...>
<span jsfc="h:form"> <span jsfc="h:form">
<table> Must not use <br> <table>
<tr> as XML requires a <tr>
closing tag <td width="40%">
<td width="40%">
<a jsfc="h:commandLink" <a jsfc="h:commandLink"
action="home">Home</a> action="home">Home</a>
<br/> <br/>
<a jsfc="h:commandLink" <a jsfc="h:commandLink"
action="products">Products</a> action="products">Products</a>
<br/> <br/>
<a jsfc="h:commandLink" <a jsfc="h:commandLink"
action="contact">Contact</a> action="contact">Contact</a>
</td> </td>
<td>This is the Home page.</td> <td>This is the Products page.</td>
</tr> </tr>
</table> </table>
</span></html> </span></html>
Unique content
Or graphically, the situation is like what is shown below, the HTML page
structures are the same, the only difference is the cell content:
Html Html
Form Form
Table Table
Row Row
Cell Cell Cell Cell
In that case, you can create a page to contain the common structure (see the
diagram below). The varying parts are left as abstract. Then let each page
extend this page and provide its unique content, just like Java class inheritance:
Base
Abstract
Home Products
Unique content Unique content
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets">
<span jsfc="h:form">
<table> The <insert> tag is a Facelets tag
<tr>
<td width="40%">
<a jsfc="h:commandLink" action="home">Home</a> <br/>
<a jsfc="h:commandLink" action="products">Products</a> <br/>
<a jsfc="h:commandLink" action="contact">Contact</a>
</td>
<td>
<span jsfc="ui:insert">unique content</span>
</td>
</tr>
It indicates that this is an abstract part
</table>
and the concrete part will be inserted.
</span>
</html>
products.xhtml is:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets">
<span jsfc="ui:composition" template="/base.xhtml">
This is the Products page.
</span>
</html>
Run it and it should work (except for the links). To create the links, you may
define the navigation rules like:
Chapter 7 Providing a Common Layout with Facelets 201
/home.xhtml
/home.xhtml
home
/products.xhtml
products
/contact.xhtml
contact
/products.xhtml
/home.xhtml
home
/products.xhtml
products
/contact.xhtml
contact
/home.xhtml
home
/products.xhtml
products
/contact.xhtml
contact
Base Abstract2
Abstract1
This is like a base class having two abstract methods. For this to work, you
need to give a name to each abstract part:
204 Chapter 7 Providing a Common Layout with Facelets
<faces-config ...>
<navigation-rule>
<from-view-id>*</from-view-id>
<navigation-case>
<from-outcome>products</from-outcome> 2: It will be considered if
<to-view-id>/products.xhtml</to-view-id> no matching navigation
</navigation-case> case was found in the
<navigation-case> specific rule
<from-outcome>home</from-outcome>
<to-view-id>/home.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
<navigation-rule>
<from-view-id>/products.xhtml</from-view-id>
<navigation-case> 1: It will be considered
<from-outcome>hotDeals</from-outcome> first as it specifies a
<to-view-id>/hotdeals.xhtml</to-view-id> specific view id
</navigation-case>
</navigation-rule>
...
</faces-config>
Summary
If you have pages with a common layout, you can extract the common stuff into
a base page and mark the abstract parts using <ui:define>. Then in each child
page, suck in the base page using <ui:composition> and provide each concrete
part using <ui:define>. Each part should have a unique name. If there is only
one abstract part, you can omit the name and provide the concrete part as the
body of the <ui:composition> element.
You can use a star as the view id in a navigation rule. In that case it will match
any view id. This is useful if multiple pages share the same navigation cases. If
a page needs some additional navigation cases, it can have its own normal
navigation rule which will take be checked first.
207
Chapter 8
Chapter 8 Using JBoss Seam
208 Chapter 8 Using JBoss Seam
If you don't see this option, it means you didn't install the JBoss IDE Tools as
told in the previous chapter. Click Next. Then enter a project name and choose
to use Seam 2.0:
Chapter 8 Using JBoss Seam 209
things like database name, user name and password. Here, choose the
HSQLDB type:
Click "Add":
Choose it
Choose the hsqldb.jar file and click "Edit Jar/Zip" to specify its correct location:
214 Chapter 8 Using JBoss Seam
Finish the part about the DB. Then specify the Java packages to be used:
216 Chapter 8 Using JBoss Seam
Finish the creation of the project. It will asks if you'd like to switch to the Seam
perspective. Say Yes. Then copy commons-collections.jar, commons-
logging.jar, dom4j.jar, hibernate-validator.jar, javassist.jar, jta.jar and
persistence-api.jar in c:\jboss-seam\lib into WEB-INF/lib. Refresh the project.
By default, the Seam plugin assumes that your application will connect to the
database at startup. As you don't have a database, modify WEB-
INF/components.xml (see the diagram below). This components.xml file is used
to configured the Seam components, which are like the JSF managed beans or
Spring beans:
Chapter 8 Using JBoss Seam 217
<core:manager concurrent-request-timeout="500"
conversation-timeout="120000" conversation-id-parameter="cid" />
Comment out these two elements. They'll try
<!-- to connect to a database.
<persistence:managed-persistence-context name="entityManager"
auto-create="true"
entity-manager-factory="#{ShopSeamEntityManagerFactory}" />
<persistence:entity-manager-factory
name="ShopSeamEntityManagerFactory" persistence-unit-name="ShopSeam" />
-->
...
</components>
In addition, by default Seam will start and commit a transaction for you
automatically at suitable times (for example, when updating domain values and
invoking application):
This in turn requires a database connection (or an EJB3 server). As you have
neither, you need to tell Seam not to manage the transactions for you. This is
again done by configuring components in components.xml:
218 Chapter 8 Using JBoss Seam
<components xmlns="http://jboss.com/products/seam/components"
xmlns:core="http://jboss.com/products/seam/core"
xmlns:persistence="http://jboss.com/products/seam/persistence"
xmlns:drools="http://jboss.com/products/seam/drools"
xmlns:bpm="http://jboss.com/products/seam/bpm"
xmlns:security="http://jboss.com/products/seam/security"
xmlns:mail="http://jboss.com/products/seam/mail"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:transaction="http://jboss.com/products/seam/transaction"
xsi:schemaLocation=" What elements are included in
http://jboss.com/products/seam/core this namespace? This xsd file
http://jboss.com/products/seam/core-2.0.xsd includes all the definitions. Having
... this information Eclipse can
http://jboss.com/products/seam/transaction perform content assist.
http://jboss.com/products/seam/transaction-2.0.xsd">
<transaction:no-transaction />
... It tells the component named
</components> "init" not to start and commit
Seam assumes that there is always transactions automatically
a transaction component that can
start or commit a transaction. Here
you install a no-op transaction
component that does nothing for
those operations:
transaction
1: start a
transaction 2: Do nothing
Now start the Tomcat instance and observe the output in the console. Seam
should start and print out a lot of information saying various components are
initialized:
INFO: Initializing Sun's JavaServer Faces implementation (1.2_07-b03-FCS) for
context '/ShopSeam'
INFO: Welcome to Seam 2.0.1.GA
...
INFO: Installing components...
Feb 17, 2008 1:16:03 PM org.jboss.seam.Component <init>
INFO: Component: authenticator, scope: EVENT, type: JAVA_BEAN, class:
shop.session.Authenticator
Feb 17, 2008 1:16:03 PM org.jboss.seam.Component <init>
INFO: Component: org.jboss.seam.async.dispatcher, scope: APPLICATION, type:
JAVA_BEAN, class: org.jboss.seam.async.ThreadPoolDispatcher
Feb 17, 2008 1:16:03 PM org.jboss.seam.Component <init>
INFO: Component: org.jboss.seam.captcha.captcha, scope: SESSION, type: JAVA_BEAN,
class: org.jboss.seam.captcha.Captcha
...
Most importantly, there shouldn't be any exception.
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html">
<body>
<h:form>
<h:dataTable border="1" value="#{showCatalog.products}" var="p">
<h:column>
<h:outputText value="#{p.id}"></h:outputText>
</h:column>
<h:column>
<h:commandLink value="#{p.name}" action="#{showCatalog.showDetails}">
</h:commandLink>
</h:column>
<h:column>
<h:outputText value="#{p.price}"></h:outputText>
</h:column>
</h:dataTable>
</h:form>
</body>
</html>
There is nothing special here. The interesting thing is the "showCatalog" bean.
Create a class ShowCatalog in the shop.helper package. Note that there are
two existing source folders in the project:
Which one to use doesn't really affect the functionality. However, as the helper
belongs to the UI, not the domain, so the src/action folder seems a better fit. So
create the ShowCatalog class there:
220 Chapter 8 Using JBoss Seam
package shop.helper;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.In; Define a bean named "showCatalog"
from this class. It is actually not a JSF
import org.jboss.seam.annotations.Name; bean, but a Seam component that
import org.jboss.seam.annotations.Scope; can be used like a JSF bean.
@Name("showCatalog")
@Scope(ScopeType.EVENT)
public class ShowCatalog {
@In The scope for the bean (i.e.,
private CatalogService catalogService; component). Event scope the
same thing as the request
public List<Product> getProducts() { scope.
return catalogService.getProducts();
}
public String showDetails() {
return "details";
}
}
The injection performed by Seam is a bit different from JSF: For example, in
this case, if someone calls any method on "showCatalog" component (see the
diagram below), Seam will intercept the call and set the "catalogService" field to
point to the "catalogServie" component before the body of the method is
executed:
@Name("showCatalog") 2: Seam will set the field to the
@Scope(ScopeType.EVENT) component
public class ShowCatalog {
@In
1: Someone calls any private CatalogService catalogService;
method (e.g., the
getProducts method)
public List<Product> getProducts() {
return catalogService.getProducts();
}
3: The body of the public String showDetails() {
method starts to return "details"; ...
execute }
}
Seam components for the app
Name Value
catalogService
... ...
... ...
The advantage of this is that even if the "showCatalog" component were in the
session scope, after deserialization it would still gain access to the injected
components.
Next, create the "catalogService" component and the CatalogService class. As
it is a service, let's put it into the shop.service package. Strictly speaking, a
Chapter 8 Using JBoss Seam 221
service doesn't belong to the UI or the business domain, but for simplicity, let's
put it into the src/model folder:
package shop.service;
Component name
@Name("catalogService")
@Scope(ScopeType.APPLICATION) Application scope
public class CatalogService {
private List<Product> products;
public CatalogService() {
products = new ArrayList<Product>();
products.add(new Product("p01", "Pencil", "a", 1.20));
products.add(new Product("p02", "Eraser", "b", 2.00));
products.add(new Product("p03", "Ball pen", "c", 3.50));
}
public List<Product> getProducts() {
return products;
}
public Product getProduct(String pid) {
for (Product p : products) {
if (p.getId().equals(pid)) {
return p;
}
}
return null;
}
}
Create the Product class in the shop.domain package. Put it into the src/model
folder:
package shop.domain;
public Product() {
}
public Product(String id, String name, String desc, double price) {
this.id = id;
this.name = name;
this.desc = desc;
this.price = price;
}
public String getDesc() {
return desc;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
}
It is not a Seam component. It is just a normal Java class. Now, run it by going
222 Chapter 8 Using JBoss Seam
to the URL:
Context path, as usual.
http://localhost:8080/ShopSeam/showcatalog.seam
This is because unlike JSF managed beans, by default Seam will NOT create
Seam components automatically, unless it is accessed directly by an EL
expression in your page. In your case the "showCatalog" component will be
created automatically but not the "catalogService" component:
<html ...>
<body>
<h:form>
<h:dataTable border="1" value="#{showCatalog.products}" var="p">
<h:column>
<h:outputText value="#{p.id}"></h:outputText>
</h:column>
<h:column>
<h:commandLink value="#{p.name}" action="#{showCatalog.showDetails}">
</h:commandLink>
Chapter 8 Using JBoss Seam 223
</h:column>
<h:column>
<h:outputText value="#{p.price}"></h:outputText>
</h:column>
</h:dataTable>
</h:form>
</body>
</html>
To tell Seam to automatically create the "showCatalog" component, do it this
way:
@Name("catalogService")
@Scope(ScopeType.APPLICATION)
@AutoCreate
public class CatalogService {
...
}
Run it again and it should work (except for the links).
@Name("showCatalog")
@Scope(ScopeType.EVENT) By default, if the product field is null, Seam will
public class ShowCatalog { treat it as an error. Setting required to false will
@In suppress the error.
private CatalogService catalogService;
@Out(required=false)
private Product product;
product:
3: Point to some 4: Just before the method returns to
Product object the caller, Seam intercepts it and sets
the "product" component to the
"product" field.
Information about
the /showcatalog.xhtml
page
<page view-id="/showcatalog.xhtml">
<navigation> If outcome is "details"
<rule if-outcome="details">
<render view-id="/productdetails.xhtml" />
</rule>
</navigation> Then render /productdetails.xhtml
</page>
<page view-id="*">
<navigation>
<rule if-outcome="home">
<redirect view-id="/home.xhtml" />
</rule>
</navigation>
</page>
...
</pages>
Run it and it should work. Now, the next question is how to find out the selected
product. In the past you use the <setPropertyActionListener> to do that but it is
quite a low level thing and it requires setting "immediate" attribute of the
command link to true and thus is not really a good solution. The idea is, instead
of providing a List to the UI Data, you provide a DataModel (see the diagram
below). The UI Data will ask it to see how many rows there are, loop through
each row and get the object on the each row:
UI Data
How many rows?
DataModel
The key is, when a link is clicked, in the Invoke Application phase, the UI Data
will set the current row to the selected row:
1: A link is clicked
2: Set the current
UI Data row to the selected
row
DataModel
As long as you can get access to the DataModel, you can find out the selected
row index and get the right Product object. To implement this idea, modify
ShowCatalog:
226 Chapter 8 Using JBoss Seam
...
import javax.faces.model.DataModel;
import javax.faces.model.ListDataModel;
@Name("showCatalog")
@Scope(ScopeType.EVENT)
public class ShowCatalog {
@In
private CatalogService catalogService;
@Out(required=false)
private Product product;
private DataModel dataModel;
Create a DataModel wrapping
around this List of Product
public DataModel getDataModel() { objects
if (dataModel == null) {
dataModel = new ListDataModel(getProducts());
}
return dataModel;
}
public List<Product> getProducts() {
return catalogService.getProducts();
}
public String showDetails() {
product = new Product("p04", "a", "b", 3.6);
product = (Product) dataModel.getRowData();
return "details";
} 1: Get the object on
} the current row List Current row: 0
DataModel
List
2: Call get(0) on the
list, assuming that
the current row is 0.
Name Value
... ... 1: Give me the component
named "productList"
... ...
... ...
@Name("showCatalog") Seam
@Scope(ScopeType.EVENT) 2: Check if there is such a
public class ShowCatalog { component there. At the
@In beginning there is not.
private CatalogService catalogService;
@Out(required = false)
private Product product;
@Out
private DataModel productList;
4: The field is set
@Factory("productList")
public void loadProducts() { 3: Look, there is a factory
productList = new ListDataModel(getProducts()); method for the component.
} Call it.
public List<Product> getProducts() {
return catalogService.getProducts();
}
public String showDetails() {
product = (Product) productList.getRowData();
return "details";
}
}
import org.jboss.seam.annotations.datamodel.DataModel;
@Name("showCatalog")
@Scope(ScopeType.EVENT)
public class ShowCatalog {
@In
private CatalogService catalogService;
@Out(required = false)
private Product product;
@Out You only need to provide a List
@DataModel
private DataModel List<Product> productList;
@DataModel will create the DataModel. You
@Factory("productList")
only need to provide a List.
public void loadProducts() {
productList = new ListDataModel(getProducts());
}
public List<Product> getProducts() {
return catalogService.getProducts();
}
@In(value = "productList", required = false) As the DataModel is no
private javax.faces.model.DataModel dataModel; longer a field, to access
it, you need to inject it.
public String showDetails() {
product = (Product) dataModel.getRowData();
return "details"; This is the name of the component to be injected.
} As it is different from the field name ("dataModel"),
} you have to specify it explicitly.
Run it and it will continue to work. In addition, Seam can help you inject the row
data of a DataModel component you outjected:
Chapter 8 Using JBoss Seam 229
@Name("showCatalog")
@Scope(ScopeType.EVENT)
public class ShowCatalog {
@In
private CatalogService catalogService;
@Out(required = false)
private Product product;
@DataModel
private List<Product> productList;
DataModel
@Factory("productList") 1: Find a @DataModel and get the
component name ("productList")
public void loadProducts() { 3: Call getRowData() on
productList = getProducts(); it and inject the result
} into the field
public List<Product> getProducts() {
return catalogService.getProducts();
} Name Value
@DataModelSelection ... ...
private Product selected; 2: Look up the "productList"
DataModel ... ...
@In(value = "productList", required = false) ... ...
private javax.faces.model.DataModel dataModel;
Run it and it will continue to work. Finally, the code can be further improved a
little bit. Currently the flow is:
@Name("showCatalog")
@Scope(ScopeType.EVENT)
public class ShowCatalog {
@In
private CatalogService catalogService;
@Out(required = false)
private Product product; 3: Outject
@DataModel
private List<Product> productList;
@Factory("productList")
public void loadProducts() {
productList = getProducts();
}
public List<Product> getProducts() {
return catalogService.getProducts();
}
@DataModelSelection 2: Assign
private Product selected; 1: Inject
@Name("showCatalog")
@Scope(ScopeType.EVENT)
public class ShowCatalog {
@In
private CatalogService catalogService;
@Out(required = false)
@DataModelSelection
private Product product; Now it is both injected and outjected. It is
@DataModel said to be bijected.
private List<Product> productList;
@Factory("productList")
public void loadProducts() {
productList = getProducts();
}
@DataModelSelection
private Product selected;
@Name("productDetails")
@Scope(ScopeType.EVENT)
public class ProductDetails {
@In
private Product product;
clicked, a new one will be created which is not the one displayed. In the past
you solved this problem by storing the product id in a hidden field and loading all
its properties when the id is set. This is a lot of work. Seam provides a very
powerful solution. It provides a new scope: conversation scope. For example,
suppose at a certain time you start a new conversation (see the diagram below).
Seam will assign a unique id (say 123) to it. Then a request comes in and a
response is rendered. Assume in the process a Seam component (say "foo") is
created and it is marked to be in the conversation scope, then it will be stored
into a component table for conversation 123. That table is stored in the session.
Suppose that another request comes in. The "foo" component will still be there
for you to access. If later you decide to end the conversation, Seam will then
destroy the component table for conversation 123:
Time
How is it better than using the session scope? For example, if the user views
product 1 in a tab in the browser (see the diagram below), if you're using the
session scope, product 1 will be stored in the session scope. Suppose that the
user then opens a new tab and views product 2. Then product 2 will replace
product 1 in the session. Finally, suppose that the user returns to the original
tab and clicks the "Add" button. Even though he is looking at product 1 in the
tab, your application will add product 2 to the shopping cart as product 2 is in
the session! This is extremely confusing to the user:
232 Chapter 8 Using JBoss Seam
What if you use the conversation scope instead? Then, you'll have a separate
conversation for each tab and the two tabs will work independently:
Conversation 1 Conversation 2
Name Value Name Value
product product
... ... ... ...
... ... ... ...
Time
Why is it important? When handling the request, you may create a conversation
scope component first (see the diagram below) and then decide to start a
conversation. In that case, Seam will NOT create a new conversation. Instead,
it will use that temporary conversation and mark it as a normal (non-temporary)
conversation. The result is, the conversation scoped component will remain
there even though when it was added, the conversation was still a temporary
one:
Time
To make use of this feature, change the scope of the "product" component and
let it implement Serializable:
@Name("product")
@Scope(ScopeType.EVENT)
@Scope(ScopeType.CONVERSATION)
public class Product implements Serializable {
private String id;
private String name;
private String desc;
private double price;
...
}
234 Chapter 8 Using JBoss Seam
end conversation
showcart
<pages ...>
<page view-id="/showcatalog.xhtml">
<navigation>
<rule if-outcome="details">
<begin-conversation/>
<render view-id="/productdetails.xhtml" />
</rule>
</navigation> Start a conversation before
</page> rendering this page
<page view-id="/productdetails.xhtml">
<begin-conversation/>
<navigation>
<rule if-outcome="catalog">
<end-conversation/>
<render view-id="/showcatalog.xhtml" />
</rule>
<rule if-outcome="addedToCart">
<end-conversation/>
<render view-id="/showcart.xhtml" />
</rule>
</navigation>
</page>
...
</pages>
To really add the product to the shopping cart, you need to create a session
scoped component named "cart". To do that, create a Cart class in the
shop.domain package:
No page is going to refer to it
package shop.domain; directly, so need to auto-
create it.
@Name("cart") Everything in the session
@Scope(ScopeType.SESSION) must implement
@AutoCreate Serializable
public class Cart implements Serializable {
private List<String> productIds;
public Cart() {
productIds = new ArrayList<String>();
}
public void add(String pid) {
productIds.add(pid);
}
public List<String> getProductIds() {
return productIds;
}
}
</h:column>
<h:column>
<h:outputText value="#{p.price}"></h:outputText>
</h:column>
</h:dataTable>
<h:form>
<h:commandButton value="Checkout" action="checkout"></h:commandButton>
<h:commandButton value="Continue shopping" action="catalog"></h:commandButton>
</h:form>
</body>
</html>
Create the ShowCart class in the shop.helper package:
package shop.helper;
@Name("showCart")
@Scope(ScopeType.EVENT)
public class ShowCart { Inject it to get the product ids
@In Inject it to get the product names
private Cart cart; and prices from the ids
@In
private CatalogService catalogService;
<h:form>
<h:commandButton value="Confirm" action="#{confirm.confirm}">
</h:commandButton>
<h:commandButton value="Continue shopping" action="catalog">
</h:commandButton>
</h:form>
</body>
</html>
Create the Confirm class in the shop.helper package:
package shop.helper;
@Name("confirm")
@Scope(ScopeType.EVENT)
public class Confirm {
@In
private Cart cart;
@In
private CatalogService catalogService;
Logging in
In order to retrieve the credit card number from the user account, you need to
allow the user to login first. To do that, Seam already provides a session scoped
component that can hold the user name and password:
Identity
username:
password:
The plugin even creates a login.xhtml file for you. But here, let's modify it as:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html">
<body>
It will output an HTML <label>
<h:messages/>
element:
<h:form id="f">
<h:panelGrid columns="2">
<h:outputLabel for="email">Email</h:outputLabel>
<h:inputText id="email" value="#{identity.username}" />
<h:outputLabel for="password">Password</h:outputLabel>
<h:inputSecret id="password" value="#{identity.password}" />
</h:panelGrid>
<h:commandButton value="Login" action="#{identity.login}" />
</h:form>
</body>
</html>
The "identity" component also
<label for="f:email">Email</label>
has a login() method to
authenticate the user <input id="f:email">....</input>
How can its login() method know if the user name and password are valid or
not? It doesn't know. It will assume that you will provide an "authenticator"
component which has an authenticate() method. Furthermore, the plugin
actually created one for you in the shop.session package:
package shop.session;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Logger;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.log.Log;
import org.jboss.seam.security.Identity;
@Name("authenticator")
public class Authenticator
{
@Logger Log log;
return true;
}
}
To authenticate the user, create the UserService class in the shop.service
package:
package shop.service;
@Name("userService")
@Scope(ScopeType.APPLICATION)
@AutoCreate
public class UserService {
private List<User> users;
public UserService() {
users = new ArrayList<User>();
users.add(new User("paul@yahoo.com", "aaa", "1111 2222 3333 4444"));
users.add(new User("john@hotmail.com", "bbb", "2222 3333 4444 5555"));
}
public User findMatchingUser(String email, String password) {
for (User user : users) {
if (user.matches(email, password)) {
return user;
}
}
throw new UserNotFoundException();
}
public User getUser(String email) {
for (User user : users) {
if (user.getEmail().equals(email)) {
return user;
}
}
throw new UserNotFoundException();
}
}
Create the User class in the shop.domain package:
package shop.domain;
}
Now, fill in the code in the Authenticator class:
package shop.session;
</page>
...
</pages>
Where it will go after logging in? The plugin already created a login.page.xml file
to define the navigation case:
This navigation case will apply only if the
Instead of checking the outcome, it
action expression is #{identity.login}, which
evaluates an EL expression to check
is indeed the case here.
if the user has logged in.
<h:form id="login">
<h:panelGrid columns="2">
<h:outputLabel for="email">Email</h:outputLabel>
<h:inputText id="email" value="#{identity.username}" />
<h:outputLabel for="password">Password</h:outputLabel>
<h:inputSecret id="password" value="#{identity.password}" />
</h:panelGrid>
<h:commandButton value="Login" action="#{identity.login}" />
</h:form>
The home.xhtml page is a dummy page created by the plugin. Modify it to tell
the browser to go to your showcatalog page:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Refresh" content="0; URL=showcatalog.seam"/>
</head>
</html>
Tell the browser to refresh Wait 0 second before Relative path to the
refreshing. It means desired page
do not wait.
Modify the confirm page to get the credit card number from the logged in user:
Chapter 8 Using JBoss Seam 243
import org.jboss.seam.security.Identity;
@Name("confirm")
@Scope(ScopeType.EVENT)
public class Confirm {
@In
private Cart cart;
@In
private CatalogService catalogService;
@In
private UserService userService;
...
<page view-id="/confirm.xhtml" login-required="true">
<navigation >
<rule if-outcome="charged">
<render view-id="/thankyou.xhtml" />
</rule> Check if a user has logged in
<rule if-outcome="catalog"> (by asking the "identity"
component). If not display the
<render view-id="/showcatalog.xhtml" />
login page. Which page is the
</rule>
login page? It is specified
</navigation> here:
</page>
</pages>
After logging in, it will return you to the originally requested page (the confirm
page). How does it do that? If you check the components.xml file, you'll see:
Chapter 8 Using JBoss Seam 245
<components ...>
...
<event type="org.jboss.seam.security.notLoggedIn"> Redirect
<action execute="#{redirect.captureCurrentView}" />
</event>
<event type="org.jboss.seam.security.loginSuccessful">
<action execute="#{redirect.returnToCapturedView}" /> 5: Capture the
</event> view id for
</components> later restore
This will return
to the original
view id
Implementing logout
The logout link is already defined:
<html ...>
<body>
<h:form>
<h:dataTable border="1" value="#{productList}" var="p">
...
</h:dataTable>
<h:commandLink value="Login" action="login"></h:commandLink>
<h:commandLink value="Logout" action="#{showCatalog.logout}"></h:commandLink>
</h:form>
</body>
</html>
For it to work, define the logout method in the ShowCatalog class:
246 Chapter 8 Using JBoss Seam
@Name("showCatalog")
@Scope(ScopeType.EVENT)
public class ShowCatalog {
@In
private CatalogService catalogService;
@Out(required = false)
@DataModelSelection
private Product product;
@DataModel
private List<Product> productList;
@Factory("productList")
public void loadProducts() {
productList = getProducts();
}
public List<Product> getProducts() {
return catalogService.getProducts();
}
public String showDetails() {
return "details";
} This will invalidate the session
public String logout() {
Identity.instance().logout();
return null;
} Do not change the view id
}
Run it. Try to logout and then click a product link. It will trigger an error:
To fix this problem, you can force a browser redirect. To do that modify the
ShowCatalog class to return an outcome:
@Name("showCatalog")
@Scope(ScopeType.EVENT)
public class ShowCatalog {
@In
private CatalogService catalogService;
@Out(required = false)
@DataModelSelection
private Product product;
Chapter 8 Using JBoss Seam 247
@DataModel
private List<Product> productList;
@Factory("productList")
public void loadProducts() {
productList = getProducts();
}
public List<Product> getProducts() {
return catalogService.getProducts();
}
public String showDetails() {
return "details";
}
public String logout() {
Identity.instance().logout();
return "loggedOut";
}
}
Define the navigation case:
<pages ...>
...
<page view-id="/showcatalog.xhtml">
<navigation>
<rule if-outcome="details">
<render view-id="/productdetails.xhtml" />
</rule>
<rule if-outcome="login">
<render view-id="/login.xhtml" />
</rule>
<rule if-outcome="loggedOut">
<redirect view-id="/showcatalog.xhtml" />
</rule>
</navigation>
</page>
</pages>
Run it again and it will work.
Redirect vs render
Let's take an experiment: Click a product link from the catalog page. Note that
the URL is not changed:
...
</rule>
<rule if-outcome="addedToCart">
<end-conversation/>
<redirect view-id="/showcart.xhtml" />
</rule>
</navigation>
</page>
<page view-id="/showcart.xhtml">
<navigation >
<rule if-outcome="checkout">
<redirect view-id="/confirm.xhtml" />
</rule>
<rule if-outcome="catalog">
<redirect view-id="/showcatalog.xhtml" />
</rule>
</navigation>
</page>
<page view-id="/confirm.xhtml" login-required="true">
<navigation >
<rule if-outcome="charged">
<redirect view-id="/thankyou.xhtml" />
</rule>
<rule if-outcome="catalog">
<redirect view-id="/showcatalog.xhtml" />
</rule>
</navigation>
</page>
...
</pages>
Run it and it should continue to work. Besides, the URL will change with each
click.
Summary
A Seam component is like a JSF managed bean except that it is more powerful.
The injection does it work before each method call and thus prevents the
problem of deserialization. It also supports outjection so that you can store
components. If a property is both injected and outjected, it is said to be bijected.
Seam components are defined using annotations so that you don't have to edit
configuration files. If you need to change their settings, you can do it in the
components.xml file.
Seam provides a very powerful scope: conversation scope. You define when to
start a conversation and when to end it. During this time period, all conversation
scoped components will remain available, across different requests.
You can use the @DataModel annotation to create a DataModel wrapping a List
and outject that DataModel as a component. Most likely you'll use a @Factory
annotation to mark a method as a factory method for the DataModel component
and load the List in the method. You can inject the selected object in the
DataModel using @DataModelSelection.
You define the navigation cases in the pages.xml file. In that file you can define
additional properties of the pages: for example, if a page requires a logged in
user or requires to start a new conversation.
Seam provides an "identity" component to keep track of the currently logged in
250 Chapter 8 Using JBoss Seam
user (if any). All you need is to provide a page to store the user input into that
"identity" component and a method in the "authenticator" component. By default,
event listeners have been set up to capture the original view id and to return to it
on a successful login.
To render the next page, you can choose between a plain render or a redirect.
Redirect will show the new URL in the browser and will work fine with the
Reload/Refresh button. To allow redirect, if one page needs to pass a
component to the next page, it should be in the conversation scope.
Download at WoweBook.com
251
Chapter 9
Chapter 9 Supporting Other Languages
252 Chapter 9 Supporting Other Languages
A sample application
Suppose that you have an application that displays the current date:
This is easy. Create a JBoss JSF project named MultiLang and enable
Facelets. Copy the JSF RI jar files into WEB-INF/lib. Create a showdate.xhtml
file in the WebContent folder:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html">
<head>
<title>Current date</title>
</head>
<body>
Today is: <h:outputText value="#{showDate.today}"/>.
</body>
</html>
Create a ShowDate class in the multilang package:
package multilang;
import java.util.Date;
<application>
<view-handler>com.sun.facelets.FaceletViewHandler</view-handler>
</application>
</faces-config>
Start the Tomcat instance and try to access http://localhost:
8080/MultiLang/showdate.jsf in the browser. It should work.
Supporting Chinese
Suppose that some of your users are Chinese. They would like to see the
application in Chinese when they run the application. To do that, create a file
msgs.properties (the filename is not really important) in the multilang package:
currentDate=Current date
todayIs=Today is:
To support Chinese, create another file msgs_zh.properties. "zh" represents
Chinese. Usually, people use the Big5 encoding to encode Chinese. However,
Java requires this file be in a special encoding called "escaped Unicode
encoding". For example, the Chinese for "Current date" consists of four Unicode
characters (see the diagram below). Their Unicode values (hexadecimal) are
also shown. The properties file should be written as:
當前日期
0x7576 0x65E5
0x524D 0x671F
currentDate=\u7576\u524d\u65e5\u671f
todayIs=...
Obviously, this is very difficult to do. Fortunately, the JBoss IDE tools includes a
properties file editor that allows you to enter any Unicode characters directly
including Chinese:
254 Chapter 9 Supporting Other Languages
As you probably don't know how to input Chinese, you can simply type some
random text pretending to be Chinese. To make use of the properties files,
modify showdate.xhtml:
<html ...>
<head>
<f:loadBundle basename="multilang.msgs" var="b" />
<title>Current date</title>
</head>
<body>
Today is: <h:outputText value="#{showDate.today}"/>.
</body>
</html> 1: Create this UI Load
Bundle component Attribute table for the request
Object name Object instance
b
UI View Root
... ...
... ...
2: Render
UI Load 4: Put the resource
Bundle bundle into an attribute in WEB-INF
the request scope. The
attribute name is "b" as classes
specified.
multilang
ShowDate.class
msgs.properties
3: Load multilang.msgs from the class path msgs_zh.properties
and assume an extension of .properties.
Create a table from the content. Such a table
is called a "resource bundle":
Key String
currentDate Current date
todayIs Today is:
b (resource bundle)
Key String
1: Evaluate "b" and get currentDate Current date
access to the resource
bundle
todayIs Today is:
2: JSF will call getCurrentDate() on "b". It will fail. Then
it will check if it is a resource bundle. If so, it will look up
<html ...> the key "currentDate" and return the string ("Current
<head> date").
<title><h:outputText value="#{b.currentDate}"/></title>
</head>
<body>
<h:outputText value="#{b.todayIs}"/>
<h:outputText value="#{showDate.today}"/>.
</body>
</html>
Look up the "todayIs"
key
Run it and it should continue to work. How to make the page use the Chinese
version of the resource bundle (msgs_zh.properties)? For example, in FireFox,
choose "Tools | Options | Advanced":
Click "Choose" and make sure that Chinese is listed as the first entry (most
preferred):
256 Chapter 9 Supporting Other Languages
When the browser sends a request, it will include this list in the request (see the
diagram below). After Tomcat receives it, it will let the view handler handle it
(here it's the Facelets engine but could have been the JSP engine). The view
handler will create the component tree as usual. Then it will get the most
preferred language ("zh" here) from the list and store it into the view root. Later,
the UI Load Bundle component will find that the language of the view root is
"zh", so it will load msgs_zh.properties instead:
...
languages: zh, en, ...
1: Send a request
Browser Tomcat
2: You handle it
5: Store zh into it
View handler as its language
4: Look, zh is the most
preferred language.
View
root 6: What's your language?
3: Create the
Oh it's zh. Load
component tree Load msgs_zh.properties.
bundle
There is still a minor twist: Actually, the view handler will not blindly store the
most preferred language into the view root. It will check if that language is
supported by your application (see the diagram below). For example, if the most
preferred language is en or zh, it is supported and will be stored into the view
root. However, if it is, say, de (German), then it is not supported and the default
language will be used (en here):
Chapter 9 Supporting Other Languages 257
default: en
supported: zh
2: Do you support zh? supported: fr
Yes. Use it.
2: Choose it
<faces-config ...>
<managed-bean>
<managed-bean-name>showDate</managed-bean-name>
<managed-bean-class>multilang.ShowDate</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
<application>
<view-handler>com.sun.facelets.FaceletViewHandler</view-handler>
<locale-config>
<default-locale>en</default-locale>
<supported-locale>zh</supported-locale>
</locale-config>
</application>
</faces-config> If you also supported,
say, fr, you would add
one more <supported-
locale> element
there:
Save the file, restart the Tomcat instance and then reload the page. You should
see the Chinese version:
If you can't see Chinese on your computer, make sure it has a font that
supports Chinese. For example, login as the Administrator, open the Control
Panel and choose "Regional Settings" and ensure that traditional Chinese
support is enabled.
You may be wondering why the UI Load Bundle component doesn't load the
msgs_en.properties file when the most preferred locale is en. To understand
how it works, first consider the case when the most preferred locale is zh. In
that case, it will load msgs_zh.properties and use msgs.properties as the parent
(see the diagram below). If a key is not found in the child, the child will look for it
in the parent:
Chapter 9 Supporting Other Languages 259
msgs.properties
Key String
currentDate Current date
todayIs Today is:
If a key is not found
(e.g., "foo"), look for it
in the parent.
msgs_zh.properties
Key String
currentDate 當前日期
todayIs 今日是:
That's why it is called a bundle. Now, consider the case when the most
preferred locale is en. In that case, it try to load msgs_en.properties but it is not
found (see the diagram below). Then you can consider it will use a non-existing
or empty msgs_en.properties as the child. Effectively only the parent will be
used:
msgs.properties
Key String
currentDate Current date
todayIs Today is:
parent
msgs_en.properties
NON-EXISTING
Anyway, now you are done. It is said that you have "internationalized" this page
(let it use a resource bundle) and "localized" it to Chinese (provide
msgs_zh.properties). If in the future you need to add support for say French,
you will not need to internationalize it again but just need to localize it to French
(provide msgs_fr.properties). As the word "internationalization" is very long,
sometimes people use "i18n" as its short form because there are 18 characters
between the starting "i" and the ending "n". Similarly, people use "l10n" as a
short form for "localization".
<html ...>
<head>
<f:loadBundle basename="multilang.msgs" var="b" />
<title><h:outputText value="#{b.currentDate}"/></title>
</head>
<body>
<h:outputText value="#{b.todayIs}"/>
<h:outputText value="#{showDate.today}">
<f:convertDateTime dateStyle="long"/>
</h:outputText>.
</body>
</html> This setting is not really
The date time converter will
use the locale stored in the required. It is set to long so
view root to format the Date that you can see Chinese
characters in the date
display.
Run it and it will work. Finally, the Change button should also be
internationalized and localized:
Chapter 9 Supporting Other Languages 263
Should be in Chinese
stop). To solve this problem, you could add a new entry to your properties files
for the full stop:
msgs.properties msgs_zh.properties
currentDate=Current date currentDate=當前日期
todayIs=Today is:
todayIs=今日是:
change=Change
fullStop=. change=變更
fullStop=。
Then change showdate.xhtml to:
<h:outputText value="#{b.todayIs}"/>
<h:outputText value="#{showDate.today}">
<f:convertDateTime dateStyle="long"/>
</h:outputText>
<h:outputText value="#{b.fullStop}"/>
This would work. But you are now breaking the sentence up into three parts:
<h:outputText value="#{b.todayIs}"/>
<h:outputText value="#{showDate.today}">
<f:convertDateTime dateStyle="long"/>
</h:outputText>.
<h:outputFormat value="#{b.todayIs}">
<f:param value="#{showDate.today}"/>
</h:outputFormat>
Format
renderer
UI Output
1: Read the value of the
value: "Today is: {0}." 0th parameter
value: Date
The full stop is working. However, the date display is no longer in the long style.
To fix it, modify the format pattern:
msgs.properties msgs_zh.properties
currentDate=Current date currentDate=當前日期
todayIs=Today is: {0, date, long}.
todayIs=今日是: {0, date, long}。
change=Change
change=變更
Now run it and it should work:
266 Chapter 9 Supporting Other Languages
BUG ALERT: Due to a bug in the JSF reference implementation, the Format
renderer is not using the locale of the view root and therefore it is displaying the
date in English.
Displaying a logo
Suppose that you'd like to display a logo on the showdate page. You have
created a logo (a GIF image) as shown below:
In addition, note the "4" in the logo. In Chinese, "4" doesn't mean "for" at all. In
fact, it is pronounced just like the word "death" in Chinese so people tend to
avoid it in names. So, you'd like to have a Chinese version of the logo:
To display the right version of the logo, save them into the WebContent folder
as logo_en.gif and logo_zh.gif respectively:
Chapter 9 Supporting Other Languages 267
<html ...>
<head>
<f:loadBundle basename="multilang.msgs" var="b" />
<title><h:outputText value="#{b.currentDate}"/></title>
</head>
<body>
<h:graphicImage value="logo_en.gif"></h:graphicImage><p/>
<h:outputFormat value="#{b.todayIs}">
<f:param value="#{showDate.today}"/>
</h:outputFormat>
<h:form>
<h:selectOneMenu value="#{showDate.locale}">
<f:selectItems value="#{showDate.locales}"/>
</h:selectOneMenu>
<h:commandButton action="#{showDate.changeLocale}"
value="#{b.change}"/>
</h:form>
</body>
</html>
3: Copy
UI Graphic
1: Create a UI Graphic
component ...
<img src="logo_en.gif">
2: Render an <img>
element
http://localhost:8080/MultiLang/showdate.jsf
Run it and it should display the English logo (regardless of the most preferred
locale):
The next step is to ask the view handler to use it if it exists. To do that, you need
to create your own view handler. Let's call create a MyViewHandler class in the
multilang package:
The Facelets view handler has
this method to get the most Extend the view handler
preferred locale from the provided by Facelets
request
To tell the JSF engine use your own view handler, modify faces-config.xml:
Chapter 9 Supporting Other Languages 271
1: Choose it
<message-bundle>multilang.MyApp</message-bundle>
</application>
...
</faces-config>
It is simply a global resource bundle used by the built-in components. To
localize it for, say, Chinese, all you need is to create MyApp_zh.properties, just
like you would any other resource bundle.
Summary
To internationalize a page, you can extract the strings into resource bundles,
one for each supported language and a default resource bundle to act as the
parent. To look up a string for a certain key in a resource bundle, use
<loadBundle> to load bundle as a request attribute and access it like using an
EL expression like #{attribute-name.key}.
To determine the locale to use, the view handler will check the most preferred
locale as specified in the HTTP request and check if it is supported by your
application. If yes, it will store it into the view root. If no, it will use the default
locale specified in your application.
If you'd like to let the user specify a particular locale to use, overriding the most
preferred locale set in the browser, you may want to store it into the session and
provide a view handler subclass to retrieve it later.
If you'd like to fill in various slots in a pattern before outputting it, you can use
<outputFormat> and specify the value for each slot using a UI Parameter
component (created by a <param>). The meaning of UI Parameter is entirely
determined by its parent component.
To display an image, use a <graphicImage> tag. Its "value" attribute is a relative
path from the current URL. To internationalize an image, just internationalize its
"value" attribute.
Essential JSF, Facelets & JBoss Seam 273
References
• Apache Software Foundation. Tomcat 6 Documentation.
http://tomcat.apache.org/tomcat-6.0-doc/index.html.
• Facelets developers. Facelets - JavaServer Faces View Definition
Framework. https://facelets.dev.java.net/nonav/docs/dev/docbook.html.
• RedHat JBoss. JBoss Seam Reference Documentation.
http://docs.jboss.com/seam/2.0.1.GA/reference/en/html/
• RedHat JBoss. RichFaces Developer Guide. http://www.jboss.org/file-
access/default/members/jbossrichfaces/freezone/docs/devguide/en/html/in
dex.html.
• Sun Microsystems, Inc. Expression Language Specification v2.1.
http://jcp.org/en/jsr/detail?id=245.
• Sun Microsystems, Inc. Java™ Servlet Specification v2.4.
http://jcp.org/en/jsr/detail?id=154.
• Sun Microsystems, Inc. JavaServer™ Faces Specification v1.2 Rev A.
http://www.jcp.org/en/jsr/detail?id=252.
• Sun Microsystems, Inc. JavaServer Pages™ Specification v2.1.
http://jcp.org/en/jsr/detail?id=245.
274 Essential JSF, Facelets & JBoss Seam
Alphabetical Index
Action listener...............................................................................................108
Execution..................................................................................................59
Immediate execution...............................................................................127
Action method..............................................................................................131
AJAX............................................................................................................164
How it works............................................................................................166
Refreshing multiple components.............................................................171
Using a panel to refresh optional elements.............................................169
Apache MyFaces............................................................................................10
Application....................................................................................................109
Attribute..............................................................................................................
In request scope......................................................................................122
In session scope.....................................................................................269
Big5..............................................................................................................253
Calendar component......................................................................................77
Cascading style sheet..................................................................................102
Client id..........................................................................................................71
Component........................................................................................................
Visibility...................................................................................................164
Component tree.............................................................................................38
Component tree.................................................................................................
Created by JSP engine..............................................................................38
Components.xml..........................................................................................216
Connection profile........................................................................................210
Context path...................................................................................................24
Conversation......................................................................................................
Beginning and ending..............................................................................234
Temporary...............................................................................................232
Conversation scope......................................................................................231
Vs session scope....................................................................................231
Conversion.........................................................................................................
Customizing error message......................................................................73
Process.....................................................................................................67
Conversion errors...........................................................................................70
Cookies........................................................................................................139
CSS..............................................................................................................102
Database connection....................................................................................217
DataModel....................................................................................................225
Date converter................................................................................................66
Debugging a JSF application..........................................................................40
Decode...........................................................................................................51
Eclipse..............................................................................................................8
EL expression................................................................................................36
Accessing a resource bundle..................................................................254
Essential JSF, Facelets & JBoss Seam 275
Managed beans..............................................................................................34
Accessing in Java code...........................................................................109
Hooking up................................................................................................80
Managed property.....................................................................................83
Scopes......................................................................................................32
Message Bundle.............................................................................................74
Modal panel..................................................................................................172
Mojarra...........................................................................................................11
Navigation..........................................................................................................
Remaining in the current view.................................................................146
Setting the next view id in Java...............................................................154
Navigation case..............................................................................................54
Redirect...................................................................................................247
Navigation rule...............................................................................................54
In Seam...................................................................................................224
More specific one is considered first.......................................................205
Using wildcard.........................................................................................201
Outcome........................................................................................................54
Setting.......................................................................................................59
Using null as the outcome.......................................................................146
Pages.xml....................................................................................................224
Phase listener...............................................................................................154
Phases...............................................................................................................
Affected by conversion errors....................................................................70
Affected by validation errors......................................................................95
Apply Request Values...............................................................................51
Invoke Application.....................................................................................54
Process Validations.............................................................................67, 94
Render Response.....................................................................................49
Restore View.............................................................................................50
Update Model Values................................................................................51
Properties file.................................................................................................73
Protected pages.................................................................................................
Enforcing login........................................................................................154
Returning to after logging in....................................................................156
Redirect..............................................................................................................
Vs render.................................................................................................247
Renderer........................................................................................................87
Request..........................................................................................................32
Resource bundle..........................................................................................254
Access in an EL expression....................................................................254
Parent.....................................................................................................258
Resource key.................................................................................................73
RichFaces......................................................................................................78
Skins.......................................................................................................176
Seam............................................................................................................208
Authenticator component........................................................................239
Event.......................................................................................................244
Essential JSF, Facelets & JBoss Seam 277
Identity component..................................................................................239
Protected pages......................................................................................244
Redirect...................................................................................................247
Removing the session.............................................................................245
Runtime...................................................................................................210
Seam component...............................................................................................
Bijecting...................................................................................................229
Conversation scope.................................................................................231
Defining...................................................................................................219
Injecting...................................................................................................219
Outjecting................................................................................................223
Seam components.......................................................................................216
SelectItem......................................................................................................64
Serializable...................................................................................................132
Session..........................................................................................................32
Attribute...................................................................................................269
Get rid of.................................................................................................138
How to maintain......................................................................................139
Id.............................................................................................................140
JSESSIONID...........................................................................................139
Removing................................................................................................159
Session scoped managed beans.......................................................................
Must implement Serializable....................................................................132
Must not refer to other managed beans..................................................135
Tag lib............................................................................................................25
Tag library......................................................................................................25
Tomcat.............................................................................................................9
Tomcat instance.................................................................................................
Creating.....................................................................................................20
Modules.....................................................................................................60
Transaction..................................................................................................217
Transient......................................................................................................134
UI Column component..................................................................................116
UI Command component...............................................................................46
Rendered as a button..............................................................................125
Rendered as a link..................................................................................125
UI Data component............................................................................................
Client id...................................................................................................130
UI Form component.......................................................................................46
UI Graphic component.................................................................................267
UI Input component........................................................................................46
Rendered as a hidden field......................................................................137
Rendered as a password field.................................................................144
UI Load Bundle component..........................................................................254
UI Messages component................................................................................71
UI Output component.....................................................................................38
Using a format renderer..........................................................................264
UI Panel.............................................................................................................
278 Essential JSF, Facelets & JBoss Seam
Grid renderer...........................................................................................104
Group renderer........................................................................................104
UI Panel component.......................................................................................86
UI Parameter component.............................................................................264
UI Select One component..............................................................................63
Validating a combination of multiple input values.........................................108
Validation...........................................................................................................
Customizing the "required" message.......................................................77
Customizing error message......................................................................96
Localizing error messages......................................................................271
Marking input as required..........................................................................76
Process.....................................................................................................94
Skipping null input.....................................................................................97
Validator method.......................................................................................99
Validator.........................................................................................................94
Verbatim HTML code.....................................................................................38
View handler.................................................................................................154
Creating your own...................................................................................270
Facelets..................................................................................................187
Setting the locale.....................................................................................256
View root component......................................................................................38
Web.xml.............................................................................................................
Security constraint...................................................................................193
XHTML File..................................................................................................183
XHTML vs JSP.............................................................................................185
XML namespace..........................................................................................185
@AutoCreate...............................................................................................223
@DataModel................................................................................................227
@DataModelSelection..................................................................................228
@Factory......................................................................................................226
@In..............................................................................................................219
Specifying the component name.............................................................227
@Name........................................................................................................219
@Out............................................................................................................223
@Scope.......................................................................................................219
<a4j:commandButton> tag...........................................................................169
<a4j:commandLink> tag...............................................................................168
<a4j:support> tag.........................................................................................172
<actionListener> tag.....................................................................................108
<calendar> tag...............................................................................................79
<column> tag...............................................................................................116
<commandButton> tag...................................................................................45
<commandLink> tag.....................................................................................124
<convertDateTime> tag..................................................................................67
<dataTable> tag...........................................................................................116
<facet> tag...................................................................................................116
<form> tag......................................................................................................44
<graphicImage> tag.....................................................................................267
Essential JSF, Facelets & JBoss Seam 279
<inputHidden> tag........................................................................................137
<inputSecret> tag.........................................................................................144
<inputText> tag..............................................................................................45
<loadBundle> tag.........................................................................................254
<message> tag............................................................................................103
Setting the CSS class..............................................................................107
<messages> tag.............................................................................................71
Setting the CSS class..............................................................................102
<outputFormat> tag......................................................................................264
<outputText> tag............................................................................................28
<panelGrid> tag..............................................................................................86
<panelGroup> tag........................................................................................105
<param> tag.................................................................................................264
<rich:panel> tag............................................................................................176
<selectItems> tag...........................................................................................63
<selectOneMenu> tag....................................................................................62
<setPropertyActionListener> tag..................................................................127
<ui:component> tag......................................................................................189
<ui:composition> tag....................................................................................200
Providing multiple concrete parts............................................................204
<ui:define> tag..............................................................................................204
<ui:insert> tag...............................................................................................199
Specifying a name...................................................................................203
<validateDouble> tag......................................................................................97
<validateLength> tag......................................................................................97
<validateLongRange> tag..............................................................................95
<view> tag......................................................................................................27