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 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.
Java Server Pages (JSP) technology was never really designed to support the component-orientated nature of Java Server Faces. As a result, these two have always been rather awkward bedfellows. Although some of these issues have been addressed in Java EE 5 through the 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 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.
Although HTML/XHTML rendering has been available as part of the 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 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.
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 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 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.
Figure 1. A typical JSF Facelets application
The table is bound to the data in the underlying backing bean by using the standard <h:dataTable> component:
... <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>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 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.
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.
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); ... } ... } ... }The ContentCaptureServletResponse simply wraps a PrintWriter around an in-memory 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.
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; } }At the moment, the Flying Saucer Renderer does not support the <thead> and <tbody> 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.
The Flying Saucer Renderer supports rendering to PDF files directly through the use of its ITextRenderer class.
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; }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.
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:
<style type="text/css"> @page{ size: 11.69in 8.27in; } ... </style>A great benefit of Java's lightweight rendering approach is that anything rendered through a Graphics2D object can also be rendered to an image. For more information on this technique, you can refer to my earlier article, " 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 Graphics2D object associated with a BufferedImage instance. This BufferedImage instance can then be transformed into PNG, BMP, JPEG or GIF image formats through the use of the ImageIO API.
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; }Figure 3 shows the page rendered as a PNG image file.
Figure 3. The page rendered as a PNG image file
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.
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; }One slight limitation in the SVGGraphics2D class is the fact that it does not support the getDeviceConfiguration() method of the Graphics2D class. Due to this, we have to use the somewhat unseemly workaround in the code above where we use the Graphics2D object of a BufferedImage instance for layout purposes instead.
Figure 4 shows the page rendered as an SVG file.
Figure 4. The page rendered as a SVG file, viewed using the Adobe SVG Viewer
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.
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.
by fatroom - 2010-01-16 07:53
Where can I get source for this article? Link in article body is dead :( Login or register to post commentsby nabitokaxu - 2010-05-06 02:52
I don't down source code. Please check link download. Thanks you Login or register to post comments