How to make a redirection on page load in JSF 1.x

Romain Linsolas picture Romain Linsolas · Oct 27, 2010 · Viewed 131k times · Source

I have a web-application where the users can be sent directly to some specific pages (such as a page where he can view or edit an item). To achieve that, we provide a specific url. These urls are located outside the current web-application (i.e. they can be present in another web-application, or in an email).

The url looks like http://myserver/my-app/forward.jsf?action=XXX&param=YYY, where:

  • action represents the page where the user will be redirected. You can consider this as the from-outcome of any JSF action in navigation-case in faces-config.xml.
  • actionParam is a parameter for the previous action (generally an item ID)

So for example, I can have these kind of urls:

  • http://myserver/my-app/forward.jsf?action=viewItem&actionParam=1234
  • http://myserver/my-app/forward.jsf?action=editItem&actionParam=1234

Of course, I have a Java class (bean) that will check some security constraints (i.e. is the user allowed to see / edit the corresponding item?) and then redirect the user to the correct page (such as edit.xhtml, view.xhtml or access-denied.xhtml).


Current implementation

Currently, we have a basic way to accomplish the forward. When the user clicks on the link, the following XHTML page is called:

<html>
    <body id="forwardForm">
        <h:inputHidden id="myAction" binding="#{forwardBean.hiddenAction}"/>
        <h:inputHidden id="myParam" binding="#{forwardBean.hiddenActionParam}"/>
        <h:commandButton id="forwardBtn" actionListener="#{forwardBean.doForward}" style="display: none;"/>
    </body>
    <script type="text/javascript">
        document.getElementById('forwardForm:forwardBtn').click();
    </script>
</html>

As you can see, I bind two <h:inputHidden> components in my Java bean. They will be used to store the value of both action and actionParam request parameter (using FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("actiontParam");). I also provide the doForward method that which will be called immediately when the page is rendered, which will redirect (again) the user to the real page. The method is:

public void doForward(ActionEvent evt) {
    FacesContext facesContext = FacesContext.getCurrentInstance();
    String redirect = // define the navigation rule that must be used in order to redirect the user to the adequate page...
    NavigationHandler myNav = facesContext.getApplication().getNavigationHandler();
    myNav.handleNavigation(facesContext, null, redirect);
}

This solution is working, but I have two problems with that:

  • I don't like the way it is implemented. I'm sure that I can have something simplier (using a Servlet?).
  • This solution is using Javascript, and I must not use Javascript (as this forward may be used by old Blackberry users, where the Javascript is not supported).

So my question is how to refactor this redirection / forward feature?

Technical information

Java 1.6, JSF 1.2, Facelets, Richfaces

Answer

BalusC picture BalusC · Nov 4, 2010

Set the GET query parameters as managed properties in faces-config.xml so that you don't need to gather them manually:

<managed-bean>
    <managed-bean-name>forward</managed-bean-name>
    <managed-bean-class>com.example.ForwardBean</managed-bean-class>
    <managed-bean-scope>request</managed-bean-scope>
    <managed-property>
        <property-name>action</property-name>
        <value>#{param.action}</value>
    </managed-property>
    <managed-property>
        <property-name>actionParam</property-name>
        <value>#{param.actionParam}</value>
    </managed-property>
</managed-bean>

This way the request forward.jsf?action=outcome1&actionParam=123 will let JSF set the action and actionParam parameters as action and actionParam properties of the ForwardBean.

Create a small view forward.xhtml (so small that it fits in default response buffer (often 2KB) so that it can be resetted by the navigationhandler, otherwise you've to increase the response buffer in the servletcontainer's configuration), which invokes a bean method on beforePhase of the f:view:

<!DOCTYPE html>
<html xmlns:f="http://java.sun.com/jsf/core">
    <f:view beforePhase="#{forward.navigate}" />
</html>

The ForwardBean can look like this:

public class ForwardBean {
    private String action;
    private String actionParam;

    public void navigate(PhaseEvent event) {
        FacesContext facesContext = FacesContext.getCurrentInstance();
        String outcome = action; // Do your thing?
        facesContext.getApplication().getNavigationHandler().handleNavigation(facesContext, null, outcome);
    }

    // Add/generate the usual boilerplate.
}

The navigation-rule speaks for itself (note the <redirect /> entries which would do ExternalContext#redirect() instead of ExternalContext#dispatch() under the covers):

<navigation-rule>
    <navigation-case>
        <from-outcome>outcome1</from-outcome>
        <to-view-id>/outcome1.xhtml</to-view-id>
        <redirect />
    </navigation-case>
    <navigation-case>
        <from-outcome>outcome2</from-outcome>
        <to-view-id>/outcome2.xhtml</to-view-id>
        <redirect />
    </navigation-case>
</navigation-rule>

An alternative is to use forward.xhtml as

<!DOCTYPE html>
<html>#{forward}</html>

and update the navigate() method to be invoked on @PostConstruct (which will be invoked after bean's construction and all managed property setting):

@PostConstruct
public void navigate() {
    // ...
}    

It has the same effect, however the view side is not really self-documenting. All it basically does is printing ForwardBean#toString() (and hereby implicitly constructing the bean if not present yet).


Note for the JSF2 users, there is a cleaner way of passing parameters with <f:viewParam> and more robust way of handling the redirect/navigation by <f:event type="preRenderView">. See also among others: