Pages

Friday, December 7, 2012

How to render xhtml with images using flying saucer in a servlet container

If you need to render an xhtml file to produce a PDF, you can use flying saucer xhtmlrenderer library. You can do that in a servlet container by implementing a Filter:

public class RendererFilter implements Filter {

    private FilterConfig config;

    private DocumentBuilder documentBuilder;

    /**
     * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
     */
    public void init(FilterConfig configToSet) throws ServletException {
        try {
            this.config = configToSet;
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            documentBuilder = factory.newDocumentBuilder();
        } catch (ParserConfigurationException e) {
            throw new ServletException(e);
        }
    }

    /**
     * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse,
     *      javax.servlet.FilterChain)
     */
    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);

            try {
                // Parse the XHTML content to a document that is readable by the XHTML renderer.
                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");
                    response.setHeader("Content-Disposition", "inline; filename=print.pdf");
                    OutputStream browserStream = response.getOutputStream();
                    renderer.createPDF(browserStream);
                    return;
                }

            } catch (SAXException e) {
                throw new ServletException(e);
            } catch (DocumentException e) {
                throw new ServletException(e);
            }

        } else {
            // Normal processing
            filterChain.doFilter(request, response);
        }
    }
}

The content of the xhtml file is obtained from the response using a wrapper that captures the response writer and produces a ByteArrayOutputStream that is converted to a string in 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());
      return xhtmlContent; 
   }
}

Source code obtained from here.


This works fine except in the case that the xhtml has images in it. In that case, the renderer library must kwow the url that the xhtml came from, in order to calculate relative image paths. To achieve that you can change the line 45 of the RendererFilter: 

   renderer.setDocument(xhtmlContent, "");

by:  

   renderer.setDocument(xhtmlContent, request.getRequestURL().toString());

Before you can use the renderer, you must configure the filter and mapping in the web.xml of the application.

Finally, to automatically generate the PDF on the fly you must call or link to the url in the following way:

../path/to/file.xhtml?RenderOutputType=pdf


19 comments:

  1. Thanks ... after long search i found your simple solution!!! works great for me!

    ReplyDelete
  2. HTTP Status 500 - org.xml.sax.SAXParseException; lineNumber: 193; columnNumber: 1202; The entity "nbsp" was referenced, but not declared.

    why???

    ReplyDelete
  3. 02E37
    ----
    ----
    ----
    ----
    ----
    ----
    matadorbet
    ----
    ----

    ReplyDelete