One of the loudest criticisms of JSF (and other component-based web frameworks) is its reliance on HTTP POST
requests. JSF uses HTTP POST
to match user actions (e.g., button clicks) with UI event-handler methods on the server side (i.e., in Seam stateful session beans). It also uses hidden fields in the HTTP POST
requests to keep track of the user’s conversational state.
In an HTTP POST
request, the URL does not contain the complete query information about the request. It is impossible to bookmark a web page dynamically generated from an HTTP POST
. However, in many web applications, it is highly desirable to have bookmarkable web pages (a.k.a. RESTful URLs—REST stands for Representational State Transfer). For instance, for an e-commerce web site, you probably want to display information via URLs such as http://mysite.com/product.seam?pid=123
; for a content site, you probably want to display articles via URLs such as http://mysite.com/article.seam?aid=123
. The chief benefit of the bookmarkable URLs is that they can be saved for later access and emailed/messaged (i.e., they can be bookmarks).
In plain JSF, bookmarkable pages are somewhat difficult to construct: When the page is loaded from an HTTP GET
, it is cumbersome to pass request parameters to backing beans and then automatically start bean method to process the parameters and load page data. However, with Seam, the barrier is easy to overcome. In this chapter, we discuss two approaches: using Seam page parameters and using request parameter injection with component lifecycle methods.
The example application is in the integration
project in the source code bundle. It works like this: After people enter their names and messages in the hello.seam
page, you can load any individual’s personal details and comments via the http://localhost:8080/integration/person.seam?pid=n
URL, where n
is the unique ID of that individual. You can then make changes to any of the details and submit them back to the database (see Figure 12.1., “The bookmarkable individual person information edit page in the integration example project”).???
The easiest way to pass HTTP GET
request parameters to back-end business components is to use Seam page parameters. Each Seam web page can have zero to several page parameters, which are HTTP request parameters bound to properties on back-end components.
Seam page parameters are defined in the pages.xml
file in the app.war/WEB-INF/
directory. You have already seen this file in Section 8.2., “Workspace Switcher”, where we used it to store the description of each web page for the conversation list. In the following example, when the person.xhtml
page is loaded, the HTTP GET
request parameter pid
is converted to a Long
value and bound to the #{manager.pid}
property. Notice that we can use JSF EL and the converter here, although the pages.xml
file is not a JSF web page; it is the power of Seam’s expanded use of JSF EL.
<pages> <page view-id="/person.xhtml"> <param name="pid" value="#{manager.pid}" converterId="javax.faces.Long"/> </page> </pages>
So when you load a URL such as person.seam?pid=3
, Seam automatically invokes the ManagerAction.setPid(3)
method. In the setter method, we initialize the person
object and outject it.???
@Stateful @Name("manager") public class ManagerAction implements Manager { @In (required=false) @Out (required=false) private Person person; @PersistenceContext (type=EXTENDED) private EntityManager em; Long pid; public void setPid (Long pid) { this.pid = pid; if (pid != null) { person = (Person) em.find(Person.class, pid); } else { person = new Person (); } } public Long getPid () { return pid; } ... ... }
Using similar techniques, you can have multiple page parameters binding to the same or different back-end components on the same page. The person.xhtml
page displays the editing form with the outjected person
component.
<s:validateAll> <table> <tr> <td>Your name:</td> <td> <s:decorate> <h:inputText value="#{person.name}"/> </s:decorate> </td> </tr> <tr> <td>Your age:</td> <td> <s:decorate> <h:inputText value="#{person.age}"/> </s:decorate> </td> </tr> <tr> <td>Email:</td> <td> <s:decorate> <h:inputText value="#{person.email}"/> </s:decorate> </td> </tr> <tr> <td>Comment:</td> <td> <s:decorate> <h:inputTextarea value="#{person.comment}"/> </s:decorate> </td> </tr> </table> </s:validateAll> <h:commandButton type="submit" value="Update" action="#{manager.update}"/>
When you click on the Update button, the person
object corresponding to the pid
is updated. Many readers might find this puzzling: When we first loaded the person.xhtml
page via HTTP GET
, we explicitly gave the pid
parameter. Why don’t we need to explicitly pass the pid
in an HTTP POST
request associated with the Update button submission (e.g., as a hidden field in the form or as a f:param
parameter for the Update button)? After all, the person
and manager
components are both in the default conversation scope (Section 7.1., “The Default Conversation Scope”); they have to be constructed anew when the form is submitted. So how does JSF know which person
you want to update? Well, as it turns out, the page parameter has a PAGE
scope (Section 5.5., “High Granularity Component Lifecycle”). When you submit the page, it always submits the same pid
parameter from which the page is originally loaded. This is a very useful and convenient feature.???
The Seam page parameter is an elegant solution for bookmarkable pages. You will see its application again in Chapter 13, The Seam CRUD Application Framework.
The page parameter is not the only solution for bookmarkable pages. For one thing, a lot of developers dislike putting application logic in XML files. The pages.xml
file can also get too verbose in some cases. For instance, you might have the same HTTP request parameter on multiple pages (e.g., editperson.seam?pid=x
, showperson.seam?pid=y
etc.) or have multiple HTTP request parameters for the same page. In either case, you then must repeat very similar page parameter definition in the pages.xml
file.
Furthermore, the page parameter does not work correctly if the page is loaded from a servlet, which is the case for some third party JSF component libraries. Those libraries use their own special servlets to do more processing/rendering of the page. For an example, see Section 15.3., “Use ICEfaces with Seam”.
To resolve those issues, Seam provides a mechanism for processing HTTP request parameters in a “pure Java” way. This is more involved than the page parameter approach, but the benefit is that at more points you can add your own custom logic. In this section, we show you how.
Our first challenge is to pass the HTTP GET
query parameter to the business component that provides contents and supports actions for the page. Seam provides a @RequestParameter
annotation to make this happen. The @RequestParameter
annotation is applied to the String
variable in a Seam component. When the component is accessed at runtime, the current HTTP request parameter matching the variable name is automatically injected into the variable. For instance, we could have the following code in the ManagerAction
stateful session bean to support URLs such as person.seam?pid=3
. Notice that the HTTP request parameter is a String
object, but the injected value is a Long
type. Seam converts the String
to a Long
during injection. Of course, you can inject a String
value and convert it yourself.
@Stateful @Name("manager") public class ManagerAction implements Manager { @RequestParameter Long pid; // ... ... }
Whenever a method (e.g., a UI event handler, a property accessor, or a component lifecycle method) inside the ManagerAction
class is accessed, Seam first injects the request parameter pid
into the field variable with the same name. If your request parameter and field variable have different names, you must use the value
argument in the annotation. For instance, the following code injects the pid
request parameter into the personId
field variable.
@RequestParameter (value="pid") Long personId;
Getting the request query parameter is only the first step. When the person.seam?pid=3
page is loaded, it has to also trigger Seam to actually retrieve the person’s information from the database. For instance, the person.xhtml
page simply displays data from the person
component. So how do we instantiate the person
component with the pid
parameter at the HTTP GET
?
As we discussed in Section 6.1.4., “Factory Methods”, we can use a factory method to initialize any Seam component. The factory method for the person
component is located in the ManagerAction
bean. Seam calls ManagerAction.findPerson()
when it instantiates the person
component. The factory method uses the injected pid
to retrieve the Person
object from the database.
@Stateful @Name("manager") public class ManagerAction implements Manager { @In (required=false) @Out (required=false) private Person person; @PersistenceContext (type=EXTENDED) private EntityManager em; @RequestParameter Long pid; ... ... @Factory("person") public void findPerson () { if (pid != null) { person = (Person) em.find(Person.class, pid); } else { person = new Person (); } } }
In summary, the whole process works like this: When the user loads the person.seam?pid=3
URL, the person.xhtml
page is processed and Seam finds it necessary to instantiate the person
component to display data on the page. Seam injects the pid
value into the ManagerAction
object and then calls the ManagerAction.findPerson()
factory method to build and outject the person
component. The page is then displayed with the person
component.
The person
component can be constructed with a factory method. But what if the page data comes from a business component? For instance, the page could display data from #{manager.person}
instead of #{person}
. In this case, we need to initialize the person
property in the manager
component when Seam instantiates the manager
component. According to Section 6.1.3., “Stateful Component Lifecycle”, we can do it via the @Create
lifecycle method in the ManagerAction
class.???
@Stateful @Name("manager") public class ManagerAction implements Manager { @RequestParameter Long pid; // No bijection annotations private Person person; @PersistenceContext(type=EXTENDED) private EntityManager em; public Person getPerson () {return person;} public void setPerson (Person person) { this.person = person; } @Create public String findPerson() { if (pid != null) { person = (Person) em.find(Person.class, pid); } else { person = new Person (); } } // ... ... }
Without the PAGE
scoped page parameter, we must include the HTTP request parameter in all subsequent requests. For instance, the person.xhtml
page loads the manager
and person
components only in the default conversation scope (see Section 7.1., “The Default Conversation Scope”), so the components expire when the page is fully rendered. When the user clicks on the Say Hello button to edit the person’s information, a new set of manager
and person
components must be constructed for the new conversation. Thus, the JSF POST
for the Say Hello button submission must also include the pid
parameter. The pid
is injected into the ManagerAction
class, which uses it to build the person
component before the event-handler method ManagerAction.sayHello()
is invoked. To do that, we use a hidden field in the form.
<h:form> <input type="hidden" name="pid" value="#{person.id}"/> <s:validateAll> ... ... </s:validateAll> <h:commandButton type="submit" value="Update" action="#{manager.update}"/> </h:form>
If you annotate the @Factory
or @Create
methods with the @Begin
annotation, you can start a long-running conversation from a bookmarked page. For instance, in an e-commerce web site, you can start a shopping cart conversation when the user loads a bookmarked product page with a productId
. The REST-loaded product
component stays available throughout the conversation until the user checks out or aborts the shopping session. There is no need to load the product
component again from the productId
as long as the conversation stays valid.???
Seam provides great REST support for JSF applications. This is one of the most compelling reasons to use Seam with JSF.
18.224.60.227