Introduction
There is a standard way to implement a TrustBuilder configuration including usual locations to store files and scripting best practices.
These are outlined here and it is advised to follow these guidelines to assist with handover between consultants and ease of support.
In short the guidelines cover:
File and directory structure
Workflow organisation
Activity purpose and relationship
Directories and Files - TB_HOME
There is one TBHOME for each application, this can be shared over different trustbuilder instances (across application servers and physical servers). This is the recommended way to organise a TBHOME.
Items marked with (*) are optional.
TB_HOME/
TB_HOME/config.xml
TB_HOME/logback.xml (*)
TB_HOME/myAppWorkflow.xml
TB_HOME/myAppWorkflow
TB_HOME/myAppWorkflow/scripts
TB_HOME/myAppWorkflow/templates (*)
TB_HOME/myAppWorkflow/templates.js (*)
TB_HOME/common
TB_HOME/common/scripts
TB_HOME/common/templates (*)
TB_HOME/common/templates.js (*)
In this case myAppWorkflow.xml is a workflow. The directory myAppWorkflow contains everything that is used by that workflow such as script and templates. If there are common scripts and templates that are used by different workflows then it is the norm to place them in the common directories.
All file names should be descriptive. For instance if there is a script that contains functions that deal with adapter responses name the script something like myApp_adapter-responses.js thus relating the scripts to the workflow and describing the general purpose.
The workflow ID that is configured in TBA does not need to be the same as the workflow file name, but it allows easier mapping between url and workflow.
Workflows and Sub-workflows
An normal workflow can be a parent or a sub-workflow. If a workflow is marked as being Only Sub-workflow within TBA under the workflow section of configuration.
Once marked as Only Sub-workflow a workflow cannot be used as a parent and call other workflows or be a standalone workflow it must be called by another.
Templates
There are two ways to create templates that can be read and populated by scripts.
Separate files with extensions of .htm, .html or .tmpl can be created or variables can be set within Javascript.
It is recommended that when the template is large or contains an amount of formatting markup such as HTML which will likely be used by another department (such as the design department) that this be a template file with extension of .htm .html or .tmpl.
If the template is to be shared with a design department to control the layout then it is more common that a .html extension be used to ease sharing.
If the template is forming a request such as a SOAP message or XML that is likely to be used internally to call another adapter or external service then it is more common that these templates are created in script files as variables.
Example of Templates Defined as Variables
var Response = '<RESPONSE> \
<USERNAME>$username</USERNAME><PASSWORD>$password</PASSWORD> \
</RESPONSE>';
var Error = '<ERROR> \
<STATUS>$status</STATUS> \
<MESSAGE>$message</MESSAGE> \
</ERROR>';
Static Files
Static files are files such as images that can be referenced by the standalone templates.
Any static files need to be stored in a specific directory. Specify the folder where all the static files are stored in the STATIC_FOLDER jndi environment variable.
The value of the STATIC_FOLDER variable is an absolute path to a directory on the server.
In WebSphere this can be set when installing the TrustBuilder application In Tomcat it can be set in context.xml thus:
<Environment name="STATIC_FOLDER" value="/opt/securit/tb\_home/static"/>
Scripting Guidelines
Adapter Activities
When preparing a request to be sent to an adapter activity the script should be set in the Before Adapter Script function. Not in an activity that comes before the adapter activity such as a script contained within a condition activity.
For instance there is a JDBC adapter activity.
A select request must be created that is used by the adapter to select from a database.
This select request should be created in a function defined in the Before Adapter Script of the JDBC adapter activity itself not in any other activity.
Sub-workflow Activity
A sub-workflow should be used to separate blocks of logic or to allow for code reuse.
As for the adapter activity and preparation of data that is to be sent into the sub-workflow should be created within the sub-workflow Before Worfkow Activity Script function of the sub-workflow activity itself. Not in any other activity such as a preceding condition activity.
There is no restriction on how a workItem is used within a sub-workflow. The parent workflow workItem can be re-used or not. This is specified in the sub-workflow activity properties.
Code Reuse
Try to reuse code as much as possilble, this is good practice in any scripting language. If for instance it is noted that some lines of code are written many times with maybe just one variable changing then this code can be moved to a function that accepts an argument for the changing variable.
Then wherever this code is required the function is called.
For instance.
Bad Practice: Code Not Being Reused
function scriptFunction1(workItem) {
var credHeader = workItem.input.parameter("iv-creds"),
arrCred = credHeader.split(", "),
credential = arrCred[1];
}
function scriptFunction 2(workItem) {
var credHeader = workItem.input.parameter("iv-creds"),
arrCred = credHeader.split(", "),
credential = arrCred[1]; }
Good Practice: Code Refactored for Reuse
function scriptFunction1(workItem) {
var cred = getCredential(workItem.input.parameter("iv-creds"));
}
function scriptFunction2(workItem) {
var cred = getCredential(workItem.input.parameter("iv-creds"));
}
function getCredential(header) {
var arrCred = header.split(", ");
return arrCred[1];
}
Function formatting
Mandatory
This guideline is mandatory to get your script function working, it is important that the curly brace sits on the same line as the function definition. This is in order for the functions to be discoverable and selectable in a property box of an activity.
Global and Local Variables
In Javascript a global variable is one that is created outside of any containing function or a variable that is created in a containing function but not preceded with the var keyword.
Global variables should be used with caution as they are overwritten this can be an advantage but can be difficult to monitor and can easily lead to bugs.
For instance if a global variable is set in scriptA and a global variable is set in scriptB with the same name. If scriptB is loaded by the engine first then the value of the variable set in scriptA will be used as the value for the global variable.
Global and Local Variables
var globalOne = "hello";
function setVar(){
globalOne = "goodbye"; //change the value of a global variable
var localOne = "local hello"; //create a local variable can only be read within this function
thinksLocal = "this is also a global"; //this is not local it is global, not preceded with var
}
alert(globalOne); //reads hello
setVar();
alert(globalOne); //reads goodbye
alert(thinksLocal); //can be read is global although has been set within a function
try{
alert(localOne); //this is undefined and throws an error it is a local variable
}
catch(e){
alert(e);
}
SQL and String Concatenation
Creating a query to send to a database using string concatenation exposes a script to potential SQL injection attacks. Where a string that is set as a variable can contain SQL that is then executed against the database.
To avoid this prepared statements and query parameters should be used.
A prepared statement is the query itself with values defined as question marks. The query parameters are the actual values that replace the question marks when the query is run.
When using this technique the values are validated before being sent to the database.
Improper Creation of an SQL Query
function prepareJdbcReq(workItem) {
username = workItem.input.parameter("user");
passwd = workItem.input.parameter("pwd");
sql = "SELECT \* FROM users WHERE user ="+username+" AND password = "+passwd + ";";
workItem.dbInput = tb.jdbcSelectRequest(sql);
}
Correct Creation of an SQL Query with properly scoped variables
function prepareJdbcReq(workItem) {
var username = workItem.input.parameter("user"),
passwd = workItem.input.parameter("pwd"),
sql = "SELECT \* FROM users WHERE user = ? AND password = ? ";
workItem.dbInput = tb.jdbcSelectRequest(sql, [username, passwd]);
}
Type Safety
Javascript is a type less language. Variable types do not need to be defined as they do in other languages such as Java. So a variable can be an integer or a string for instance.
To make a genuine comparison in a condition type save comparitors should be used. These take the format of a triple character set such as === as opposed to a non type safe comparison ==.
Comparing with non type safe can be used if that is the desired outcome. Javascript will attempt to cast the object being compared to that which is expected but it can have undesired effects for example when a boolean is a string.
var thisMayBeTrue;
thisMayBeTrue = 1;
alert(thisMayBeTrue == true); //=\> true the 1 is cast to boolean true by Javascript
alert(thisMayBeTrue === true); //=\> false the value is not a boolean true
thisMayBeTrue = '1';
alert(thisMayBeTrue == true); //=\> true the value is cast from a string to a boolean
alert(thisMayBeTrue === true); //=\> false the value is not a boolean true
thisMayBeTrue = true;
alert(thisMayBeTrue == true); //=\> true no cast needed alert(thisMayBeTrue === true); //=\> true this is a boolean true
thisMayBeTrue = 'true';
alert(thisMayBeTrue == true); //=\> false no cast from string to boolean.
alert(thisMayBeTrue === true); //=\> false as expected not a boolean true
WorkItem Object
It is good practice to keep a clean workItem object throughout the scripts. The best approach is to scope variables into sub objects.
For example:
Poor WorkItem Organisation
function SomeScriptfunc(workItem) {
workItem.user = workItem.input.parameter("user");
workItem.pwd = workItem.input.parameter("pwd");
}
Good WorkItem Organisation
function SomeScriptFunc(workItem) {
workItem.credentials = {
user: workItem.input.parameter("user"),
pwd: workItem.input.parameter("pwd")
};
}
Use Object Constructors
It is more performant to create an object with properties defined and populated as opposed to creating an object and then setting properties and values.
Poor Object Creation
function SomeScriptfunc(workItem) {
workItem.req = {};
workItem.req.user = workItem.input.parameter("user");
workItem.req.pwd = workItem.input.parameter("pwd");
}
Better Object Creation
function SomeScriptfunc(workItem) {
workItem.req = {
user: workItem.input.parameter("user"),
pwd: workItem.input.parameter("pwd")
};
}
Comments
All functions should be commented thus:
/*
Description: A descriptive purpose of the function, what it does.
Previous Activity: The activity that was run before this activity was called, if relevant.
Next Activity: The next activity after this script is run.
*/
function someScriptFunc(workItem) {
// This is inline comment to describe what is happening within the function
}