Skip to main content

Combine JSF Facelets and the Flying Saucer XHTML Renderer

October 31, 2006

{cs.r.title}







Over the last ten years or so, the Java platform has evolved into
a vast ecosystem that spans across most of the computing landscape
and now supports application development on the server, the desktop,
and on mobile devices. As a result, many Java developers have now
started to specialize in certain areas of the platform. Although
this trend of specialization is a testament to the level of
maturity that the platform has reached, developers now run the risk
of only focusing on their particular area of expertise and not
taking notice of innovations in other areas of the platform. As is
sometimes the case, many of these innovations can often find uses
beyond their original target platform.

Two great open source projects are currently hosted on java.net:
the Flying Saucer
All-Java XHTML Renderer
and the "https://facelets.dev.java.net/">Facelets Java Server Faces
Framework. At first glance, these two project might seem to target
different audiences--the JSF Facelets Project seems to be aimed
at server-based applications, while the Flying Saucer XHTML Renderer
is mainly for use in the desktop arena. Despite this, however, these
two projects have a high level of synergy. In this article we will
be exploring how it is possible to combine these two technologies
from different backgrounds to render more than just simple HTML web
pages.

Facelets

Java Server Pages
(JSP)
technology was never really designed to support the
component-orientated nature of "http://java.sun.com/javaee/javaserverfaces/">Java Server
Faces
. As a result, these two have always been rather "http://www.onjava.com/pub/a/onjava/2004/06/09/jsf.html">awkward
bedfellows
. Although some of these issues have been addressed
in Java EE 5 through the "http://java.sun.com/products/jsp/reference/techart/unifiedEL.html">
Unified Expression Language
, there are still restrictions
imposed by backward-compatibility and the sheer amount of
boilerplate code that is required to make the two work together.
You only need to look at "http://java.sun.com/javaee/5/docs/tutorial/doc/JSFCustom8.html">what
is involved
in creating a new JSP tag handler for evidence of
this.

Facelets is a view framework for JSF that streamlines the whole
development process and does away with a lot of unnecessary
boilerplate code. Instead of relying on JSP pages (and all the
baggage it comes with) for templating, Facelets instead uses XHTML
files. Among the many benefits that this approach provides is the
simplification of new component development, performance
improvements, the ability to
run JSF 1.2 on J2EE 1.4 web containers such as Tomcat 5.5
, and
better
error reporting
during the development phase. In addition, all
the pages produced by a Facelets application are required to be
valid XHTML documents--something that will be significant when
integrating with the Flying Saucer Renderer.

The Flying Saucer XHTML Renderer

Although HTML/XHTML rendering has been available as part of
the "http://java.sun.com/j2se/1.5.0/docs/api/javax/swing/text/html/HTMLDocument.html">
Swing API
for a while now, the current implementation is
incomplete and has been found by many to be inadequate for
practical use. One solution that has been devised to render HTML in
Java is to embed a native browser window in the application through
the "https://jdic.dev.java.net/documentation/Specification.html">JDIC
library. Different platforms have different native browsers, however,
and unlike with the Swing API, you can never really guarantee that
a page rendered by a native browser would appear exactly the same
on all platforms. In addition, the two native browsers currently
supported by JDIC, Internet Explorer and Mozilla Firefox, might
not be available on the platforms your application is
targeting.

The Flying Saucer project aims to address these problems by
implementing a pure Java XHTML Renderer. Although there are some
minor gaps in the renderer's XHTML implementation, it still does an
excellent rendering job and is no doubt a very useful API to have
in your toolbox.

Combining JSF Facelets and the Flying Saucer Renderer

Facelets and the Flying Saucer Renderer have a symbiotic
relationship: one produces XHTML content while the other "parses"
it and lays it out into an object graph in memory. While this
object graph is then usually translated into

"http://java.sun.com/j2se/1.5.0/docs/api/java/awt/Graphics2D.html">Graphics2D

API calls to render the document in Swing/AWT, it can also be
transformed into various other formats such as PDF, a selection of
image formats, or as a "http://en.wikipedia.org/wiki/Scalable_Vector_Graphic">Scalable
Vector Graphics (SVG)
document.

The first step to making these to APIs work together is to
implement a simple JSF Facelet application that will serve as our
XHTML content source.

In Figure 1, we have a fairly straightforward application that
displays a list (read from a CSV file) of the top 12 worldwide
companies according to Forbes.

A typical JSF Facelets Application
Figure 1. A typical JSF Facelets application

The table is bound to the data in the underlying backing bean by
using the standard

"http://java.sun.com/javaee/javaserverfaces/1.2/docs/tlddocs/h/dataTable.html">
component:

[prettify]
...
<h:dataTable styleClass="companiesTable"
             value="#{topCompaniesBean.companiesList}" 
             var="company" 
             cellpadding="0" cellspacing="0" 
             headerClass="header"
             ... 
             rowClasses="odd-row,even-row">
   <h:column>
      <f:facet name="header">   
         <a href="?Sort=rank">Rank</a>
      </f:facet>
      <h:outputText value="#{company.rank}"/>
   </h:column>   
   <h:column>
      <f:facet name="header">   
         <a href="?Sort=name">Name</a>
      </f:facet>
      <h:outputText value="#{company.name}"/>
   </h:column>
   <h:column>
      <f:facet name="header">   
         <a href="?Sort=country">Country</a>
      </f:facet>
      <h:outputText value="#{company.country}"/>
   </h:column>
   <h:column>
      <f:facet name="header">   
          <a href="?Sort=category">Category</a>
      </f:facet>
      <h:outputText value="#{company.category}"/>
   </h:column>   
   <h:column>
      <f:facet name="header">   
         <a href="?Sort=sales">Sales</a>
      </f:facet>
      <h:outputText value="#{company.sales}">
         <f:convertNumber type="currency"/>
      </h:outputText>
   </h:column>   
... 
</h:dataTable>
[/prettify]

The implementation of the Facelets application is not the main
focus of this article, however. You can download the full source
code used in this article from the "#resources">Resources section if you would like to know more
about the implementation details of the Facelets part of the
application.

Now, once we are happy with the XHTML that is being produced by
the Facelets application, we are ready to put the Flying Saucer
Renderer through its paces and start to transform the "plain" XHTML
content into a wide variety of different formats.

The RendererFilter Class

Before we can start rendering the XHTML content, we first need
to capture it. The easiest way to accomplish this is to implement a
servlet filter that sits between the browser and the underlying
XHTML page. Incidentally, this filter will then also be responsible
for transforming the "captured" XHTML content into a different
format.

[prettify]
public class RendererFilter implements Filter {
...
   public void doFilter(ServletRequest req, ServletResponse resp, 
                                    FilterChain filterChain) 
                                    throws IOException, ServletException {
      HttpServletRequest request = (HttpServletRequest)req;
      HttpServletResponse response = (HttpServletResponse)resp;
      //Check to see if this filter should apply.
      String renderType = request.getParameter("RenderOutputType");
      if(renderType != null) {
        //Capture the content for this request
        ContentCaptureServletResponse capContent = 
           new ContentCaptureServletResponse(response);  
        filterChain.doFilter(request,capContent);
          ...
        //Transform the XHTML content to a document 
        //readable by the renderer.
        StringReader contentReader = new StringReader(capContent.getContent());
        InputSource source = new InputSource(contentReader);
                Document xhtmlContent = documentBuilder.parse(source);
         ...
         }
        ...
  }
...
}       
[/prettify]

The ContentCaptureServletResponse simply wraps a

"http://java.sun.com/j2se/1.5.0/docs/api/java/io/PrintWriter.html">PrintWriter

around an in-memory
"http://java.sun.com/j2se/1.5.0/docs/api/java/io/ByteArrayOutputStream.html">
ByteArrayOutputStream
to collect the produced XHTML.
Once the underlying Facelets page has completed, The
RendererFilter retrieves the content in the form of a
String by calling the getContent() method.

[prettify]
public class ContentCaptureServletResponse 
                            extends HttpServletResponseWrapper {
   private ByteArrayOutputStream contentBuffer;
   private PrintWriter writer;
   public ContentCaptureServletResponse(HttpServletResponse resp) {
      super(resp); 
   }
   @Override
   public PrintWriter getWriter() throws IOException {
      if(writer == null){
         contentBuffer = new ByteArrayOutputStream();
         writer = new PrintWriter(contentBuffer);
      }
      return writer;
   }
   public String getContent(){
      writer.flush();
      String xhtmlContent = new String(contentBuffer.toByteArray());
      xhtmlContent = xhtmlContent.replaceAll("<thead>|</thead>|"+ 
                                             "<tbody>|</tbody>","");
      return xhtmlContent; 
   }
}
[/prettify]

At the moment, the Flying Saucer Renderer does not support the
and tags. In
order to work around this limitation, the getContent()
method strips these tags out of the result it returns back to the
RenderFilter. Hopefully this issue will be addressed
in future versions of the Flying Saucer Renderer and will make this
workaround unnecessary.

We are now ready to transform the captured XHTML content into
some other formats.

XHTML to PDF Conversion

The Flying Saucer Renderer supports rendering to PDF files
directly through the use of its ITextRenderer
class.

[prettify]
HttpServletResponse response = (HttpServletResponse)resp;
String renderType = request.getParameter("RenderOutputType");
...
StringReader contentReader = new StringReader(capContent.getContent());
InputSource source = new InputSource(contentReader);
Document xhtmlContent = documentBuilder.parse(source);
...
if(renderType.equals("pdf")){
   ITextRenderer renderer = new ITextRenderer();
   Renderer.setDocument(xhtmlContent,"");
   renderer.layout();
   response.setContentType("application/pdf");
   OutputStream browserStream = response.getOutputStream();
   renderer.createPDF(browserStream);
   return;
}
[/prettify]

Setting the content type to application/pdf ensures that the
byte stream is correctly recognized as a PDF file by the browser
and is handled appropriately, as shown in Figure 2.

The page rendered as a PDF file
Figure 2. The page rendered as a PDF file

You can specify the size and other properties of the PDF page
through the use of the CSS3 Paged Media module. In
our example, the page is set to landscape with the following CSS
code:

[prettify]
<style type="text/css">
@page{
   size: 11.69in 8.27in;   
}       
...
</style>
[/prettify]

XHTML to Image Conversion

A great benefit of Java's lightweight rendering approach is that
anything rendered through a

"http://java.sun.com/j2se/1.5.0/docs/api/java/awt/Graphics2D.html">Graphics2D

object can also be rendered to an image. For more information on
this technique, you can refer to my earlier article, " "http://today.java.net/pub/a/today/2006/04/20/bringing-swing-to-the-web.html">
Bringing Swing to the Web
."

In our example, we will be utilizing the Flying Saucer's
Graphics2DRenderer class to render the layout object
graph to the

"http://java.sun.com/j2se/1.5.0/docs/api/java/awt/Graphics2D.html">Graphics2D

object associated with a
"http://java.sun.com/j2se/1.5.0/docs/api/java/awt/image/BufferedImage.html">
BufferedImage
instance. This
"http://java.sun.com/j2se/1.5.0/docs/api/java/awt/image/BufferedImage.html">
BufferedImage
instance can then be transformed into
PNG, BMP, JPEG or GIF image formats through the use of the

ImageIO
API.

[prettify]
HttpServletResponse response = (HttpServletResponse)resp;
String renderType = request.getParameter("RenderOutputType");
...
StringReader contentReader = new StringReader(capContent.getContent());
InputSource source = new InputSource(contentReader);
Document xhtmlContent = documentBuilder.parse(source);
...
Graphics2DRenderer renderer = new Graphics2DRenderer();
renderer.setDocument(xhtmlContent,"");
if(renderType.equals("image")){
   BufferedImage image = new BufferedImage(width,height,
                                           BufferedImage.TYPE_INT_RGB);
   Graphics2D imageGraphics = (Graphics2D)image.getGraphics();
   imageGraphics.setColor(Color.white);
   imageGraphics.fillRect(0, 0, width, height);
   renderer.layout(imageGraphics,new Dimension(width,height));
   renderer.render(imageGraphics);
   //Now output the image to PNG using the ImageIO libraries.
   OutputStream browserStream = response.getOutputStream();
   response.setContentType("image/png");
   ImageIO.write(image, "png", browserStream);
   return;
}       
[/prettify]

Figure 3 shows the page rendered as a PNG image file.

The page rendered as PNG image file
Figure 3. The page rendered as a PNG image file

XHTML to Scalable Vector Graphics (SVG) Conversion

Scalable
Vector Graphics
is an XML open standard markup language
created by the World Wide Web Consortium to describe
two-dimensional vector graphics. The main advantage that vector
graphic formats have over raster graphic formats such as PNG and
JPEG is the fact that they are defined through geometric points,
lines, and relationships rather than pixels. This makes it possible
to resize a vector graphic image to any size without a loss of
quality and, thanks to its XML syntax, to allow animation through the
use of a scripting language such as JavaScript.

The open source Batik library provides
excellent SVG support for the Java platform. Batik makes the
process of rendering graphics to an SVG file rather straightforward--instead of using a Graphics2D
object as we have done in the previous example, use the drop-in
replacement class SVGGraphics2D.

[prettify]
HttpServletResponse response = (HttpServletResponse)resp;
String renderType = request.getParameter("RenderOutputType");
...
StringReader contentReader = new StringReader(capContent.getContent());
InputSource source = new InputSource(contentReader);
Document xhtmlContent = documentBuilder.parse(source);
...
Graphics2DRenderer renderer = new Graphics2DRenderer();
renderer.setDocument(xhtmlContent,"");
if(renderType.equals("svg")){
   Document svgDocument = documentBuilder.newDocument();
   //Used to work around a limitation in SVGGraphics2D
   BufferedImage image = new BufferedImage(width,height,
                                           BufferedImage.TYPE_INT_RGB);
   Graphics2D layoutGraphics = (Graphics2D)image.getGraphics();
   // Create an instance of the SVG Generator
   SVGGeneratorContext ctx = SVGGeneratorContext.createDefault(svgDocument);
   ctx.setEmbeddedFontsOn(true);
   ctx.setPrecision(12);
   SVGGraphics2D svgGenerator = new SVGGraphics2D(ctx,false);
   renderer.layout(layoutGraphics,new Dimension(width,height));
   renderer.render(svgGenerator);
   // Finally, stream out SVG to the browser
   response.setContentType("image/svg+xml");
   Writer browserOutput = response.getWriter();
   svgGenerator.stream(browserOutput, true);
   return;
}
[/prettify]

One slight limitation in the SVGGraphics2D class is
the fact that it does not support the
getDeviceConfiguration() method of the

"http://java.sun.com/j2se/1.5.0/docs/api/java/awt/Graphics2D.html">Graphics2D

class. Due to this, we have to use the somewhat unseemly workaround in
the code above where we use the
"http://java.sun.com/j2se/1.5.0/docs/api/java/awt/Graphics2D.html">Graphics2D

object of a
"http://java.sun.com/j2se/1.5.0/docs/api/java/awt/image/BufferedImage.html">
BufferedImage
instance for layout purposes instead.

Figure 4 shows the page rendered as an SVG file.

The page rendered as a SVG file
Figure 4. The page rendered as a SVG file, viewed using the
Adobe SVG Viewer

Future Directions

While there are certainly some limitations to the XHTML that can
currently be processed by the Flying Saucer Renderer, there is
little doubt that these issues will be resolved in future versions.
By virtue of it being an open source project, it is very easy for
anyone to lend a hand and to get involved in the development
process to help make these improvements happen.

It is not inconceivable that XHTML might at some point replace
HTML as the lingua franca of the internet and that the
Flying Saucer has matured to a point where the technique described
in this article might become more commonplace. The Flying Saucer
Renderer might also have a future as part of the Java SE platform,
where it could serve as a drop-in replacement for Swing's currently
incomplete HTML rendering engine.

Conclusion

Hopefully this article not only offered you an insight into two
great open source projects hosted here on java.net, but also on how
two projects at the opposite sides of the Java SE/EE divide can
be combined to offer compelling solutions unrivalled on other
platforms. In the Java ecosystem, innovation is happening at an
increasingly rapid rate across all platforms. Do not risk missing
out on new and innovative ideas by not looking outside of the niche
you are currently focused on.

Resources


width="1" height="1" border="0" alt=" " />
Jacobus Steenkamp has been developing in Java for the last 4.5 years and has worked in various sectors including the financial, pharmaceutical and energy industries.
Related Topics >> Programming   |   

Comments

Where can I get source for

Where can I get source for this article? Link in article body is dead :(

I don't down source code.

I don't down source code. Please check link download. Thanks you

This page contains the following errors: error on line 1 ...

This page contains the following errors:

error on line 1 at column 1: Document is empty
error on line 1 at column 1: Encoding error
Below is a rendering of the page up to the first error.
===========================================
SEVERE: Servlet.service() for servlet [Faces Servlet] in context with path [/WebApplication1] threw exception [org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 1; Content is not allowed in prolog.] with root cause
org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 1; Content is not allowed in prolog.
at com.sun.org.apache.xerces.internal.parsers.DOMParser.parse(DOMParser.java:251)
at com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl.parse(DocumentBuilderImpl.java:300)
at filter.RendererFilter.doFilter(RendererFilter.java:65)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
========================
Sep 26, 2013 1:21:15 AM org.apache.catalina.core.StandardWrapperValve invoke
SEVERE: Servlet.service() for servlet [Faces Servlet] in context with path [/WebApplication1] threw exception [org.xml.sax.SAXException: FWK005 parse may not be called while parsing.] with root cause
org.xml.sax.SAXException: FWK005 parse may not be called while parsing.
at com.sun.org.apache.xerces.internal.parsers.DOMParser.parse(DOMParser.java:265)
at com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl.parse(DocumentBuilderImpl.java:300)
at filter.RendererFilter.doFilter(RendererFilter.java:65)