Oracle ADF Faces - Communicating Between Pages: processScope
Overview

One of the most common questions about application development in JavaServer Faces (JSF) is how to communicate values between pages. For example, how do I pick one row out of a table, and show it on a second page to start editing it? How do I take search criteria entered on one page, and show the results on a second? Both of these (and many other common web application scenarios) require some mechanism to pass a value from one page to another. The two common solutions in JSF have been storing values on the request or on the session. Both can be made to work, but both have major limitations. ADF Faces introduces a new "processScope" that seeks to offer the best of both. (This support is built entirely off of existing, specified hooks in the Faces specification, and is not based on proprietary extensions. If there is interest from the rest of the Faces expert group, we would be very interested in contributing this back into the specification.)

When using a request-scope value, an action listener might take the ID of that table row, or the collection of search results, and place that object into request scope, either directly:

      FacesContext context = FacesContext.getCurrentInstance();
      context.getExternalContext().getRequestMap().put("search", criteria);

... or indirectly, by storin g it as a property of a request-scoped < managed-bean > . This works, but has some significant drawbacks:

  • The < navigation-case > used for page navigation can't specify < redirect/ > , because that would lead to a client-side redirect, which would mean the second page gets rendered on a new request. < redirect/ > is very useful for supporting bookmarking and better Back button support.
  • Even if you don't redirect, the second page still has the problem of making sure that this request-scoped value is still available for its own purposes when it posts back.

To avoid these problems, developers might use session-scoped variables instead. This fixes both of those problems, but adds new ones:

  • A single user cannot have two windows open simultaneously; session-scoped variables are global to the user. So, for instance, a user could not work with two different search results simultaneously.
  • Back button support is highly limited, since navigating back can't magically restore the session to its old state.

Finally, both session- and request-scoped parameters make bookmark support completely hopeless; similarly, they make it very difficult if you need to support emailing a link to a page. The URL does not and cannot contain enough information on its own to show a target page.

ADF Faces offers a new scope - "processScope" - that aims to solve all of these problems. It's a very new and experimental feature, and we're interested in feedback on how well it addresses the problem (it's not a panacea - some limitations are described below).

processScope

In addition to the standard JSF scopes - applicationScope, sessionScope, and requestScope - ADF Faces adds a new scope, processScope. Values added to this scope will automatically continue to be available as the user continues navigating from one page to another. This is true even if you use < redirect/ > . But unlike session scope, these values are only visible in the current "process". If the user opens up a new window and starts navigating, that series of windows will have their own process, and values stored in each window will remain independent. And clicking the Back button will automatically reset the process scope to its original state.

From the JSF EL, it looks just like any other scope:

     <h:outputText value="#{processScope.someKey}"/>
Note: As you can see in this example, "processScope" is supported for all JSF components, not just ADF Faces components.

From Java code, you can access the process scope as a java.util.Map off of the AdfFacesContext API. (Despite its name, this class does not extend FacesContext , but it is a similar idea.)

import oracle.adf.view.faces.context.AdfFacesContext;

public class BackingBean
{
  public String myAction()
  {
    Object someValue = ...;

    AdfFacesContext afContext = AdfFacesContext.getCurrentInstance();
    afContext.getProcessScope().put("someKey", someValue);

    return "myOutcome";
  } 
}

Example:

Let's start with an < h:dataTable > showing some data:

            <h:dataTable var="employee" value="#{....}">
              ...
              <h:column>
                <h:outputText value="#{employee.name}"/>
              <h:column>
              <h:column>
                <h:outputText value="#{employee.id}"/>
              <h:column>
            </h:dataTable>

Now, we want to show more information about that employee on another detail page. We'll add a commandButton, and tie it to a "showEmployee" action in our backing bean:

            <h:dataTable var="employee" value="#{....}">
              ...
              <h:column>
                <h:outputText value="#{employee.name}"/>
              <h:column>
              <h:column>
                <h:outputText value="#{employee.id}"/>
              <h:column>
              <h:column>
                <h:commandButton value="Show more"
                                 action="#{backingBean.showEmployee}"/>
              <h:column>
            </h:dataTable>

Now all we've got to do is write the code for showEmployee() . First, we'll find the current employee, and then we'll put it onto the process scope.

import javax.faces.context.FacesContext;
import oracle.adf.view.faces.context.AdfFacesContext;

public class BackingBean
{
  public String showEmployee()
  {
    // Find the current employee.  I'll just look on the VariableResolver.
    // (A lot of code out on the web manually creates a ValueBinding
    // for "#{employee}" and executes it - this is a much simpler approach!
    FacesContext context = FacesContext.getCurrentInstance();
    Employee emp = (Employee)
       context.getVariableResolver().resolveVariable(context, "employee");
    if (emp == null)
      return "error";
   
    AdfFacesContext afContext = AdfFacesContext.getCurrentInstance();
    afContext.getProcessScope().put("detailEmployee", emp);

    // Navigate to whatever page handles the "showEmployee" outcome
    return "showEmployee";
  }
}

If you owned the code for "Employee", you might consider moving showEmployee() directly onto Employee , in which case the code would simply be:

import oracle.adf.view.faces.context.AdfFacesContext;

public class Employee
{
  ...
  public String showEmployee()
  {
    AdfFacesContext afContext = AdfFacesContext.getCurrentInstance();
    // No need to find the employee - it's "this"
    afContext.getProcessScope().put("detailEmployee", this);

    // Navigate to whatever page handles the "showEmployee" outcome
    return "showEmployee";
  }
}
... but as you'll see below, there's an even easier way.

Now, on our detail page, we can just refer to the "processScope.detailEmployee" object:

  <h:panelGrid columns="2">
    <h:outputText value="Name:"/>
    <h:outputText value="#{processScope.detailEmployee.name}"/>

    <h:outputText value="Employee ID:"/>
    <h:outputText value="#{processScope.detailEmployee.id}"/>

    <h:outputText value="Salary"/>
    <h:outputText value="#{processScope.detailEmployee.salary}">
      <f:convertNumber type="currency"/>
    <h:outputText>
  </h:panelGrid>

That's all there is to it. The detail page does need to know where to look for the incoming value. The "detailEmployee" object also persists automatically at processScope if there were a "Show Even More Details" button on this detail page.

Limitations of processScope

Before moving on, there are, however, a number of limitations of processScope.

First, since processScope is not part of the standard JSF specification, a couple of the niceties of standard scopes can't be supported:

  • EL expressions do not automatically look into processScope; if you wish to locate a process-scoped value, you must include "processScope." (For instance, in the previous example, we couldn't write "#{employeeDetail}" - we had to write "#{processScope.employeeDetail}".)
  • "process" cannot be used as a < managed-bean-scope > . (But the < value > of a < managed-property > can refer to process-scoped values.)

Second, because the original and detail pages have to agree on the name of the process-scoped variable, they a re more tightly coupled than is ideal.

Finally, processScope never empties itself; the only way to clear processScope is to manually force it to clear:

     AdfFacesContext afContext = AdfFacesContext.getCurrentInstance();
     afContext.getProcessScope().clear();

af:setActionListener

This code is easy, but to make coding even simpler, we provide a new ActionListener tag that lets you code this style without writing any Java code. The < af:setActionListener > tag has two properties, "from" and "to", and simply takes a value from the "from" attribute and puts it where "to" says to put it. Let's recode the last example with this tag:

            <h:dataTable var="employee" value="#{....}">
              ...
              <h:column>
                <h:outputText value="#{employee.name}"/>
              <h:column>
              <h:column>
                <h:outputText value="#{employee.id}"/>
              <h:column>
              <h:column>
                <h:commandButton value="Show more" action="showEmployee">
                  <af:setActionListener from="#{employee}"
                                        to="#{processScope.employeeDetail}"/>
                </h:commandButton>
              <h:column>
            </h:dataTable>

And that's it! No code is required in your backing bean at all to implement this pattern. Let's walk through how this page works when the button is clicked:

  1. An ActionEvent fires on the commandButton. Since it's inside a table row, the "employee" EL variable is pointing at the current row.
  2. ActionListeners execute before any "action" excute, so < af:setActionListener > executes.
  3. < af:setActionListener > ret rieves the employee using the "#{employee}" EL expression.
  4. < af:setActionListener > stores the employee into process scope with the "#{processScope.employeeDetail}" EL expression.
  5. Finally, the "action" executes with a static outcome - always show the employee. No backing bean is needed.

Some may point out that this tag amounts to putting code in the UI, and that is actually quite true. It is a matter of personal taste whether this style of coding is acceptable or not, but used sparingly, it can greatly simplify reading and understanding page logic. We would never recommend using < af:setActionListener > to write values into a true model object. For example, you could write:

            <h:dataTable var="employee" value="#{....}">
              ...
              <h:column>
                <h:outputText value="#{employee.name}"/>
              <h:column>
              <h:column>
                <h:outputText value="#{employee.id}"/>
              <h:column>
              <h:column>
                <h:commandButton value="Give Raise">
                  <af:setActionListener from="#{employee.salary + 500}"
                                        to="#{employee.salary}"/>
                </h:commandButton>
              <h:column>
            </h:dataTable>
... but this style of coding mixes application logic directly into your page. This is all well and good for quick prototyping, but is very dangerous for writing production code that has to be maintained.

Bookmarking support

The current implementation of processScope adds a single query parameter to your URL, for example, "?_afPfm=4". This token points into internal structures stored by ADF Faces at session scope. The strategy allows processScope to store objects of any type, but does nothi ng to help with bookmarking (the tokens are not persistent across requests or users). In future versions of ADF Faces, we plan to support an augmented strategy that will detect when processScope contains nothing but primitive objects - such as strings, java.lang.Integers - and automatically store these values directly on the URL. This can directly and transparently enable bookmarkability (especially if you use < redirect/ > in < navigation-case > ) for web developers using processScope with that restriction.

Copyright © 2003-2006, Oracle Corporation. All Rights Reserved.

E-mail this page
Printer View Printer View
Oracle Is The Information Company About Oracle | Oracle RSS Feeds | Careers | Contact Us | Site Maps | Legal Notices | Terms of Use | Privacy