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
        }
    }
...

1 comment:

  1. Borgata Hotel Casino & Spa - Mapyro
    Mapyro is a travel and hotel information 광주광역 출장안마 and information resource for 진주 출장샵 casinos in Atlantic City, New 강릉 출장안마 Jersey. Get 안산 출장마사지 casino directions, 목포 출장마사지 reviews and information for

    ReplyDelete