Blog

Monday, 16 June 2014

Generating PDFs with Apache FOP and Velocity

Generating PDF files is a common thing in web applications. For a long time I was using iText to accomplish this task but writing PDF’s content in Java code was a struggle. Nowadays iText is free for non-commercial use only which makes it even less sensible choice.

What I was looking for was a solution similar to good old Seam 2 PDF. Unfortunately, that extension was not ported to Seam 3 / CDI which we are currently using at IT Crowd. So the goal was to find a library which allows developer to define an XML template that can be styled in CSS alike manner and which can be easily replaced if needed. Also I wanted the template to be parameterizable i.e. it should contain variables which could be populated with values dynamically.

I inspected several libraries including Jasper Reports but all of them were either too limited or had too complicated syntax. Finally I came across Apache FOP which met my XML+CSS condition perfectly. Sadly FOP templates were not dynamic. That’s why I decided to combine FOP with Velocity. Here is the complete solution for CDI/Seam 3 applications (though it can be easily ported to other Java EE frameworks too):

PDF Generator class:

import org.apache.fop.apps.FOPException;
import org.apache.fop.apps.Fop;
import org.apache.fop.apps.FopFactory;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.apache.xmlgraphics.util.MimeConstants;

import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamSource;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Map;
import java.util.Scanner;

/**
 * Utility class for generating PDF files from Apache FOP templates.
 *
 * @author Tomasz Zabkowicz / IT Crowd LLC
 */
public class PDFGenerator implements Serializable {

    /**
     * Generates PDF file with given name basing on provided Apache FOP template with Velocity markers which will be populated with given parameter values.
     *
     * @param fileName         a name of the PDF file that should be created. The name should have format '*.pdf'.
     * @param templateFilePath a path to Apache FOP template basing on which PDF will be generated. Example path: '/layout/pdf/purchaseOrderPDF.fo'.
     * @param params           a map containing values for Velocity parameters placed in the template. Parameter values should be accessed the following
     *                         way in the FOP template: $param.[param_key].
     *
     * @throws IOException
     * @throws FOPException
     * @throws TransformerException
     */
    public void generate(String fileName, String templateFilePath, Map<String, Object> params) throws IOException, FOPException, TransformerException
    {
        // Obtain server context and template file
        final ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
        final String template = new Scanner(new File(externalContext.getRealPath(templateFilePath))).useDelimiter("\\Z").next();
        final StringWriter content = new StringWriter();

        // Use Velocity to replace placeholders in template with desired values
        Velocity.init();
        VelocityContext velocityContext = new VelocityContext();
        velocityContext.put("param", params);
        Velocity.evaluate(velocityContext, content, "", template);
        final String sourceXml = content.toString();

        // Prepare output stream to which PDF will be written
        final HttpServletResponse response = (HttpServletResponse) externalContext.getResponse();
        response.setHeader("Content-disposition", "inline; filename=\"" + fileName + "\"");
        response.setContentType("application/pdf");
        response.setHeader("attachment", fileName);

        // Run FOP transformer which will generate PDF and write it to the stream
        final ServletOutputStream out = response.getOutputStream();
        final Fop fop = FopFactory.newInstance().newFop(MimeConstants.MIME_PDF, out);
        final Transformer transformer = TransformerFactory.newInstance().newTransformer();
        transformer.setParameter("versionParam", "2.0");
        final Source src = new StreamSource(new StringReader(sourceXml));
        final Result res = new SAXResult(fop.getDefaultHandler());
        transformer.transform(src, res);

        // Close stream and http response - PDF file will be downloaded to user's machine
        out.flush();
        out.close();
        response.flushBuffer();
        FacesContext.getCurrentInstance().responseComplete();
    }
}
Sample FOP template. This is an example file coming from here. I replaced text with velocity markers to show how it can be done:





    
        
            
            
            
        
    
    

        

            
                $param.header
            

            
                $param.block1
            

            
                $param.block2
            

        
    

And finally some code to invoke PDFGenerator.generate() method which can be placed in backing bean:

...

@Inject
private PDFGenerator pdfGenerator;

public void generatePDF()
    {
        final Map<String, Object> params = new HashMa<String, Object>();
        params.put("header", "Simple document with 2 blocks of text");
        params.put("block1", "Lorem ipsum dolor sit amet.");
        params.put("block2", "Sed dapibus tempus ultrices. Mauris faucibus bibendum lacus, quis mattis eros.");

       try {
            pdfGenerator.generate("Simple.pdf", "/layout/pdf/simplePDF.fo", params);
        } catch (Exception e) {
            // do something
        }
    }
...