Custom Business Rules
Introduction
ConnectALL gives you the ability to inject Custom Business Rules into the flows. When ConnectALL is installed, it comes with a default set of flows to enable the message transfer between the connected applications, based on the defined rules. But it is possible to inject custom rules using Message Processors that can be injected into the flows.
Message Processors can be categorized based on the function they perform and we list them below:
- Components are flexible tools that perform business logic implemented in Java and other scripting languages.
- Filters only allow certain messages to continue to be processed in a flow.
- Routers control message flow to route, resequence, or split and aggregate messages in a flow.
- Scopes wrap other message processors to enable them to perform together as a group.
- Transformers convert message payload type and data format to facilitate communication between systems.
ConnectALL extends its message mediation for writing custom business-specific transformation scripts with the help of Business Rules. You can write business rules in the language of your choice. The following scripting languages are supported as of date for writing the business rules.
- Groovy
- Javascript
- Ruby
- Python and
- Java
We provide example business rules, developed for a customer on this page. For consistency, all the example scripts are written in Groovy.
Prerequisites
- Be sure an automation is defined in ConnectALL.
- The fields that you want to use in the mediation (business rule) are mapped.
- Analyze the logs to identify the field names and values being transformed by ConnectALL.
Scripting Variables
Some of the variables that you might need in writing the scripts are given below:
- message: The parent object that will be handed over to the script from which you can read all the content of the properties.
- message.payload: The body of the message that stores the fields and values that are being transferred from source and destination.
Useful APIs
- message.getSessionProperty("property-name"): Useful to read session properties from the message, some of the useful session properties are given below:
- source - retrieves application id of the application
- connection.name - Retrieves automation name
- source.appmodel - Retrieves the internal ConnectionModel object to receive some internal CommentHandler, AttachmentHandler etc.,
- message.payload.getSingleValueField("field-name"): Useful to read the record information from the body of the message.
- field-name is the source field-name usually (but depends on the where you are adding the scripting component in the flow).
- field-name will be either an internal id of the field or label of the field (confirm this from the logs before starting to write your script).
- message.payload.setSingleValueField("field-name","new-field-value"): Useful to overwrite the existing value in the message.
The below table shows some examples of retrieving data from the APIs mentioned above.
Get Automation name | message.getSessionProperty('connection.name') | ||||||
---|---|---|---|---|---|---|---|
Get Project | message.payload.getProject() | ||||||
Get Entity Type | message.payload.getIssueType() | ||||||
Get Application ID | message.getSessionProperty('source') | ||||||
Get Application name | message.getSessionProperty('source.appmodel').getApplicationKey()
|
Adding Fields to the Payload
Starting from version 2.10.3, ConnectALL’s Jira adapter will use field IDs for synchronization. This has an impact on business scripts. The business scripts that you use may contain labels. And these labels need to be replaced with field IDs.
For example,
Before 2.10.3 => ( “Resolution”:“Done”)
After 2.10.3 => (“Customfield_10000”:“Done”)
Notice that the label ‘Resolution’ is replaced with field ID, ‘Customfield_10000’.
So, if you are on version 2.10.3 or above, or upgrading to it, replace the label with ID as shown above. It will allow the business script to function without any issues.
At times, you may want to add new fields to the payload that cannot be retrieved from the source application. In order to do this, make sure you have mapped the fields that you want to push to the destination as a _CONSTANT and add your business rule after the MapValues transformer.
Example
Snippet of BusinessRule
message.payload.getFields().add(new FieldValue(new Name("customfield_10000"),"Some value"))
For using the above statement in your business script, you will also need to add the import statement from the beginning of your business script.
import com.go2group.connectall.config.model.bean.*
Snippet of GenericPoll.xml
<transformer ref="BusinessRules"/>
<transformer ref="MapValues"/>
<logger level="DEBUG" message="Process the message after clone... #[payload]"/>
<scripting:component doc:name="Groovy">
<scripting:script engine="Groovy" file="BusinessRules.groovy"></scripting:script>
</scripting:component>
<payload-type-filter expectedType="com.go2group.connectall.config.model.bean.Entity" />
Steps to write business rule scripts
*Before You Begin*
Make a backup of the ConnectALL core service-installation-directory\apps\ConnectAll-2.xxxx
directory proceeding
- Write the business script in your favorite editor (sample scripts are given below).
- Save the business script in the
mule-installation-directory\conf
directory. - Stop the ConnectALL core service.
- Navigate to the
mule-installation-directory\apps\ConnectAll-2.xxxx
directory. - Open
GenericPoll.xml
in your editor. - Add the scripting component with the business script file name in the flow, with name
CAUpdateRecord
, usually above or below the {{<transformer ref="MapValues"/>}}.- If you add the script before MapValues, then the value retrieved using
message.payload.getSingleValueField("fieldname")
will return the value in the source application. If you add the script after MapValues, then the value retrieved using
message.payload.getSingleValueField("fieldname")
will return the value after doing the value mapping configured in the automation field mapping.<scripting:component doc:name="Groovy"> <scripting:script engine="Groovy" file="BusinessRules.groovy"> </scripting:script> </scripting:component>
CODE
- If you add the script before MapValues, then the value retrieved using
- Restart the ConnectALL core service.
Important Note
Ensure that the groovy script is free from any compilation errors before you start the ConnectALL core service. The ConnectALL core service will not deploy the ConnectALL application when there are any compilation errors in the Groovy script/validation issues in GenericPoll.xml/.
Update record flow snippet with scripting component in GenericPoll.xml
<flow name="CAUpdateRecord">
<vm:inbound-endpoint path="ConnectAll/UpdateRecord" connector-ref="connectAll.queue"/>
<payload-type-filter expectedType="com.go2group.connectall.config.model.bean.Entity" />
<logger level="DEBUG" message="Process the message... #[payload]"/>
<transformer ref="Clone"/>
<logger level="DEBUG" message="Process the message after clone... #[payload]"/>
<transformer ref="GetDestApplication"/>
<logger level="DEBUG" message="Process the message and get dest... #[payload]"/>
<transformer ref="BusinessRules"/>
<transformer ref="MapValues"/>
<logger level="DEBUG" message="Process the message after clone... #[payload]"/>
<scripting:component doc:name="Groovy">
<scripting:script engine="Groovy" file="BusinessRules.groovy"></scripting:script>
</scripting:component>
<payload-type-filter expectedType="com.go2group.connectall.config.model.bean.Entity" />
<choice-exception-strategy>
<catch-exception-strategy when="exception.causedBy(com.go2group.connectall.model.transformer.exception.RetryExhaustedException)">
<logger message=">>>>>>> RetryExhausted Exception ..." level="INFO" doc:name="Logger"/>
<flow-ref name="CAdead.letter" doc:name="dead.letter"/>
</catch-exception-strategy>
<catch-exception-strategy when="exception.causedBy(org.mule.api.transformer.TransformerException)">
<logger message=">>>>>>> TransformerException Exception ..." level="INFO" doc:name="Logger"/>
</catch-exception-strategy>
<catch-exception-strategy doc:name="Log Error">
<logger message=">>>>>>> General Exception ..." level="INFO" doc:name="Logger"/>
</catch-exception-strategy>
</choice-exception-strategy>
</flow>
Example Scripts
Example 1: Append a static text to the existing value
In this below example, we have used a Groovy component script to prefix the summary of a Jira ticket with [ConnectALL].
This is a simple script, and it will not have any restrictions on the automation being processed. This rule might be applied to all the automations that are configured in ConnectALL.
Assumptions
- An automation is configured between Jira and HPE QC.
- A summary field of Jira Bug and HPE QC Defect are mapped.
- A summary field is a mandatory field and the value will never be null.
Groovy Script
BusinessRules.groovy
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
Logger log = LogManager.getLogger('scriptname.groovy');
def summary = message.payload.getSingleValueField("Summary");
log.info "Applying Custom Business Rules";
if(summary.startsWith("[ConnectAll]")) {
log.info "Summary field is prefixed with ConnectAll.";
} else {
summary = "[ConnectAll] " + summary;
message.payload.setSingleValueField("Summary", summary);
log.info "Concatenated ConnectAll prefix to Summary field";
}
log.info "Business Rules transformation completed";
return message.payload;
Rule Logic
Get the value of the summary field from message.payload.
def summary = message.payload.getSingleValueField("Summary");
- Check that the summary field is already prefixed with "ConnectAll".
if(summary.startsWith("[ConnectAll]")) {
- Concatenate a string "[ConnectAll]" with Summary field.
summary = "[ConnectAll] " + summary;
- Set the concatenated summary back to message.payload.
message.payload.setSingleValueField("Summary", summary);
- Return message.payload object.
return message.payload;
Once you add this script to the mule-installation-directory\conf folder, and make the necessary changes in the GenericPoll.xml file, you can see every new Jira ticket created via ConnectALL is prefixed with the text [ConnectAll].
Example 2: Stop syncing a record conditionally
In this example, we will prevent the record from synchronizing to the destination when the Jira Summary field starts with “DRAFT”.
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
Logger log = LogManager.getLogger('scriptname.groovy');
def Jira_title = message.payload.getSingleValueField("Summary");
if (Jira_title.startsWith("DRAFT")) { // if Summary starts with DRAFT
return org.mule.transport.NullPayload.getInstance();
} else {
return message.payload;
}
We need to add an additional line in GenericPoll.xml after the groovy script invocation to stop the processing of empty messages as shown below:
<scripting:component doc:name="Groovy">
<scripting:script engine="Groovy" file="BusinessRules.groovy">
</scripting:script>
</scripting:component>
<payload-type-filter expectedType="com.go2group.connectall.config.model.bean.Entity" />
Example 3: Copy the value of one field to another
In this example, we copy a value of one field in the source to another field in the destination.
Assumptions
- An automation is created in ConnectALL between Rally and Jira with the name "RallyJiraEpic".
- The Summary and Epic Name fields in your Jira "Epic" are mapped to the Name and the Formatted ID fields of yourRally Feature respectively.
- For a field whose value needs to be copied from another field, set the sync to uni-direction pointing from source to destination.
Logic
The value of the Rally Feature "Name" should be updated in Jira for both Summary and EPIC Name fields.
Script
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
Logger log = LogManager.getLogger('scriptname.groovy');
// Application Link Name (Case Sensitive)
def appLinkToApplyRule = "RallyJiraEpic";
// Source Field from which the value is to be copied from
def srcFieldIdToBeCopied = "Name";
// Source Field to which the value is to be set to
def srcFieldIdToBeModified = "FormattedId";
def appLinkBeingProcessed = message.getSessionProperty('connection.name')
def valueTobeCopied = message.payload.getSingleValueField(srcFieldIdToBeCopied);
// Field Copy Rule
if(appLinkBeingProcessed == appLinkToApplyRule) {
log.info "Copy the value from : $srcFieldIdToBeCopied to: $srcFieldIdToBeModified";
message.payload.setSingleValueField(srcFieldIdToBeModified,valueTobeCopied);
} else {
log.info "Field copy rule cannot be applied for this applink : $appLinkBeingProcessed";
}
log.info "Business Rules transformation completed";
return message.payload;
Example 4: Concatenate values of multiple field values into one
In this example, we concatenate multiple fields into one single field.
Assumptions
- An application is defined in ConnectALL between Jira and ALM with name
JiraQCDefectSync.
- Fields to be concatenated are added in the field mapping.
Logic
Concatenate Summary, key, Sprint Name and result in ALM Summary as [Jira Key] + " - " + [Sprint] + " - " [Jira Summary]
Groovy Script
/**
* Custom Business script for concatenating a set of fields to a single field.
*
* <pre>Handles Multiple Application Links and can be customized at the application
* link level on what fields to be concatenated and which string needs to be used as a seperator.
* </pre>
*
*/
// Use 'CONST:' as prefix if you want to concatenate custom strings other than the fields of the message
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
Logger log = LogManager.getLogger('scriptname.groovy');
def CustStr = 'CONST#'
// Define Application Rule, that is what fields to be concatenated in which application link
def link1Config = [linkName:'Applink1', concatinationFields:["id","$CustStr-","summary","$CustStr-","priority"], intoField:"description"]
def link2Config = [linkName:'BusinessRulesDemo', concatinationFields:["id","$CustStr-","priority","$CustStr-","Sprint"], intoField:"user-02_CONSTANT"]
// Add your application link config to this list for processing
def rulesConfig = [link1Config ,link2Config ]
// Application link being processed
def appLinkBeingProcessed = message.getSessionProperty('connection.name')
def getValue = { field ->
if(field.startsWith(CustStr))
field.replaceFirst(CustStr,'')
else
message.payload.getSingleValueField(field);
}
def setValue = { field , value ->
message.payload.setSingleValueField(field , value );
}
/**
* Process Rule based on the configuration defined.
*/
def processRule = { entry ->
log.info "Processing rule $entry.linkName on fields $entry.concatinationFields"
def concatenatedValue = ""
entry.concatinationFields.each { field ->
def fieldValue = getValue(field)
concatenatedValue = concatenatedValue + fieldValue
}
setValue(entry.intoField,concatenatedValue)
log.info "Processing completed for $entry.linkName [ Value $concatenatedValue copied into field $entry.intoField ]"
}
/**
* Main Function to start the process
*/
rulesConfig.each { entry ->
if(entry.get('linkName') == appLinkBeingProcessed )
processRule(entry)
else
log.info "Dont apply rule on ${entry.get('linkName')}"
}
return message.payload;
Master Script
Starting from version 2.10.29.2, ConnectALL provides you with a master script that controls and calls out the other business scripts that you may have deployed. Provided below are the steps that you have to do to enable the master script to take control and execute.
- Ensure that the master-script.groovy file is available in the following location:
MULE_HOME/conf - Create a new folder by name ‘UserScripts’ inside the Database folder(C:\ConnectALLmulesoft\mule-standalone-3.9.0\database).
- Place all the scripts that you want to be executed inside the UserScripts folder.
Info
If you want a script to be executed,
- Before the value transformation, the file name should have the prefix, ‘preMapValues’ (example: preMapValuessample1.groovy).
- After the value transformation, the file name should have the prefix, ’postMapValues’ (example:postMapvaluessample2.groovy).
The preMapValues and postMapValues scripts will be executed in ascending order (alphabetically). If you have numbers as prefixes, then numbers will be given priority (followed by alphabets). The file names that start with an upper case alphabet will be executed before a file name that starts with a lower case alphabet. For example, if you have three file names — preMapValuesaafile.groovy, preMapValuesNewfile1.groovy, and preMapValuesAlltest.groovy, they will be executed in the following order:
- preMapValuesAlltest.groovy
- preMapValuesNewfile1.groovy
- preMapValuesaafile.groovy
When using the master script approach, you don’t have to restart the ConnectALL core service when you add a new script or modify a script.