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 documentation.
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
- strPath: a relative path of the file to import from
- 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
- strPath: a relative path of the CSV file from
- 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)
- strPath: a relative path of the file to import from
- 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
- strPath: a relative path of the directory to import from
- 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
- strPath: a relative path of the file to import from
- 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, typeScala
and installScala 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 asGenerated Source Root
Generated STEPXML are then in target/classes/[..]/<fileName>.step.xml
.