Using FreeMarker in the Process Editor

Get started using FreeMarker for transformation activities in the Process Editor.

Important: The FreeMarker plug-in is deprecated. We consider using the Transformation Activity for transforming XML message or string content.

Table of Contents

FreeMarker Introduction

Apache FreeMarker is a free Java-based template engine, a Java library that can be used to generate text output by combining variable input data with templates. It includes powerful macro-based capabilities.

Policy Manager supports FreeMarker as one of the languages available for creating Transformation activities in the Process Editor.

FreeMarker is very powerful, and its capabilities are not documented here. For a comprehensive description of the template language, see https://freemarker.apache.org/.

The important thing with FreeMarker is to understand the concept. Essentially what FreeMarker does is to provide a means of creating a template that outputs text with the ability to insert content in the template based on extracting information from an input data model. In the context of the platform, the data model is built from the source message and process context. FreeMarker provides a language for specifying how to insert content from the data model, and it provides a set of control directives for actions such as looping through content and using conditionals.

FreeMarker templates

At its core, a FreeMarker template is just the text that is going to be output. For example, the template below would simply output that piece of static text.

<html>
  <body>
    <p>Hello World</p>
  </body>
</html>

FreeMarker provides a language for inserting values from the data model into the template. In the example below, a variable is used along with some static text:

<html>
  <body>
    <p>Hello ${user}</p>
  </body>
</html>

The template above extracts the user value from the data model and uses it to generate a personalized piece of html. That is, of course, assuming that your data model contains a scalar value stored as user.

Dot Notation

The data model is hierarchical, so if the user value is actually an object, you would access the inner values using a dot notation. For example:

${user.name.lastname}

This is a very important point. The value that you insert must be a scalar value. If your user value was a complex object, and you tried ${user} in your template, the template rendering would fail with an error something like the below:

"${...}" content: Expected a string or something automatically convertible to string (number, date or boolean), but this has evaluated to an extended_hash.

The FreeMarker process activity replaces the content of the specified message variable with the result of running a template over the data model.

You can also use the special "D" prefix to define a default namespace so that you can use dot notation. See Airport API example: Approach 1 below.

This document provides a couple of examples showing ways that you can use this activity for some common use cases.

Array Notation

You can also address elements using array notation rather than dot notation.

You can access the XML document structure by using the contentAsXml helper method, and then addressing the elements. For any XML document using namespaces, the first step is to define the namespaces using the FreeMarker ns_prefixes tag. Using that, it's possible to address elements using array notation.

See Example using array notation below.

Downloading the template-tester utilities

If you're developing FreeMarker templates locally and want to run the commands to test your templates outside the platform, you can download the FreeMarker JAR file from the FreeMarker section of the AkanaInc GitHub project at https://github.com/akanainc/freemarker-activity/tree/master/tools. Go to the \release folder and download template-tester.jar.

The JAR file includes a couple of simple utilities for quickly testing a template against content you provide without installing the Akana FreeMarker Activity for the Akana API Gateway.

The tools mimic the Akana FreeMarker Activity helper methods, creating a top-level object in the data model called message and adding the following methods: contentAsXml, contentAsString, and a limited getProperty methods.

For more information about the FreeMarker utilities, refer to the readme.md file at the above URL.

Developing a FreeMarker Activity: Overview

The FreeMarker process activity allows you to create message content using FreeMarker templates. It provides a powerful content creation mechanism that can address any element in the process and message contexts, including headers, parameters, content, and more. It includes helper methods for returning message context as an XML document structure or a string, for easy addressing using the FreeMarker template syntax.

To use the FreeMarker activity you:

  • Provide a FreeMarker template.
  • Specify the message variable, the content of which you will replace with the results of processing the template.
  • Specify the Content-Type of the resulting message content.

The activity populates the FreeMarker data model with all the variables named in the processContext, most notably the default message variable. For details on the processContext and messageContext, refer to the script API documentation (go to https://help.akana.com/content/current/ag/apidoc_scripting_api_all.htm and choose the correct version for your installation).

There are two added helper methods in each message context:

  • contentAsString—returns the message content as a string.
  • contentAsXml—returns the message content as an XML DOM object.

You can access any of the properties in each context using a dot notation that skips the get prefix on the property. For example, a method in the script API of getTransportHeaders() can be accessed as a property using message.transportHeaders. This accesses the transportHeaders property of the default message context.

Note: while many properties directly return a scalar value, this example returns a Headers map. To use this in a template, you'd need to find a specific instance of a header using something like the following:

${message.transportHeaders.get("User-Agent").value}

JSON to XML: simple example

The example below shows a very simple template for processing a JSON document, with example input and output.

Template:

<#assign m = message.contentAsString?eval>
<name>${m.pet.name}</name>

JSON input:

{
  "pet":{
    "name":"fido",
    "type":"dog"
  }
}

XML output results:

<name>fido</name>

Extracting data from XML

FreeMarker provides an easy way to extract some elements from an XML document with a complex structure and use their values to generate JSON, or even HTML, without having to become an XSLT expert. FreeMarker doesn't eliminate the need for XSLT, but it does provide a simpler alternative for many common uses.

This section includes two examples:

Borrower example

For this example, we'll use a fairly simply document that will be enough to show a lot of the common approaches and pitfalls:

<br:add xmlns:br="http://demo.akana.com/borrower">
  <br:borrower>
    <br:id>111-11-1111</br:id>
    <br:ssn>111-11-1111</br:ssn>
    <br:city>Los Angeles</br:city>
    <br:first>John</br:first>
    <br:last>Smith</br:last>
    <br:line1>12100 Wilshire Blvd</br:line1>
    <br:phone>310-000-0000</br:phone>
    <br:state>CA</br:state>
    <br:zip>90025</br:zip>
  </br:borrower>
</br:add>

The basic idea is that we are going to access the XML document structure by using the contentAsXml helper method, and then addressing the elements. For any XML document using namespaces, the first step is to define the namespaces using the FreeMarker ns_prefixes tag. Using that, it's possible to address elements using array notation (not dot notation).

Example using array notation

Below is an example using array notation, with sample output.

Example:

<#ftl ns_prefixes={"br":"http://demo.akana.com/borrower"}>
{ "result" : "${message.contentAsXml["br:add"]["br:borrower"]["br:last"]}" }

Output:

{ "result" : "Smith" }

Using the "D" prefix to define default namespace

You can also use the special "D" prefix to define a default namespace so that you can use dot notation.

Assigning the result of the helper function to a variable

Another strategy is to assign the result of the helper function to a variable so that you can use it more efficiently, as in the example below.

<#ftl ns_prefixes={"D":"http://demo.akana.com/borrower"}>
<#assign xmlmsg = message.contentAsXml>
{ "result" : "${xmlmsg.add.borrower.last}" }

The above has the same effect as the first template, but is much more readable.

Note: if you have multiple namespaces in your message, pick the one you'll need to access the most for your default, and then remember that you'll have to use array notation with the prefixes to address elements in other namespaces.

Airport API example

For this example, we'll use a fairly simply document that will be enough to show a lot of the common approaches and pitfalls:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:sch="https://soa.smext.abc.org/asws/schemas">
  <soapenv:Header />
  <soapenv:Body>
    <sch:GET_getAirportStatus_InputMessage>
      <sch:airportCode>jfk</sch:airportCode>
    </sch:GET_getAirportStatus_InputMessage>
  </soapenv:Body>
</soapenv:Envelope>

For this example, there are two approaches you could use to get the value of the input parameter, airportCode, using FreeMarker. Both are shown below.

Airport API example: Approach 1

The example below uses dot notation (see Dot Notation above).

<#ftl ns_prefixes={"D":"http://schemas.xmlsoap.org/soap/envelope/","sc":"https://soa.smext.abc.org/asws/schemas"}>
<#assign xmlmsg = message.contentAsXml>
{ "airportCode": "${xmlmsg.Envelope.Body["sc:GET_getAirportStatus_InputMessage"]["sc:airportCode"]}" }

Airport API example: Approach 2

The example below uses the FreeMarker ns_prefixes tag so that elements can be addressed using array notation (see Array Notation above).

<#ftl ns_prefixes={"se":"http://schemas.xmlsoap.org/soap/envelope/","sc":"https://soa.smext.abc.org/asws/schemas"}>
{ "airportCode" : "${message.contentAsXml["se:Envelope"]["se:Body"]["sc:GET_getAirportStatus_InputMessage"]["sc:airportCode"]}" }

Creating an XML Document

The FreeMarker template is essentially just the text you will output, so if you want to create an XML document, you must write the document.

Using the example above, and rewriting output XML rather than JSON, you might end up with something like the below:

<#ftl ns_prefixes={"D":"http://demo.akana.com/borrower"}>
<#assign xmlmsg = message.contentAsXml>
<br:borrower xmlns:br="http://demo.akana.com/borrower">
  <br:last>${xmlmsg.add.borrower.last}</br:last>
</br:borrower>

Note: You'll need to manually structure your document so that it produces valid XML, complete with namespace definitions and prefixes, though if the resulting XML is invalid, the product doesn't generate errors unless you then choose to mediate the XML.

Extracting Data from JSON

JSON data is structured in a similar way to a FreeMarker data model. FreeMarker provides a simple evaluation utility for evaluating a string containing a JSON document and mapping it to a data model.

The following examples will all work with the fairly complex JSON document below:

[
    {
        "category": {
            "id": 100,
            "name": "Dinosaur"
        },
        "name": "Tyrannosaurus",
        "photoUrls": [
            "http://en.wikipedia.org/wiki/Tyrannosaurus#/media/File:Tyrannosaurus_rex_mmartyniuk.png"
        ],
        "tags": [
            {
                "id": 100,
                "name": "reptile"
            },
            {
                "id": 101,
                "name": "dinosaur"
            }
        ],
        "status": "available",
        "id": "100",
        "href": "http://localhost:9900/things/petstore/100"
    },
    {
        "category": {
            "id": 200,
            "name": "Dog"
        },
        "name": "Rover",
        "photoUrls": [
            "http://en.wikipedia.org/wiki/Tyrannosaurus#/media/File:Tyrannosaurus_rex_mmartyniuk.png"
        ],
        "tags": [
            {
                "id": 102,
                "name": "mammal"
            },
            {
                "id": 103,
                "name": "dog"
            }
        ],
        "status": "available",
        "id": "101",
        "href": "http://localhost:9900/things/petstore/101"
    },
    {
        "category": {
            "id": 200,
            "name": "Dog"
        },
        "name": "Fido",
        "photoUrls": [],
        "tags": [
            {
                "id": 102,
                "name": "mammal"
            },
            {
                "id": 103,
                "name": "dog"
            }
        ],
        "status": "available",
        "id": "102",
        "href": "http://localhost:9900/things/petstore/102"
    },
    {
        "id": "1433943941767",
        "category": {
            "id": 100,
            "name": "Dinosaur"
        },
        "name": "Brontosaurus",
        "photoUrls": [],
        "tags": [
            {
                "id": 100,
                "name": "reptile"
            },
            {
                "id": 101,
                "name": "dinosaur"
            }
        ],
        "status": "sold",
        "href": "http://localhost:9900/things/petstore/1433943941767"
    }
]

One of the most complex aspects of this example JSON document is that it has no top-level named objects; it's just an array of objects. Because of this, to extract information from the first item in the array, it's important to address it appropriately, as in the example below.

This:

<#assign m = message.contentAsString?eval>
{ "name" : "${m[0].name}" }

Returns this:

{ "name" : "Tyrannosaurus" }

Note: the <#assign> directive uses the platform's helper function to return the string value of the message content, and uses the FreeMarker eval function to map the JSON document to the data model.

Of course, it's really not that useful to be forced to manually select which element in the array you want to work with; a better approach is to use the <#list> directive:

<#assign m = message.contentAsString?eval>
{
  "name" : [
	<#list m as pet>
		"${pet.name}"<#sep>,
	</#list>
  ]
}

This template iterates through the elements in the top-level array (assigned to m), and assigns each element to a new variable named pet. We can then work on each element in the pet array separately. This template also introduces the <#sep> directive, which is used when you have to display something between items (but not before the first item or after the last item).

Error Handling

If the template fails in the activity, it outputs any exceptions to a debug entry in the message log.

Once you have a template working, the most likely failure scenario is that an element doesn't exist. For example, in the JSON document above, if one of the pet objects didn't have a name, the listing template would fail. To address this, we can use conditionals around elements that might or might not be present. For example:

<#assign m = message.contentAsString?eval>
{
  "name" : [
	<#list m as pet>
		"<#if pet.name??>${pet.name}<#else>${pet.category.name}</#if>"<#sep>,
	</#list>
  ]
}

This example uses the ?? operator in a conditional <#if pet.name??>....</#if> to detect if pet.name exists. In this case, the <#else> directive specifies another value to use, ${pet.category.name}, in the event that the pet object doesn't have a name.

FreeMarker version

The Policy Manager Process Editor supports FreeMarker in the context of the Transformation Details Editor. The platform uses FreeMarker version 2.3.25. FreeMarker functionality is embedded in the product, and does not require any additional installation.

Using FreeMarker in the Process Editor Transformation Activity

When you're writing transformations in the Process Editor, you can choose to craft your transformation in XSLT or FreeMarker. For an example in XSLT, see Transformation Activity: XSLT Example.

Using FreeMarker, you can address any element in the process context and variables, including headers, parameters, content, and more.

To get to the Transformation Details Editor:

  1. Open the Process Editor. For instructions, see Accessing the Process Editor.
  2. In the Process Editor toolbar, click the Transformation tool (see Note: Choosing the Transformation Tool below), and drag it into the Process Editor workspace. For more information, see Transformation Activity.
  3. In the workspace, double-click the Transformation tool to access the Transformation Details Editor. See Using FreeMarker in the Transformation Details Editor below.

Note: Choosing the Transformation Tool

Make sure you choose the Transformation activity: icon.

In some cases, the Process Editor toolbar might also include the FreeMarker plug-in tool: icon. Do not use the plug-in; it is a custom activity that is not guaranteed to be available in all environments, and has been superseded by the transformation activity. Always use the Transformation activity.

Using FreeMarker in the Transformation Details Editor

  1. In the Transformation Details Editor, specify the key details for using FreeMarker in your transformation:
    • Language: choose FreeMarker.
    • Out Variable: Specify the variable from the Out message that the FreeMarker template will use: message or fault.
    • Content Type: Specify the content-type. Start typing a valid content-type and the platform matches from a list of supported values.
    • Template: In the workspace, write or paste the body of the FreeMarker template.
  2. Click Finish.