Facelets and legacy JSP

It's well known that Facelets is the far superior technology when it comes to authoring pages using JSF. By default, Facelets has no provisions to include content from JSP pages, or Servlets for that matter. Normally this really isn't needed. Facelets provides a better and clearer templating mechanism than what JSP has ever offered.

Facelets & JSP in one app

However, when migrating a large application you might have to run a while in mixed-mode, that is running with both Facelets and JSP pages in a single application. It ain't pretty, but it is explicitly supported. Ever wondered why you are forced to use prefix mapping for this? Well, the Facelets view handler delegates to the default view handler for createView, which transforms a view ID suffix like .jsf or .xhtml to a default one (e.g. .jsp), but leaves the suffix alone when using a prefix mapping like /faces/*. The Facelets viewhandler later decides to handle a request itself or delegate again to the default view handler, based on the suffix. When using suffix mapping, the first delegation had just caused this suffix to be replaced with the default suffix and the Facelets view handler thus can't make any distinction between a Facelets request and a JSP request anymore.

The above is thus why you have to use prefix mapping for Facelets pages if you want to use both Facelets and JSP in a single application.

JSP includes

Having Facelets and JSP pages in 1 application might be only half of the story. It's not unlikely that the existing JSP pages make use of included resources that are also JSP based, or maybe Servlet based. Rewriting these to full-fledged Facelets artifacts is technically the best option, but if you have to run in mixed-mode for a while, this means you have to maintain two versions of these includes. Obviously maintaining two separate versions is not ideal too.

One alternative, that I like to present here, is building a bridge component that executes the JSP or Servlet and writes its output to the standard JSF response writer. Thanks to Facelets and JSF in general, such a component is actually not that hard to build:

The component's source code:

public class JSPIncludeComponent extends UIComponentBase {

public String getFamily() {
   return "components.jsp.include";
}

public void encodeBegin(FacesContext context) throws IOException {
   try {
      ExternalContext externalContext = context.getExternalContext();
      HttpServletRequest request = (HttpServletRequest) externalContext.getRequest();
      HttpServletResponse response = (HttpServletResponse) externalContext.getResponse();

      // Create dispatcher for the resource given by the componen's page attribute.
      RequestDispatcher requestDispatcher = request.getRequestDispatcher((String) getAttributes().get("page"));

      // Catch the resource's output.
      CharResponseWrapper responseWrapper = new CharResponseWrapper(response);
      requestDispatcher.include(request, responseWrapper);

      // Write the output from the resource to the JSF response writer.
      context.getResponseWriter().write(responseWrapper.toString());
   }
   catch (ServletException e) {
      throw new IOException();
   }
}
}

The component here gets its content from the resource path denoted by the attribute "page". To JSF, there is no difference between content that is generated locally in the component and content that the component obtained from somewhere else.

The component makes use of a HttpServletResponseWrapper, which is a fairly common artifact in many legacy Servlet/JSP applications. For completeness, here is the source of the particular implementation I used:

public class CharResponseWrapper extends HttpServletResponseWrapper {

   private CharArrayWriter output;

   @Override
   public String toString() {
      return output.toString();
   }

   public CharResponseWrapper(HttpServletResponse response) {
      super(response);
      output = new CharArrayWriter();
   }

   public CharArrayWriter getCharWriter() {
      return output;
   }

   @Override
   public PrintWriter getWriter() {
       return new PrintWriter(output);
  }

   @Override
   public ServletOutputStream getOutputStream() {
      return new CharOutputStream(output);
   }

   public InputStream getInputStream() {
      return new ByteArrayInputStream( toString().getBytes() );
   }
}

class CharOutputStream extends ServletOutputStream {

   private Writer output;

   public CharOutputStream( Writer writer ) {
      output = writer;
   }

   @Override
   public void write(int b) throws IOException {
      output.write(b);
   }
}

In JSF 1.2 there's a minor inconvenience where you have to register the component you just created in an .XML file. JSF 2.0 provides a convenient annotation for this, but in JSF 1.2 it has to happen via XML:

<?xml version="1.0" encoding="UTF-8"?>
<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd"
    version="1.2"
>      
 <component>
  <component-type>com.example.component.JSPIncludeComponent</component-type>
  <component-class>com.example.component.JSPIncludeComponent</component-class>
 </component>

</faces-config>

This can either be put in your regular faces-config.xml, or in a separate faces-config.xml in a META-INF directory on your class-path. The latter option is typically when packaging in a jar, but also works for any regular Java source dir of your web module.

To make the component usable on a Facelets page, we only have to assign it a name space and a tag name. This has to happen in a file with a suffix .taglib.xml that's also in a META-INF directory on your class-path. Even in JSF 2.0, there are unfortunately no annotations for this. For this example I called the file foo-include.xml:

<!DOCTYPE facelet-taglib PUBLIC "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN" "http://java.sun.com/dtd/facelet-taglib_1_0.dtd">

<facelet-taglib> 
 <namespace>http://foo.com/jsf</namespace> 

 <tag>
   <tag-name>include</tag-name>
   <component>
     <component-type>com.example.component.JSPIncludeComponent</component-type>
   </component>
 </tag>

</facelet-taglib>

Note that this example uses the older DTD for the standalone Facelets implementation for JSF 1.2. JSF 2.0 uses a more modern xsd, for which an example can be seen here. Also note that we don't have to define any attributes nor did we had to define any getters and setters for those attributes on the UI component implementation. This convenience does come at a cost though, as your IDE now can't provide you with any content assist for the component's attributes.

Finally an example of how it all comes together on an actual Facelets page:

<?xml version="1.0" encoding="UTF-8"?> 
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page"
   xmlns:ui="http://java.sun.com/jsf/facelets" 
   xmlns:h="http://java.sun.com/jsf/html"
   xmlns:foo="http://foo.com/jsf"
   version="2.1"
> 
 <ui:composition>
  <html xmlns="http://www.w3.org/1999/xhtml">

   <head>    
    <title>JSP include example</title>
    <meta http-equiv="pragma" content="no-cache"/>
    <meta http-equiv="cache-control" content="no-cache"/>
    <meta http-equiv="expires" content="0"/> 
   </head>  

   <body>    
    JSP: <br/>
    <foo:include page="/test.jsp"/><br/><br/>

    SERVLET: <br/>
    <foo:include page="/TestServlet"/><br/><br/>          
   </body>
  </html>

 </ui:composition> 

</jsp:root>

I wouldn't recommend using this as a lasting solution, but it might ease a migration from legacy JSP with smelly scriptlets and all on them to a more sane and modern Facelets application.

Arjan Tijms

Comments

Popular posts from this blog

Implementing container authentication in Java EE with JASPIC

Jakarta EE Survey 2022

Counting the rows returned from a JPA query