Fill PDF forms in an AEM Service

If you want to provide a download of customized PDF files, you can go the full-fledged way and buy into the Adobe world or build a slim custom solution which brings no additional cost.

This post shows a simple usecase where a PDF form is uploaded to DAM and later filled inside an OSGi service and delivered to the user by a Servlet.

Dependencies: Apache PDFBox

There are multiple open source PDF libraries we could use, but in the end Apache PDFBox1 is the weapon of choice. It is well documented, many examples are available and it is compatible with OSGi out of the box (no need for any wrapper etc).

In older versions of CQ, Adobe provided PDFBox as a default dependency but since CQ5.6 the complete PDF handling (extract metadata, generate thumbnails etc) is by a custom library (Gibson).

You need to include two dependencies to your pom.xml and make sure, they are deployed to AEM:

<dependency>
  <groupId>org.apache.pdfbox</groupId>
  <artifactId>pdfbox</artifactId>
  <version>2.0.6</version>
</dependency>

<dependency>
  <groupId>commons-logging</groupId>
  <artifactId>commons-logging</artifactId>
  <version>1.2</version>
</dependency>

PDF Service

As we want a reusable solution, we will build all logic into a OSGi Service instead of the Servlet or some other class. You’ll be able to provide multiple implementations of the Service if required and generate different PDFs in each one.

The following snippet shows the required methods in our PDF Service:

@Component(immediate = true)
@Service(PDFService.class)
public class PDFService {

  public void createPDF(SlingHttpServletRequest request, OutputStream outputstream) throws IOException;

  protected PDDocument getPDDocument(SlingHttpServletRequest request) throws IOException;

  protected Asset getAsset(SlingHttpServletRequest request);

  protected void setFormField(String fieldName, String fieldValue);

}

Load PDF Document from DAM

  // TODO: should not be static, depends on the requirements
  private static final String PDF_PATH = "/content/dam/foo/bar/ipsum.pdf";

  /**
  * @return PDDocument instance of the PDF saved in DAM or null, if no asset can be loaded
  */
  protected PDDocument getPDDocument(SlingHttpServletRequest request) throws IOException {
    Asset pdfAsset = getAsset(request);
    return pdfAsset != null ? PDDocument.load(pdfAsset.getOriginal().getStream()) : null;
  }

  /**
  * @return Asset from DAM or null, if asset can't be loaded
  */
  protected Asset getAsset(SlingHttpServletRequest request){
    ResourceResolver resourceResolver = request.getResourceResolver();
    Resource assetResource = resourceResolver.getResource(PDF_PATH);
    if(assetResource != null) {
      return assetResource.adaptTo(Asset.class);
    }
    return null;
  }

Fill form fields

  /**
  * Fills the given field with the given value. If field can't be found, nothing happens
  */
  protected void setFormField(PDAcroForm acroform, String fieldName, String fieldValue) {
    PDField field = acroform.getField(fieldName);
    if(field != null){
      field.setValue(fieldValue);
    }
  }

Glue it all together

  public void createPDF(SlingHttpServletRequest request, OutputStream outputstream) throws IOException {
    try(PDDocument pddocument = getPDDocument(request)){
      if(pddocument != null){
        PDDocumentCatalog docCatalog = pddocument.getDocumentCatalog();
        PDAcroForm acroForm = docCatalog.getAcroForm();
        if(acroForm != null){
          //TODO: populate fields depending on the PDF
          setFormField(acroForm, "test_field", "test_value");
          document.save(outputstream);
        }
      }
    }
  }

PDF Servlet

Now as all logic is encapsulated in the Service, the Servlet can simply call the service and hand over the response’s OutputStream. If you need to pass any parameters (e.g. Query-Parameters) from the Request to the Service, you need to extend the Servlet accordingly.

@SlingServlet(resourceTypes = "/apps/your/resource/type"
extensions = "pdf", methods = "GET")
public class PDFPostServlet extends SlingAllMethodsServlet {

  @Reference
  private PDFService pdfService;

  @Override
  protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws IOException {
    try{
      // set Response Type to PDF
      response.setContentType("application/pdf");
      pdfService.createPDF(request, response.getOutputStream();)
    }
    catch(IOException ex){
      response.setContentType(ContentType.HTML);
      response.sendError(HttpStatus.SC_INTERNAL_SERVER_ERROR);
    }
  }
}

Footnotes

Related Posts