Twirl

Twirl is a templating tool from Play. You can call for instructions with prefix @. For example, you can call some template by typing @TemplateName(params) in your Companion for Delivery file with the parameters needed.

For more information, please read the official documentationopen in new window.

Templating

By using twirl we can create templates for your business rules in Companion for Delivery format. This is useful for quickly creating new business rules.

This will also simplify your code review by removing a lot of duplicated xml code.

Check the below use case.

Use case

Template definition

First of all, you need to create your template file ending by .companion.template.xml.

On the first lines, import the Companion for Delivery's dependencies needed for the compilation.

@import fr.cantor.c4d.xml.extensions.Extensions._ # imports c4d instructions.
@import fr.cantor.c4d.xml.extensions.CompanionParams # imports CompanionParams object type, used by every Companion for Delivery template.

Then, write your constructor which define all the parameters. By convention, the first one must be CompanionParams which contain context and workspace defined in maven profile, followed by the specific ones. It's possible to define default values for some parameters.

@(config: CompanionParams,
        id: String,
        name: String,
        setupGroup: String,
        jsContent: Any,
        dependencies: Array[Xml] = Array(),
        additionalBindings: Any = {},
        description: String = "",
        validities: Array[String] = Array(),
        allTypesValid: Boolean = false,
        runPrivileged: Boolean = true,
        onApprove: Boolean = false)

Then, define the XML template using the constructor parameters that will be used to generate your file.

<STEP-ProductInformation ContextID="@config.context" WorkspaceID="@config.workspace" xmlns="http://www.stibosystems.com/step">
    <BusinessRules>
        <BusinessRule ID="@id" Scope="Global" Type="Action" RunPrivileged="@runPrivileged">
            <Name>@name</Name>
            <Description>@description</Description>
            <SetupGroupLink SetupGroupID="@setupGroup"/>
            @if(onApprove){<OnApprove ApproveSetup="TriggerAndApproveNewParts"/>}
            @for(dependency <- dependencies) {@dependency}
            <Configuration>
            @gzipBase64 {
                @splits {
                    @split("plugin_applies_if_config").apply {
                        <?xml version="1.0" encoding="UTF-8"?>
                    }
                    @split("plugin_config").apply {
                        <?xml version="1.0" encoding="UTF-8"?>
                        <BusinessRule>
                            <BusinessPlugin BusinessRulePluginID="JavaScriptBusinessActionWithBinds">
                                <Parameters>
                                    <Parameter ID="Binds" Type="java.lang.String">@escapeXml {
                                        <?xml version="1.0" encoding="UTF-8"?>
                                        <BindMap>
                                            <Bind Alias="node" ParameterClass="null" contractID="CurrentObjectBindContract" />
                                            <Bind Alias="manager" ParameterClass="null" contractID="ManagerBindContract"/>
                                            <Bind Alias="logger" ParameterClass="null" contractID="LoggerBindContract"/>
                                            @additionalBindings
                                        </BindMap>
                                    }
                                    </Parameter>
                                    <Parameter ID="ErrorMessages" Type="java.lang.String"></Parameter>
                                    <Parameter ID="JavaScript" Type="java.lang.String">@jsContent</Parameter>
                                </Parameters>
                            </BusinessPlugin>
                        </BusinessRule>
                    }
                }
            }
            </Configuration>
            <ValidObjectTypes AllObjectTypesValid="@allTypesValid">@for(validity <- validities) {
                <ValidObjectType ID="@validity"/>}
            </ValidObjectTypes>
        </BusinessRule>
    </BusinessRules>
</STEP-ProductInformation>
 

 
 
 
 
 
 

















 




 







 
 




Here you see that it's a global business rule template with default bindings: node, manager and logger. You can notice that jsContent is filled with another call to instruction. This one is the JavaScript code imported that we are going to write.

Template code

This template will use JavaScript file inclusion for jsContent variable.

Create a file MyBusinessActionScript.js in your project hierarchy. Having a file named *.js allows your IDE to auto-complete your code.

Then, write your global business rule content as follows:

function myBusinessAction(node, logger){
    logger.info(node.getName());
}

myBusinessAction(node, logger);

Template usage

Once your template and your JavaScript file are done, create your business action Companion for Delivery file ending by .companion.xml: MyBusinessAction.companion.xml.

First, import dependencies and your template:

@import fr.cantor.c4d.xml.extensions.CompanionParams
@import fr.cantor.c4d.xml.extensions.Extensions._
@import <path.to.template>.xml._

Here again CompanionParams is used for compilation.

After that, define your constructor:

@(config: CompanionParams)

No parameter is needed for this example except for the CompanionParams.

Then, call your template with your parameters:

@GlobalBusinessAction(
    config = config,
    id = "MyBusinessAction",
    name = "My Business Action",
    setupGroup = "ImportBusinessRules",
    validities = Array("PRODUCT"),
    jsContent = file("path/to/JavaScript/MyBusinessActionScript.js", true)
)

With our template defined, we call it with the @ followed by its name. Then insert all the required parameters. In this way, you can define any other business action just by defining those parameters.

Your STEPXML is now ready to be compiled and imported into STEP Workbench.

In summary:

  • We made a reusable XML structure.
  • We are able to define new business actions just by creating new short files.
  • We can take advantage of our IDE to write proper code.

Extensions

To facilitate certain aspects of twirl and avoid writing complicated code in templates, Companion contains some extensions that can be used as functions in twirl files. You have already seen them in the use case above.

absolutePath

The absolutePath function return an absolute path from the current module

  • Syntax: @absolutePath(String strPath)
  • Parameters
    • strPath: a relative path of the file to import from src/main/step
  • Return value: Path
  • Use cases: Get the absolute path of a file from any module
  • Example:
    • @absolutePath("path/to/my/file.csv")

base64

The base64 function is used to transform a text into a base64 string.

  • Syntax: @base64(Object value) or @base64(byte[] contentBytes)
  • Parameters
    • value: a value containing the string to encode
    • contentBytes: an array of byte containing the string to encode
  • Return value: Xml (scala)
  • Use cases: Used by decompiler inside outbound integration endpoint
  • Example:
    • @base64("My string")
    • @base64{My string}

csvRecords

The csvRecords function is used to transform a CSV file into a list of maps using first line as key.

  • Syntax: csvRecords(String strPath[, char delimiter, char quote])
  • Parameters
    • strPath: a relative path of the CSV file from src/main/step
    • delimiter (optional, default value : ;): the delimiter used in the CSV file
    • quote (optional, default value : "): the quote used in the CSV file
  • Return value: Iterator<Map<String, String>> (scala)
  • Use cases: Can be used to create loop inside twirl file with variables
A full example of usage

CSV file (context.csv):

id;name;locale;dimension_point_1;dimension_point_2
FR_fr-FR;France - Français;fr_FR;FR;fr-FR
FR_en-UK;France - Anglais;en_GB;FR;en-UK
GL;GL;;AllCountries;std.lang.all
| id       | name              | locale | dimension_point_1 | dimension_point_2 |
|----------|-------------------|--------|-------------------|-------------------|
| FR_fr-FR | France - Français | fr_FR  | FR                | fr-FR             |
| FR_en-UK | France - Anglais  | en_GB  | FR                | en-UK             |
| GL       | GL                |        | AllCountries      | std.lang.all      |

Twirl file

@import fr.cantor.c4d.xml.extensions.CompanionParams
@import fr.cantor.c4d.xml.extensions.Extensions._

@(config: CompanionParams)
<STEP-ProductInformation ContextID="@config.context" WorkspaceID="@config.workspace" xmlns="http://www.stibosystems.com/step">
    <ContextList>
        @for(context <- csvRecords("context.csv")) {
            <Context ID="@context("id")" Locale="@context("locale")">
                <Name>@context("name")</Name>
                <DimensionPointLink DimensionPointID="@context("dimension_point_1")"/>
                <DimensionPointLink DimensionPointID="@context("dimension_point_2")"/>
            </Context>
        }
    </ContextList>
</STEP-ProductInformation>

Generated file :

<STEP-ProductInformation ContextID="Context1" WorkspaceID="Main" xmlns="http://www.stibosystems.com/step">
    <ContextList>
        
            <Context ID="FR_fr-FR" Locale="fr_FR">
                <Name>France - Français</Name>
                <DimensionPointLink DimensionPointID="FR"/>
                <DimensionPointLink DimensionPointID="fr-FR"/>
            </Context>
        
            <Context ID="FR_en-UK" Locale="en_GB">
                <Name>France - Anglais</Name>
                <DimensionPointLink DimensionPointID="FR"/>
                <DimensionPointLink DimensionPointID="en-UK"/>
            </Context>
        
            <Context ID="GL" Locale="">
                <Name>GL</Name>
                <DimensionPointLink DimensionPointID="AllCountries"/>
                <DimensionPointLink DimensionPointID="std.lang.all"/>
            </Context>
        
    </ContextList>
</STEP-ProductInformation>

escapeXml

The escapeXml function allows you to escape a Xml text.

  • Syntax: @escapeXml(Object value)
  • Parameters
    • value: a value containing the Object to escape
  • Return value: Xml (scala)
  • Use cases: Used by decompiler inside business rules...
  • Example:
    • @escapeXml{<LocalizableMessageTemplateBundle></LocalizableMessageTemplateBundle>}

file

The file function allows you to import the contents of another file.

  • Syntax: @file(String strPath[, boolean escape])
  • Parameters
    • strPath: a relative path of the file to import from src/main/step
    • escape (optional, default value : false): escape Xml char (useful for js code)
  • Return value: Xml (scala)
  • Use cases: Business rule code, portal screen, collection formula
  • Example:
    • file("BusinessRules/MyJavascriptFile.js", true)
    • @file("PortalConfigurations/WebUIs/MY_PORTAL/loginscreens/loginscreen.xml")

files

The files function allows you to import the contents of the files in another directory.

  • Syntax: @files(String strPath)
  • Parameters
    • strPath: a relative path of the directory to import from src/main/step
  • Return value: Xml (scala)
  • Use case: Portal screens
  • Example:
    • @files("PortalConfigurations/WebUIs/MY_PORTAL/screens/")

fileToGzipBase64

The fileToGzipBase64 function allows you to import the contents of another file in gzip base64 format.

  • Syntax: @fileToGzipBase64(String strPath)
  • Parameters
    • strPath: a relative path of the file to import from src/main/step
  • Return value: Xml (scala)
  • Use cases: Inbound integration endpoint sample file
  • Example:
    • @fileToGzipBase64("IntegrationEndpoints/Inbound_Integration_Endpoints/sampleFile.xml")

gzipBase64

The gzipBase64 function is used to transform a text into a gzip base64 string.

  • Syntax: @gzipBase64(Object value)
  • Parameters
    • value: a value containing the Object to encode
  • Return value: Xml (scala)
  • Use cases: Used by decompiler inside outbound integration endpoint, business rules...
  • Example:
    • @serializeBase64(file("xslt/myXSLT.xslt").toString())

serializeBase64

The serializeBase64 function is used to transform an object into a base64 string.

  • Syntax: @serializeBase64(Object value)
  • Parameters
    • value: a value containing the Object to encode
  • Return value: Xml (scala)
  • Use cases: Used for XSLT files
  • Example:
    • @serializeBase64(file("xslt/myXSLT.xslt").toString())

IDE Integration

IntelliJ IDEA

Handle twirl file in IntelliJ:

  • Install Scala plugin: From Plugins, browse repositories, type Scala and install Scala LANGUAGES from JetBrains.
  • Add custom extension: From Settings, Editor, File types, Play 2 and add *.companion.xml, *.companion.template.xml.
  • Mark repository: After a first maven build, right click on folder target/companion-generated-sources/scala and mark it as Generated Source Root

Generated STEPXML are then in target/classes/[..]/<fileName>.step.xml.

Last Updated: