Friendly paths to access dynamic JSF content

JSF 1 is HTTP POST based and, as such, makes it hard to generate friendly/RESTful URLs that are meaningful to the user. Try to serve something like a content management system (CMS) using JSF and you immediately run into problems. However if you write a custom ViewHandler, you can translate the virtual url, such as /page/about/contactUs, into a physical page (view), e.g. /dynamicContent.xhtml.

All cases where the ViewHandler references the viewId (which is just the full filename of the page its executing/referencing), need to be mapped correctly so that JSF opens the page that exists, while the user sees the content specified by the URL that maps to a database for example.

Below is the view handler that wraps around the Facelets view handler to provide the described functionality.:
/**
 * View Handler that wraps around first Facelets then the default handler, to translate the virtual address of the content page to the physical xhtml
 * page that will serve it.
 * 
 * @author Chris Watts
 */
public class ParameterisedPathViewHandler extends ViewHandlerWrapper
{
   /** servletmapping for physical page */
   private static final String PAGE_URL = "/dynamicPage.jsf";
   /** physical page file name */
   private static final String PAGE_FILE_NAME = "/dynamicContent.xhtml";
   /** servletmapping prefix for served pages */
   private static final String PAGE_PREFIX = "/page";
   /** richfaces exclusion */
   private static final String A4J_PREFIX = "/page/a4j/";
   private ViewHandler wrappedHandler;

   /**
    * Constructor taking the previous view handler in the chain (called by JSF implementation).
    * @param defaultHandler previous handler in the chain
    */
   public ParameterisedPathViewHandler(ViewHandler defaultHandler)
   {
      //put facelets in the chain.
      //If using richfaces the context param org.ajax4jsf.VIEW_HANDLERS
      //should be set to: "com.sun.facelets.FaceletViewHandler,net.devgrok.jsf.ParameterisedPathViewHandler"
      //and the below code commented out.

      FaceletViewHandler faceletHandler = new FaceletViewHandler(defaultHandler);
      this.wrappedHandler = faceletHandler;
//      this.wrappedHandler = defaultHandler; // to use richfaces chaining
   }

   @Override
   protected ViewHandler getWrapped()
   {
      return wrappedHandler;
   }

   @Override
   public UIViewRoot createView(FacesContext context, String viewId)
   {
      return wrappedHandler.createView(context, resolveUrl(context, viewId));
   }

   private boolean isContentPage(FacesContext context, String viewId)
   {
      String servlet = context.getExternalContext().getRequestServletPath();
      if (servlet == null || servlet.length() == 0)
         return false;
      if (servlet.startsWith(A4J_PREFIX))
         return false;
      return servlet.startsWith(PAGE_PREFIX);
   }

   //Determine what physical page will serve the request.
   private String resolveUrl(FacesContext context, String viewId)
   {
      if (!isContentPage(context, viewId))
         // not content page so use original viewId
         return viewId;

      //store pageId in request map for later retrieval
      Map<String, Object> requestMap = context.getExternalContext().getRequestMap();
      String pageId;
      //pageId is the document to be served
      pageId = viewId.substring(1);
      requestMap.put("pageId", pageId);
      return PAGE_FILE_NAME;
   }

   //If is one of the dynamic content pages then actual url of xhtml file to handle it.
   @Override
   public String getActionURL(FacesContext context, String viewId)
   {
      if (isContentPage(context, viewId))
         return getDynamicPageUrl(context, viewId);
      else
         return wrappedHandler.getActionURL(context, viewId);
   }

   private String getDynamicPageUrl(FacesContext context, String viewId)
   {
      return context.getExternalContext().getRequestContextPath() + PAGE_URL;
   }

   @Override
   public UIViewRoot restoreView(FacesContext context, String viewId)
   {
      return wrappedHandler.restoreView(context, resolveUrl(context, viewId));
   }
}

Resources

Download the source containing the above class and example usage.

Comments