Skip to Content

In the first three parts of this series we had a look on the definition/design of the user interface (https://blogs.sap.com/2016/09/08/abpm-step-by-step/), the integration of aBPM with SAP BPM workflows (https://blogs.sap.com/2016/09/25/abpm-step-by-step-part-2/) and the enhancements for emails (https://blogs.sap.com/2017/05/05/abpm-step-by-step-part-3/). This time we’ll have a look at the PDF generation with aBPM and how to use it.

aBPM PDF Support

aBPM supports multiple purposes for PDF generation, including downloadable “screenshots” or generation of PDF as byte arrays that can be used as email attachments or for archiving (also as PDF/A). The standard implementation for PDF also contains coding that transforms the form data into a XML structure that some customers are using for archiving.

The base architecture for PDF generation support fits into the general extension concept used within aBPM: There is also an interface – in this case it’s IPdfGenerator – that defines the communication between the aBPM core and the extension. This standard package contains also a base implementation for the PDF generation that uses Apache FOP (https://xmlgraphics.apache.org/fop/). This documents describes how to use and create your own PDF formats based on this implementation. Nevertheless you are free to implement your own PDF generation implementation, maybe based on Adobe Document Services that can be used on WebAS Java?

How to include PDF generation in your scenario

In order to use PDF generation, you need to apply the following steps:

  1. Develop your own implementation of the IPdfGenerator interface. You can do this either based on the BasePdfGenerator class included in aBPM or create the implementation from scratch. If you don’t need any additional enhancement you can skip this step and use the BasePdfGenerator directly.
  2. Include your implementation of the IPdfGenerator as extension class in the queryInterface method of your scenario.
  3. If you use the FOP based PDF generation you should provide your own templates because the default ones just print the form data as a table. Although this is sufficient it’s not really pretty. The BasePdfGenerator also provides some methods where you can enhance the default behavior of the FOP PDF generation.
  4. Include either the PDF download capability as simulation of “print screen” or use the methods/operations of the EJB or WebService interfaces.

We’ll have now a closer look at all steps…

IPdfGenerator interface and BasePdfGenerator

The IPdfGenerator interface contains the following methods:

  • generate – This method generates the PDF and returns it as a byte array
  • convertToPdfA – Method to transforms the given PDF into PDF/A format
  • transformData2Xml – Transforms the form data into XML format. The default implementation makes use of the XML format because it uses XLF-FO for the generation of PDFs.
  • getClassLoader / setClassLoader – As the resources (XSL-FO templates) could be located into different EAR files it’s necessary to define the classloader that is used to read the resources.
  • getScreenshotTemplate – Returns the name of the resource that is used for “screenshots”.

Based on this, the BasePdfGenerator provides an implementation that first transforms the form data into an XML document and then applies the XSL-FO transformation to generate the PDF document as output. It provides some methods to influence the default behavior:

  • addAttachment can be used to add some documents as attachments in the PDF document. This can’t be done via XSL-FO and needs Java development
  • getScreenshotTemplate/addScreenshotTemplate/setDefaultScreenshotTemplate allow the definition of different templates that are used for the “screenshot” function.
  • createDocumentProperties is used to add some properties that are included into the PDF document. The base implementation just sets the author to “aBPM”.
  • adaptUserAgentData can be used to add data for post processing, e.g. for setting security (see https://xmlgraphics.apache.org/fop/2.2/pdfencryption.html) or other options.
  • Resolve is useful if you need to add coding to influence the resource finding, e.g. for loading resources from external EAR files or from database.

Integration into scenario

Similar to other extensions the PDF generation is integrated into the scenario with some coding in the queryInterface method of the scenario callback. Here is an example that uses the BasePdfGenerator class included in aBPM:

@Override
public Object queryInterface(AbstractWrappedCallbackContext<Customer> ctx, InterfaceType type) {
  if (InterfaceType.IPdfGenerator.equals(type)) {
    return new BasePdfGenerator(this.getClass().getClassLoader());
  }
  return super.queryInterface(ctx, type);
}

As you can see, we create a new instance of the BasePdfGenerator class if the framework requests an extension object of type IPdfGenerator. The PdfGenerator constructor requires a classloader as parameter. By passing the classloader of the current scenario class the PDF generator is able to access resources from the scenario EAR file.

Writing XSL-FO stylesheets

If you plan to use apache FOP for the PDF generation you need to develop your own XSL-FO templates to enhance the basic format that is included in the BasePdfGenerator. This basic one just takes BPM information and all attributes and puts it into tables. Although this approach does show all data it’s neither pretty nor usable for all purposes.

The XSL-FO stylesheets will be stored in files as part of the EJB DC of the scenario. You can include multiple stylesheets into the DC in order to support multiple purposes. Here is an example for an XSL-FO stylesheet:

<xsl:stylesheet version="1.1" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
  xmlns:fo="http://www.w3.org/1999/XSL/Format" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  exclude-result-prefixes="fo">
	
  <xsl:output method="xml" version="1.0" omit-xml-declaration="no" indent="yes" encoding="UTF-8"/>

  <!-- ============================== -->
  <!-- Central definition of formats  -->
  <!-- ============================== -->	
  <xsl:variable name="font" select="'Helvetica'"/>
	
  <!-- many more -->
	
  <!-- ======================== -->
  <!-- Root                     -->
  <!-- ======================== -->
  <xsl:template match="/data">
    <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
      <fo:layout-master-set>
        <fo:simple-page-master master-name="simpleA4" page-height="29.7cm" page-width="21cm" margin-top="2cm" margin-bottom="2cm" margin-left="1cm" margin-right="1cm">
          <fo:region-body/>
          <fo:region-after region-name="footer" extent="0mm"/>
        </fo:simple-page-master>
      </fo:layout-master-set>
		
      <fo:page-sequence master-reference="simpleA4">
        <fo:static-content flow-name="footer" xsl:use-attribute-sets="text_format_default">
          <fo:block text-align="center">
            Page <fo:page-number/>
          </fo:block>
        </fo:static-content>
        <fo:flow flow-name="xsl-region-body">
        <fo:block xsl:use-attribute-sets="text_format_h1">aBPM Process Summary</fo:block>

        <xsl:call-template name="bpm_process"/>
        <xsl:call-template name="tasks"/>
        <xsl:call-template name="abpm_process"/>
        <xsl:call-template name="feeds"/>
        
        <!-- add more template calls here --->

      </fo:flow>
      </fo:page-sequence>			
    </fo:root>
  </xsl:template>

...
</xsl:stylesheet>

It creates a page definition for A4 paper and then calls multiple tables like “bpm_process”, “tasks”, “abpm_process” to include the definitions for different areas of the document. The data from the aBPM Process and the business objects is serialized into a XML document with “/data” as the root. Accessing the attribute ADDRESS_RESIDENCE_CITY of the root business object would look like the following XSL tag:

<xsl:value-of select="/data/process/bo/attributes/attribute[technicalName = 'ADDRESS_RESIDENCE_CITY']/stringCurrentValue"/>

Passing non-form data to the generator

In some cases you want to pass data for the PDF document that is not part of the form, e.g. the current date or some calculated values. This is also possible! First you need to enhance the BasePdfGenerator with your own class (that needs to be used in the queryInterface!) In this class you overwrite the method createData and fill the information via data.setAddtionalData:

@Override
public CombinedProcessData createData(CombinedProcessData data, PdfGeneratorContext ctx) throws Exception {
  DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");

  data = super.createData(data, ctx);
  data.addAddtionalData("today", df.format(new java.util.Date()));

  return data;
}

Then you are able to access it in the stylesheet via the XSL path

/data/additionalData/entry[key = 'today']/value

Showing images

It’s also possible to include external resources like images into the PDF. If you put the resource into the EJB DC than BasePdfGenerator contains a resolve method that reads the resources directly from the classpath. If you want to use a different resource location you need to enhance BasePdfGenerator and overwrite the “resolve” method. In case you store a logo image in the EJB DC you can use XSL-FO as:

<fo:block-container height="2cm" width="4cm" top="-1cm" left="12cm" position="absolute">
  <fo:block text-align="right">
    <fo:external-graphic src="com/sap/consulting/abpm/demo/pdf/company_logo.jpg"></fo:external-graphic>
  </fo:block>			
</fo:block-container>

Usage in scenario

There are 3 different ways to use the generated PDFs.

  1. First one is to provide something like a “screenshot” function. This one receives the current form data and applies a given stylesheet. The generated PDF is then sent back to the browser and can downloaded or directly viewed on the users PC.
  2. You can access the data via Web-Service calls by using the provided Web-Service interface. So it’s possible to trigger the generation and access the PDF in the process e.g. for transferring it to other systems.
  3. The EJB that is providing the Web-Service can also be accessed directly. This way is better if you want to use the PDF inside your Java callback coding, e.g. for creating email attachments or access storage solutions via Java Coding.

Let’s have a closer look at the different usage types:

Use for “screenshots”

If you want to use the PDF generation for “screenshot” you need to define a Javascript option inside the config.xml file.

<javascript-option id="pdf-printout">
  <![CDATA[
    var jForm = $('<form></form>', {
      action: '/abpm/pdfform',
      method: 'post',
      target: '_blank'
    });
                        
    $("<input>", {
      name: 'bo',
      value: that.getView().getModel("bo").getJSON()
    }).appendTo(jForm);
                                  
    $("<input>", {
      name: 'process',
      value: that.getView().getModel("process").getJSON()
    }).appendTo(jForm);
                        
    var oFeedList = sap.ui.getCore().byId("FeedListFormular");
    if (oFeedList) {
      var aFeeds = oFeedList.mixinLocalData(oFeedList.getData(), oFeedList.getNewData());
        $("<input>", {
          name: 'feeds',
          value: JSON.stringify(aFeeds)
        }).appendTo(jForm);                       
    }
                                  
    // if parameter sap-locale is used then we forward this parameter also to the screenshot servlet
    $("<input>", {
      name: 'sap-ui-language',
      value: sap.ui.getCore().getConfiguration().getLanguage()
    }).appendTo(jForm);
                                  
    // set form to invisible
    jForm.css('display', 'none');
    // send form
    jForm.appendTo('body').submit();
    // remove form directly after usage to avoid unnecessary elements
    jForm.remove();                     
  ]]>               
</javascript-option>

As you can see, the Javascript coding is transferring the business object into JSON and sending it to the URL /abpm/pdfform. The endpoint contains a servlet that is calling the PDF generator. As the data is taken directly from the browser it contains the data the user sees in the browser.

This option can then be executed in the event handling of the scenario by returning it with the findOptionById method like:

else if (CustomerActionEnum.PDF.matches(event.getEventName())) {
  return this.findOptionById(ctx, event.getEventName(), CustomerOptionEnum.PDF_PRINTOUT, null);
}

Usage via Web-Service

If you want to use the PDF generation from the BPM process or from outside you can use the Web-Service provided by aBPM. The service provides the following operations:

  • generate – to generate the PDF. The result is returned as byte array.
  • generateAndAttach – generate the PDF and directly add it as an attachment to the business object. The methods avoids putting the PDF into the BPM context.
  • generatePdfA – Same as generate but the resulting PDF follows the PDF/A rules.
  • getXmlData – Returns a string that contains the business object as XML
  • getXmlFile – Same as getXmlData but the XML structure is returned as byte array for download.

All methods require an aBPM process instance (via aBPM processId) and can only be used as signed in user, anonymous access isn’t allowed because the PDF contain data from the form.

Use as EJB

The direct access of the EJB allows a more performant access for Java based coding. In order to use it you need to create a DC dependency on the “abpm/core/pdf/ear DC” and its public part “api”. Then you can use the PdfGeneratorLocal interface. The methods are exactly the same as the Web-Service operations.

Summary

As shown, aBPM offers an extended support for PDF generation that allows downloads of PDFs as well usage via Web-Service and direct EJB interfaces. The base FOP implementation allows defining complex layouts via XSL-FO.

To report this post you need to login first.

Be the first to leave a comment

You must be Logged on to comment or reply to a post.

Leave a Reply