Saturday, February 4, 2012

Forward vs Redirect

http://alltiers.com/pages/jsf/jsfRedirect.jsf

While navigating to another page, the default behaviour of the JSF Controller Servlet, is to use page forwarding. Unfortunately this means that a user will be unable to bookmark a page as the browser will always display the previous pages URL in its address bar.

JSF does support the notion of a page redirect that is implemented in the FacesConfig.xml as follows:

        /pages/country.xhtml                    add-success            /pages/state.xhtml                             

This means that a must be specified for every navigation case that you want to be re-directed.

Under the above navigation rule, when the user presses the Add button the browser will display /pages/state.jsf in the address bar. Without the redirect being specified, the browser would display /pages/country.jsf.

Defining a navigation case as redirect causes JSF to send a redirect response that asks the browser to request the required new view. This works fine as long as the validation is contained by JSF. If you have additional validation ( for example that there must be at least one State per Country ) that is evaluated in your code, then the redirect will cause:

  • Your coded messages to be lost
  • The potential loss of the users inputted Data as the page is re-directed to itself causing a new View of your pre-saved Data

Thus, the disadvantages of a Redirect are as follows:

  • Redirect is a two step process, where the web application instructs the browser to fetch a second URL, which differs from the original. Thus, a redirect has a slight performance penalty over forward.
  • Managed Beans placed in the original request scope are not available to the second request
  • The Redirect must be specified for every Navigation Case.
  • Possible Loss of Messages and or data

To solve this we have implemented a ReDirect Listener that handles the redirect for us. Basically each navigation will cause a redirect unless error messages exist. The problem with this approach is that it assumes:

  • All pages will be re-directed
  • A page will not be re-directed if it contains Error Messages.

For us, the advantages of being able to bookmark every page and not having to specify for every navigation case far exceed these minor inconveniences:

FacesConfig.xml will not specify redirect and will contain the definition of the ReDirect Listener

        /pages/country.xhtml                    add-success            /pages/state.xhtml                      com.alltiers.arch.client.jsf.listeners.ARedirectListener        

AReDirectListener.java

public class ARedirectListener implements PhaseListener {       // Logger     private static final Log log = LogFactory.getLog(ARedirectListener.class);       private static final String POST = "POST";       public ARedirectListener() {     }       public PhaseId getPhaseId() {         return PhaseId.ANY_PHASE;     }       /**      * ReDirect to the next page only if there are no Error Messages.     */     public void beforePhase(PhaseEvent _event) {           // We do this only for the RENDER_RESPONSE Phase         if (_event.getPhaseId() != PhaseId.RENDER_RESPONSE) {             return;         }           // Get the Current Request         FacesContext facesContext = _event.getFacesContext();         HttpServletRequest servletRequest =                            (HttpServletRequest) facesContext.getExternalContext().getRequest();           // If POST Method         if (POST.equals(servletRequest.getMethod())) {               // Get the next View ID and URL             String nextViewID = facesContext.getViewRoot().getViewId();             String nextViewURL =                    facesContext.getApplication().getViewHandler().getActionURL(facesContext,                                                                               nextViewID);               // re-direct to the Next URL if Messages don't Exist             try {                 // If the Form does not have Messages re-direct                 // Else use Default Navigation as defined in Faces Config                 // We-Default to re-direct so the URL will represent the Page they are on                 // and so the user can properly refresh their page                 // We do this because during a re-direct the Messages are Lost                 // and any user input is refreshed from what is currently in the model                 if (!AFacesUtility.isMessagesRendered(facesContext)) {                         log.debug("Redirecting to " + nextViewURL);                         facesContext.getExternalContext().redirect(nextViewURL);                 } else {                         log.info("Messages Exist - Staying on the Same Page");                 }             } catch (IOException ex) {                  log.debug("beforePhase() -- Error re-directing to " +                             nextViewURL + " and Id = " +       nextViewID, ex);                  AErrorHandler.displayPhaseListenerException(facesContext, ex);             }         }      } 

No comments: