Chapter 12. Bookmarkable Web Pages

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 bookmarkable individual person information edit page in the integration example project

Figure 12.1. The bookmarkable individual person information edit page in the integration example project

Using Page Parameters

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 Java-Centric Approach

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.

Obtaining Query Parameters in an HTTP GET Request

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;

Load Data for the Page

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?

The @Factory Method

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 @Create Method

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 ();
    }
  }

  // ... ...
}

Further Processing from the Bookmarked Page

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.224.60.227