URL based Navigation into a JSF application – on PhaseListener and ViewHandler

2

Although most navigation takes place within the Java Server Faces application we are currently developing, some is from the outside. From an existing web application that handles the workflow for the end users, we will have to navigate to the JSF application – and not just to the homepage or start page, but deeplinking directly into specific pages more deep down in the JSF app. And before those pages can be displayed, data has to be prepared based on parameters passed from the external Web Application. This article will briefly show what we did to turn URL based navigation – GET or POST  with parameters – into JSF navigation.

In brief the solution is based on the following ideas....
:

  1. all navigation from outside the JSF application takes place to the same url, that will pass through the Faces Servlet. The url contains a parameter targetPage that contains a logical identifier for the page that should be navigated to; it may also contain additional parameters that help describe the context that should be set up for the target page. Note: the navigation can be initiated using a plain URL (http://url/faces/controller?targetPage=…&param1=….) and also through a POST request, by submitting an HTML form.
  2. a new PhaseListener is configured. This PhaseListener intercepts the requests to the controller url and redirects them. Before it actually executes the redirection, it may first set up the context by initializing business services and performing preliminary queries or derivations.

In a concrete example:

We can use a URL such as


http://host:8988/DirectNavigationTrial/faces/controller?targetPage=real3&param1=SomeParameterValue 

and type it directly in the browser’s location bar:

This will take us to whatever JSF page the PhaseListener associates with the label real3 that we set for the targetPage parameter. Note: there is no page, servlet or other resource that is called controller – it simply is a label that triggers the PhaseListener.

Alternatively – to show a more programmatic approach to navigation – we can create a simple HTML page that allows navigation to one out of four JSF pages, passing parameters into them:

 

The navigation is handled by the PhaseListener and takes to whatever JSF page is associated with the target page selected:

 

 

The Implementation

 

Let us check out the code behind this navigation handler. First of all, the HTML form:

    <form name="jsfNavigator" action="http://host:8988/DirectNavigationTrial/faces/controller" method="post">
      <table cellspacing="2" cellpadding="3" border="0" width="100%">
        <tr>
          <td>Target Page </td>
          <td>
            <select size="4" name="targetPage">
              <option value="real">Manager</option>
              <option value="real2">Inventory</option>
              <option value="real3">Order Trail</option>
              <option value="real4">MyAgenda</option>
            </select>
          </td>
        </tr>
        <tr>
          <td>Parameter 1 </td>
          <td><input type="text" name="param1"/></td>
        </tr>
        <tr>
          <td>Parameter 2</td>
          <td><input type="text" name="param2"/></td>
        </tr>
      </table>
      <input type="submit" value="Go"/>
    </form>

The action attribute of the form refers to the same URL as we used before in the browser’s location bar. 

Then the configuration of the PhaseListener in the faces-config.xml file:

  <lifecycle>
    <phase-listener>directnavigation.RedirectPhaseListener</phase-listener>
  </lifecycle>
 

The code of the RedirectPhaseListener class:

package directnavigation;

import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;

import javax.servlet.http.HttpServletRequest;

import nl.amis.jsf.navigation.NavigationBean;


public class RedirectPhaseListener implements PhaseListener {
    public RedirectPhaseListener() {
    }


    public PhaseId getPhaseId() {
        return PhaseId.RESTORE_VIEW;
    }

    public void afterPhase(PhaseEvent phaseEvent) {
    }

    public void beforePhase(PhaseEvent phaseEvent) {
        FacesContext ctx = phaseEvent.getFacesContext();
        HttpServletRequest request =
            (HttpServletRequest)ctx.getExternalContext().getRequest();
        ValueBinding vb =
            ctx.getApplication().createValueBinding("#{navigationBean}");
        NavigationBean nb = (NavigationBean)vb.getValue(ctx);
        String path = request.getPathInfo();
        if ("/controller".equals(path) ) {
            try {

                String newPageViewId=null;
                if ("real".equalsIgnoreCase(nb.getTarget()))
                  newPageViewId="/realpage.jspx";
                if ("real2".equalsIgnoreCase(nb.getTarget()))
                  newPageViewId="/realpage2.jspx";
                if ("real3".equalsIgnoreCase(nb.getTarget()))
                  newPageViewId="/otherrealpage3.jspx";

                UIViewRoot newPage =
                    ctx.getApplication().getViewHandler().createView(ctx,
                                                                     newPageViewId);
                ctx.setViewRoot(newPage);
                ctx.renderResponse();

            } catch (Exception e) {
                // TODO
            }
        }
    }

}

The RedirectPhaseListener class checks whether the URL ended with /controller. If so it is interpreted as a navigation request from the outside that will have passed in the targetPage parameter. This parameter is then read from the navigationBean’s target property. Its value is inspected and mapped to a real JSF page’s ViewId. This ViewId is then set as the new ViewRoot and the response is rendered. 

This class leverages one important managed bean: the navigationBean. This bean has three properties that contain the values of the targetPage, param1 and param2 request parameters that are either set in the URL or in the HTML Form. The NavigationBean is configured as managed bean like this:

  <managed-bean>
    <managed-bean-name>navigationBean</managed-bean-name>
    <managed-bean-class>nl.amis.jsf.navigation.NavigationBean</managed-bean-class>
    <managed-bean-scope>request</managed-bean-scope>
    <managed-property>
        <property-name>target</property-name>
        <value>#{param.targetPage}</value>
    </managed-property>
    <managed-property>
        <property-name>param1</property-name>
        <value>#{param.param1}</value>
    </managed-property>
    <managed-property>
        <property-name>param2</property-name>
        <value>#{param.param2}</value>
    </managed-property>
  </managed-bean>
 

The bean is in request scope – so it gets refreshed/created with each new request that is interested in it. It retrieves HTTP Request Parameters targetPage, param1 and param2 in its properties using EL expressions such as #{param.targetPage}.

Resources

Very useful was the information provided in the blog article POST-Redirect-GET pattern by BalusC (http://balusc.blogspot.com/2007/03/post-redirect-get-pattern.html ). Another useful source was this forum thread on the SUN Java Forums: http://forum.java.sun.com/thread.jspa?threadID=5273047 .

 

Share.

About Author

Lucas Jellema, active in IT (and with Oracle) since 1994. Oracle ACE Director for Fusion Middleware. Consultant, trainer and instructor on diverse areas including Oracle Database (SQL & PLSQL), Service Oriented Architecture, BPM, ADF, Java in various shapes and forms and many other things. Author of the Oracle Press book: Oracle SOA Suite 11g Handbook. Frequent presenter on conferences such as JavaOne, Oracle OpenWorld, ODTUG Kaleidoscope, Devoxx and OBUG. Presenter for Oracle University Celebrity specials.

2 Comments

  1. Hi Rene,

    In this case, all requests come to one url: …./faces/controller?param1=…. – the request is intercepted and redirected. For mapping different URLs I probably would have used a Filter, just like you suggest.

    best regards,
    Lucas

  2. Hello Lucas,
    very nice article.
    I’m using the same approach but i was wondering how you are mapping the URLs without extensions to the FacesServlet? Are you using a Filter?

    Regards,
    René