POST-REDIRECT-GET and JSF 2.0

by Ed Burns

Michael Jouravlev, in his influential August 2004 article Redirect After Post, described a problem that many web applications present. He described the problem as follows:

All interactive programs provide two basic functions: obtaining user input and displaying the results. Web applications implement this behavior using two HTTP methods: POST and GET respectively. This simple protocol gets broken when an application returns a web page in response to a POST request. Peculiarities of the POST method combined with idiosyncrasies of different browsers often lead to an unpleasant user experience and may produce an incorrect state of the server application.

To address the problem, Jouravlev described a technique that he called POST-REDIRECT-GET, or the PRG pattern for short. The rules of the pattern are as follows:

  • Never show pages in response to POST
  • Always load pages using GET
  • Navigate from POST to GET using REDIRECT

Previous versions of JavaServer Faces (JSF) technology violated the first of these rules by using POST for every page navigation. In navigating from one page to another in a JSF-enabled application, the JSF framework forwarded a POST request through the Servlet API's RequestDispatcher.forward( ) method. This caused a new Faces page to be rendered and returned to the browser in response to the postback request.

Indeed, most popular Java Servlet-based web frameworks, including Struts, use this approach for navigation. HTTP purists rightly point out that this approach violates the first rule in the PRG pattern. Not only did JSF violate the first rule, but until JavaServer Faces 2.0, it was very difficult to do it any other way. Thanks to a JSF contribution from the Seam team at JBoss, it is now much easier to do PRG with JSF.

This Tech Tip shows how to implement the PRG pattern in JSF 2.0. The content of the tip is an adaptation of a section on PRG and JSF 2.0 in my upcoming book, with Neil Griffin, JavaServer Faces 2.0: The Complete Reference.

A Non-PRG Example

Let's start by examining a simple JSF 2.0 application that handles user registration. In this first example, the application does not implement the PRG pattern. The initial page for the application is coded in file register.xhtml, as follows:

   <!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:h="http://java.sun.com/jsf/html"
         xmlns:f="http://java.sun.com/jsf/core">
   <h:head>
     <title>A Simple JavaServer Faces Registration Application</title>
   </h:head>
   <h:body>
     <h:form>
       <h2>JSF Registration App</h2>
       <h4>Registration Form</h4>
       <table>
         <tr>
           <td>First Name:</td>
           <td>
             <h:inputText label="First Name"
                          id="fname" value="#{userBean.firstName}"
                          required="true"/>
             <h:message for="fname" />
           </td>
         </tr>
         <tr>
           <td>Last Name:</td>
           <td>
             <h:inputText label="Last Name"
                          id="lname" value="#{userBean.lastName}"
                          required="true"/>
             <h:message for="lname" />
           </td>
           </tr>
   ... additional table rows not shown.
       </table>
       <p><h:commandButton value="Register" action="confirm" /></p>
     </h:form>
   </h:body>
   </html>

The page presents text fields for the user to enter a first name and a last name. It also displays a Register button. When the user presses the Register button, the JSF navigation rule system looks for a page within the application whose extension is the same as the current page and whose filename is confirm. If confirm.xhtml exists, JSF uses the navigation components in that file to navigate to the next page. Here is the confirm.xhtml file:

   <!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:h="http://java.sun.com/jsf/html"
         xmlns:f="http://java.sun.com/jsf/core">
   <h:head>
     <title>A Simple JavaServer Faces Registration Application</title>
   </h:head>
   <h:body>
     <h:form>
       <h2>JSF Registration App</h2>
       <h4>Registration Confirmation</h4>
       <table>
         <tr>
           <td>First Name:</td>
           <td>
             <h:outputText value="First Name" value="#{userBean.firstName}"
           </td>
         </tr>
         <tr>
           <td>Last Name:</td>
           <td>
             <h:outputText label="Last Name" value="#{userBean.lastName}"
           </td>
           </tr>
   ... additional table rows not shown.
       </table>
       <p><h:commandButton value="Edit" action="register" /></p>
       <p><h:commandButton value="Confirm" action="#{userBean.addConfirmedUser}" /></p>
     </h:form>
   </h:body>
   </html>

The confirm.xhtml file includes markup for an Edit button and a Confirm button. If the user clicks the Edit button, he or she is taken back to the register.xhtml page. If the user clicks the Confirm button, an action is invoked. The Confirm button specifies an action method, addConfirmedUser( ), that determines the outcome programmatically in the logic of the method. Here is the UserBean.java file, which contains the addConfirmedUser( ) method:

   package com.jsfcompref.model;

   ...  imports

   @ManagedBean
   @SessionScoped
   public class UserBean {

   ...  properties and methods

      public String addConfirmedUser() {
        boolean added = true; // actual application may fail to add user
        FacesMessage doneMessage = null;
        String outcome = null;
        if (added) {
            doneMessage = new FacesMessage("Successfully added new user");
            outcome = "done";
        } else {
            doneMessage = new FacesMessage("Failed to add new user");
            outcome = "register";
        }
          FacesContext.getCurrentInstance().addMessage(null, doneMessage);
          return outcome;
   }

For this simple case, addConfirmedUser( ) causes a message stating Successfully added new user to be displayed on the page and returns "done" as the outcome. When the addConfirmedUser( ) method returns "done" as the outcome, it takes the user to the done.xhtml page. This is an example of implicit navigation, a new feature in JSF 2.0. If no matching navigation case is found after checking all available rules, the navigation handler checks to see whether the action outcome corresponds to a view id. If a view matching the action outcome is found, an implicit navigation to the matching view occurs. Here the outcome is "done" and the matching view is done.xhtml, so the user is taken to the done.xhtml page. Implicit navigation saves you the effort of adding navigation rules in the faces-config.xml file.

Here is the done.xhtml 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"
         xmlns:h="http://java.sun.com/jsf/html"
         xmlns:f="http://java.sun.com/jsf/core">
   <h:head>
     <title>A Simple JavaServer Faces Registration Application</title>
   </h:head>
   <h:body>
     <h:form>
       <h2>JSF Registration App</h2>
       <h4>Registration Confirmation</h4>
       <h:messages />
       <table>
         <tr>
           <td>First Name:</td>
           <td>
             <h:outputText value="First Name" value="#{userBean.firstName}"
           </td>
         </tr>
       </table>
     </h:form>
   </h:body>
   </html>

POST-REDIRECT-GET Using View Parameters

View Parameters is a simple, declarative way to map incoming request parameter values to special components within the view. These mappings are specified using the new <f:viewParam> component, within the new <f:metadata> section of the view. Consider the following example:

   <f:metadata>
      <f:viewParam name="foo" value="#{bean.foo}"/>
   </f:metadata>

This example specifies that the value of the request parameter with the name "foo" is automatically assigned to the property at #{bean.foo}. So for a GET request as follows:

   page1.jspx?foo=bar

The value of the #{bean.foo} property will be set to bar when JSF starts processing the request.

View Parameters is similar in spirit to the page parameters feature found in JBoss Seam, but the JSF 2.0 incarnation of the feature is tightly integrated with the core JSF specification, making the feature easier to use and more powerful. Let’s look at another simple example.

   <!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:h="http://java.sun.com/jsf/html"
         xmlns:f="http://java.sun.com/jsf/core">
   <h:head>
     <title>A Simple JavaServer Faces 2.0 View</title>
   </h:head>
   <h:body>
     <h:form>

         <p>First Name:< <h:inputText id="fname"
              value="#{userBean.firstName}" /> </p>
       <p><h:commandButton value="submit"
    action="page02?faces-redirect=true&amp;includeViewParams=true" /></p>

     </h:form>
   </h:body>
   </html>

The <h:commandButton> element has action="page02?faces-redirect=true". In the Internet standard that defines URLs, the presence of a ? character indicates the remainder of the URL will be an & or &amp;-separated list of name=value pairs that should be submitted to the server along with the request for the URL. This is known as a query string. JSF borrows the meaning of the ? character here, and the meaning is exactly the same as in the Internet standard for URLs. There are two special query strings parameters recognized by JSF when it parses the outcome on the server side. The faces-redirectquery string tells the navigation system that this implicit navigation case must be treated as if it were a real <navigation-case> element that includes a <redirect/> element. The other special query string parameter, includeViewParams, tells the navigation handler to include the view parameters when performing the navigation. But what view parameters should be included? The view parameters to be included when performing the navigation are declared on the to-view-id page. In this case, we are using implicit navigation, so the implicit to-view-id is page02.xhtml, shown below.

   <!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:h="http://java.sun.com/jsf/html"
         xmlns:f="http://java.sun.com/jsf/core">
   <f:metadata>
        <f:viewParam name="fname> value="#userBean.firstName}"/>
       </f:metadata> 
   <h:head>
     <title>A Simple JavaServer Faces 2.0 View</title>
   </h:head>
   <h:body>
     <h:form>
         <p> Hello #{userBean.firstName}.</p>
     </h:form>
   </h:body>
   </html>

When the navigation handler encounters the matching navigation-case (implicit or explicit) that declares that view parameters should be included, it looks at the view parameters of the from-view-id and to-view-id pages and performs a match-and-copy algorithm to convey the view parameters to the new page. In this case, the navigation-case also requested a redirect.

Now let’s look at the registration example, this time implemented to do PRG with view parameters. The register.xhtml page looks like this:

   <!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:h="http://java.sun.com/jsf/html"
         xmlns:f="http://java.sun.com/jsf/core">
   <f:metadata>
       <f:viewParam name="fname" value="#{userBean.firstName}" />
       <f:viewParam name="lname" value="#{userBean.lastName}" />
       <f:viewParam name="sex" value="#{userBean.sex}" />
       <f:viewParam name="dob" value="#{userBean.dob}">
          <f:convertDateTime pattern="MM-dd-yy" />
       </f:viewParam>
       <f:viewParam name="mail" value="#{userBean.email}" />
       <f:viewParam name="sLevel" value="#{userBean.serviceLevel}" />
       </f:metadata>
   <h:head>
     <title>A Simple JavaServer Faces Registration Application</title>
   </h:head>
   <h:body>
     <h:form>
       <h2>JSF Registration App</h2>
       <h4>Registration Form</h4>
       <table>
         <tr>
           <td>First Name:</td>
           <td>
             <h:inputText label="First Name"
                          id="fname" value="#{userBean.firstName}"
                          required="true"/>
             <h:message for="fname" />
           </td>
         </tr>
       ... remaining table rows omitted, they are the same as the original
       </table>

       <!-- The query parameters on the action attribute cause JSF to do the
               POST REDIRECT GET pattern -->
       <p><h:commandButton value="Register"
          action="confirm?faces-redirect=true&amp;includeViewParams=true" /></p>
     </h:form>
   </h:body>
   </html>

In the previous View Parameters example, we stated that elements only appear on the to-view-id page. That is still true in this example, even though this is the first page the user sees. This particular application allows the user to go back and forth between the register.xhtml page and the confirm.xhtml page. Therefore, when the user is on the confirm.xhtml page, the to-view-id is the register.xhtml page and vice versa. Thus, <f:viewParams> is on both pages. You will need an <f:viewParam> for every input component on the from page that you wish to carry forward to the to page. Also note the <f:convertDateTime> within the <f:viewParam> for the dob property. This is necessary because, when the navigation is performed, the converter needs to be invoked to carry the value forward. If there is an explicit converter defined on the input field, then there must also be one on the corresponding <f:viewParam>. Finally, you can see the now familiar extra query parameters on the implicit navigation: confirm?faces-redirect=true&amp;includeViewParams=true.

Let’s examine the changes to the UserBean.java file.

   package com.jsfcompref.model;

   ... omits imports

   @ManagedBean
   @RequestScoped
   public class UserBean {

   ...  additional properties omitted

      public String addConfirmedUser() {
        boolean added = true; // actual application may fail to add user
        FacesMessage doneMessage = null;
        String outcome = null;
        if (added) {
            doneMessage = new FacesMessage("Successfully added new user");
            outcome = "done?faces-redirect=true&amp;includeViewParams=true";
        } else {
            doneMessage = new FacesMessage("Failed to add new user");
            outcome = "register?faces-redirect=true&amp;includeViewParams=true";
        }
          FacesContext.getCurrentInstance().addMessage(null, doneMessage);
          return outcome;
   }

The only changes to this code are to make the bean be request-scoped and to add the query parameters to the implicit navigation string. In this case, the includeViewParams=true parameter is added, causing whatever view parameters declared on the to-view-id page to be included in the navigation.

The confirm.xhtml page follows:

   <!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:h="http://java.sun.com/jsf/html"
         xmlns:f="http://java.sun.com/jsf/core">
   <f:metadata>
          <f:viewParam name="fname" value="#{userBean.firstName}" />
          <f:viewParam name="lname" value="#{userBean.lastName}" />
          <f:viewParam name="sex" value="#{userBean.sex}" />
          <f:viewParam name="dob" value="#{userBean.dob}">
             <f:convertDateTime pattern="MM-dd-yy" />
          </f:viewParam>
          <f:viewParam name="mail" value="#{userBean.email}" />
          <f:viewParam name="sLevel" value="#{userBean.serviceLevel}" />
       </f:metadata>
   <h:head>
     <title>A Simple JavaServer Faces Registration Application</title>
   </h:head>
   <h:body>
     <h:form>
       <h2>JSF Registration App</h2>
       <h4>Registration Confirmation</h4>
       <table>
         <tr>
           <td>First Name:</td>
           <td>
             <h:outputText value="First Name" value="#{userBean.firstName}"
           </td>
         </tr>

   ... additional rows omitted, they are the same as the original.
       </table>

       <p><h:commandButton value="Edit"
     action="register?faces-redirect=true&amp;includeViewParams=true" /></p>
      </h:form>

      <h:form>
	     <h:inputHidden value="#{userBean.firstName}" />
	     <h:inputHidden value="#{userBean.lastName}"/>
	     <h:inputHidden value="#{userBean.sex}" />
	     <h:inputHidden value="#{userBean.dob}">
	        <f:convertDateTime pattern="MM-dd-yy" />
	     </h:inputHidden>
	     <h:inputHidden value="#{userBean.email}" />
	     <h:inputHidden value="#{userBean.serviceLevel}" />

	     <p><h:commandButton value="Confirm"
          action="#{userBean.addConfirmedUser}" /></p>
            </h:form>

   </h:body>
   </html>

As in the register.xhtml page, we need the <f:metadata> section at the top of the page and the additional query parameters on the action string. What is new here are the additional <h:form> element and <h:inputHidden> elements, and the fact that the Confirm button has been moved into this new form. This is necessary because we need to carry forward to the next page as regular form submit parameters the values passed to this page as view parameters. But there are no regular input fields as there are on the register.xhtml page. Therefore, we use hidden fields to carry the values forward. Note also the continued necessity for the <f:convertDateTime> on the dob field.

Finally, here is the done.xhtml 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"
         xmlns:h="http://java.sun.com/jsf/html"
         xmlns:f="http://java.sun.com/jsf/core">
   <f:metadata>
             <f:viewParam name="fname" value="#{userBean.firstName}" />
             <f:viewParam name="lname" value="#{userBean.lastName}" />
             <f:viewParam name="sex" value="#{userBean.sex}" />
             <f:viewParam name="dob" value="#{userBean.dob}">
                <f:convertDateTime pattern="MM-dd-yy" />
             </f:viewParam>
             <f:viewParam name="email" value="#{userBean.email}" />
             <f:viewParam name="sLevel" value="#{userBean.serviceLevel}" />
       </f:metadata>
   <h:head>
     <title>A Simple JavaServer Faces Registration Application</title>
   </h:head>
   <h:body>
     <h:form>
       <h2>JSF Registration App</h2>
       <h4>Registration Confirmation</h4>
       <h:messages />
       <table>
         <tr>
           <td>First Name:</td>
           <td>
             <h:outputText value="First Name" value="#{userBean.firstName}"
           </td>
         </tr>
   ... additional rows omitted
       </table>
     </h:form>
   </h:body>
   </html>

The only difference between this done.xhtml and the original one is the now familiar <f:metadata> section.

Running the Sample Code

A sample application that implements PRG accompanies this tip. These instructions use the Maven 2 software project management tool to build the sample application and then deploy it in the GlassFish v3 Preview application server.

  1. If you haven't already done so, download a recent promoted build or nightly build of the GlassFish v3 Preview application server.
  2. If you haven't already done so, download Maven 2.
  3. Download the sample application package, PostRedirectGet.zip
  4. Extract the contents of the sample application package. You should see the folders prgViewParams, which contains the code for the PRG application that uses View Parameters.
  5. Create the WAR file for the PRG application by changing to the prgViewParams directory and entering the following Maven command:
       mvn install
    
    You should see the file prgViewParams.war in a newly-created target subdirectory under the prgViewParams directory.
  6. Start the GlassFish v3 Preview application server by entering the following command:
       <GFv3_inst>/bin/asadmin start-domain
    
    where <GFv3_inst> is where you installed the GlassFish v3 Preview application server.
  7. Deploy the sample application. One way to do that is to copy the prgViewParams.war file to the <GFv3inst>/domains/domain1/autodeploy directory.
  8. Execute the application by opening a browser and accessing the URL http://localhost:8080/prgViewParams. You should see the form shown in Figure 1.
    Registration Page
    Figure 1. Registration Page
     
  9. Enter information as appropriate into the form and click the Register button. You should see a page similar the one shown in Figure 2.
    Registering Through the Registration Page
    Figure 2. Registering Through the Registration Page
     
  10. Click the Confirm button. You should see a page similar the one shown in Figure 3.
    Confirmation Page
    Figure 3. Confirmation Page
     

Further Reading

For more information, see the following resources:

About the Author

Ed Burns is a senior staff engineer at Sun Microsystems. Ed has worked on a wide variety of client and server-side web technologies since 1994, including NCSA Mosaic, Mozilla, the Sun Java Plugin, Jakarta Tomcat and, most recently JavaServer Faces. Ed is currently the co-spec lead for JavaServer Faces. He is the coauthor of JavaServer Faces: The Complete Reference and the author of Secrets of the Rockstar Programmers. He is also the coauthor of the upcoming book JavaServer Faces 2.0: The Complete Reference. Read Ed Burns's blog.

Comments:

Good post!
Thank you!

Posted by fake rolex on November 17, 2009 at 10:49 AM PST #

This just made me miss developing in JSF more.

Posted by developerholic on November 28, 2009 at 01:34 PM PST #

Thanks for your useful info, I think it's a good topic.

Posted by air jordan shoes on December 03, 2009 at 10:42 AM PST #

Perfect! Thanks to Ed for those hidden technological breakthroughs of JSF regarding PRG. We had been using the traditional Non-PRG methods and we always get unfathomed, unseen, unavoidable and uncontrolled nullpointer exceptions and other VIEWID exceptions from JSF.

Posted by lava kafle on December 15, 2009 at 01:39 PM PST #

I have to say that this site is awesome and has kept me entertained on my time off from work. keep it up.

Posted by Christian Louboutin Shoes on December 27, 2009 at 07:31 PM PST #

Superb Explanation!!! Thanks!!!

Posted by Balasubramanian PS on December 29, 2009 at 08:58 PM PST #

Thanks for additional information. It will be very useful..............

Posted by Sachin Jadhavar on January 03, 2010 at 01:27 PM PST #

thank you for your article...that is a nice one...i often visit this blog and i enjoy reading all posts...

Posted by dizi izle on January 22, 2010 at 06:48 PM PST #

shows comparitive analysis in a lucid way

Posted by ankur ankur on June 08, 2010 at 04:23 AM PDT #

Very Good Explanation. Quite useful information.

Thanks

Posted by Justin on August 11, 2010 at 06:37 PM PDT #

Thanks for this article and good analysis.

Posted by Justin on August 11, 2010 at 06:39 PM PDT #

Thanks for the details explanation.

Posted by Rajesh on August 16, 2010 at 07:01 PM PDT #

It is really awesome. If I can just you a question. When you click the submit button with includeViewParams=true, all the form variables are pass along to the target page, matching correctly with those that have the f:viewParam, is that right? All these are post request, correct?

Posted by Thang Pham on November 03, 2010 at 01:00 PM PDT #

Happy to find such a wonderful article. Very useful for me. Thanks a lot!

Posted by สถานที่ท่องเที่ยว on November 06, 2010 at 05:50 AM PDT #

Post a Comment:
Comments are closed for this entry.
About

edort

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today