Groovy DSL Rules
Overview
The ability to create Groovy DSL rules is available since FIXEdge Java 1.10.3 release.
Groovy DSL (Domain Specific Language) allows non-programmers to intuitively author and maintain business rules for FIXEdge Java in a much simpler way compared to programming in pure Groovy or Java.
Groovy DSL covers most use cases in FIX protocol-defined workflows: routing to endpoints, FIX message transformation, and handling of important session management events. It supports both a declarative style of writing rules for unconditional routing “from <endpoint A> to <endpoint B>” and an imperative style where DSL conditional logic and executable actions are expressed explicitly for a given session and selected set of fields. The DSL message rules are defined by the description and bound for the processing of incoming messages from a particular session or group of sessions.
The DSL-defined business rules are stored in a way similar to the rules expressed in Groovy or Java. The rules.groovy configuration file allows mixing and matching DSL with pure Groovy and Java code for backward compatibility and complex cases with message enrichment. The processing of DSL-expressed rules is as efficient as Groovy and Java written rules.
General Groovy DSL rule structure
Groovy DSL rules can be configured in the rules.groovy configuration file which also remains compatible with the pure Java and Groovy implementation of rules.
There are three optional blocks in the rulesDSL structure:
- messageRules for message rules
- eventRules for event rules
- routing for unconditional routing rules
Groovy DSL rules must be grouped in these blocks respectively.
General Groovy DSL rule structure:
import com.epam.fej.routing.RoutingContext import static dsl.CommonRulesDsl.rulesDSL rulesDSL(routingContext as RoutingContext) { messageRules { messageRule(description) {...} messageRule(description) {...} } eventRules { eventRule(description) {...} eventRule(description) {...} } routing { from "sourceId" to "destinationId" } }
Imported classes are described in the sysconf/fej-routing.xml file.
Or/And conditions
Logical And/Or blocks can be used inside conditional blocks (messageRule: source, condition; eventRule: condition).
And
And condition means that instructions will be executed sequentially. If one is false, then the others are not executed and the entire expression returns 'false'. If all are true, then the entire expression returns 'true' also.
Syntax:
and { instruction1 instruction2 }
Or
Or condition means that instructions will be sequential. If one is true, then the others are not executed and the entire expression returns 'true'. If all are false, then the entire expression returns 'false' also.
Syntax:
or { instruction1 instruction2 }
Default
If no logical block is specified inside the conditional block then the instructions will be connected via And by default.
Unconditional routing rule
The routing block is intended to route all messages between two endpoints unconditionally.
Syntax:
routing { from <source ids> to <destination ids> ... from <source ids> to <destination ids> }
<source ids> list represents the list of source session IDs.
<destination ids> list represents the list of destination session IDs.
Example:
FIXEdge Java must send all messages from 'session2' and 'session4' sessions to 'session3', 'session2, and 'session4' destination sessions. And send all received messages from the 'session3' session to the 'session4' destination session.
import com.epam.fej.routing.RoutingContext import static dsl.CommonRulesDsl.rulesDSL rulesDSL(routingContext as RoutingContext) { routing { from "session2", "session4" to "session3", "session2", "session4" from "session3" to "session4" } }
Message rule
messageRule block is intended to build a rule for message processing. It can contain instructions for:
- Filtering of incoming and outgoing FIX messages
- Transformation of incoming and outgoing FIX messages
- Routing of incoming and outgoing FIX messages
It is also possible to route one message to multiple destinations or to the same destination multiple times.
More than one rule from the messageRule block can be applied to the message. Groovy DSL message rules will be applied in the order set in the rules.groovy configuration file.
Syntax:
messageRule(description){ source { //instructions, optional } condition { //instructions, optional } action { //instructions } }
Each messageRule block contains 4 sections:
Section | Description |
---|---|
Description | Mandatory section. This section provides a string with a free text - description of the business rule. |
Source | Optional section. This section defines instructions for primary message filtering. The source section defines a source from which the FIX message can come. If the message source coincides with the source defined in the Source section then the rule will be applied to the message. If the Source section is not specified then the rule will be applied to FIX messages from all sources. This filter can be applied on a static basis without additional effect at runtime. The Source section is the SourceCondition implementation |
Condition | Optional section. This section contains a set of pre-defined criteria. The application checks FIX messages on specified criteria and if it is met the instructions will be executed. If the Condition section is not specified then all messages will be maintained in accordance with the Action section. The Condition section is the RuleCondition implementation. |
Action | Mandatory section. This section defines the instructions that must be performed when the BL rule is being applied to a message. The Action section must contain at least one instruction. The Action section is the RuleAction implementation. |
Source section
Endpoint parameters
The source from which the FIX message can come for processing may be defined by endpoint parameters:
- id <regex>
- targetCompId <regex>
- targetSubId <regex>
- targetLocationId <regex>
- senderCompId <regex>
- senderSubId <regex>
- senderLocationId <regex>
<regex> means that the regular expression or the full name of the parameter can be specified.
Example:
The rule will be applied to the messages, which sources meet the following criteria: session ID must start with session' and the targetCompId is 'TargetCompID1' or the senderCompId contains 'Sender'.
import com.epam.fej.routing.RoutingContext import static dsl.CommonRulesDsl.rulesDSL rulesDSL(routingContext as RoutingContext) { messageRules { messageRule("Filter source by endpoint parameters") { source{ id "session*" or { targetCompId "TargetCompID1" senderCompId ".*Sender.*" } } } } }
Session
The source from which the FIX message can come for processing may be defined by the session parameters. Works similarly to the previous one, but is used for visual separation and grouping (e.g. per session).
Example:
The rule will be applied to the messages, which sources meet the following criteria: session senderCompId contains 'Sender' and either session id is 'session1' and targetCompId is 'TargetCompID1' or session id is 'session2' and targetCompId 'TargetCompID2'.
import com.epam.fej.routing.RoutingContext import static dsl.CommonRulesDsl.rulesDSL rulesDSL(routingContext as RoutingContext) { messageRules { messageRule("Filter source by endpoint parameters") { source{ senderCompId ".*Sender.*" or{ session { id "session1" targetCompId "TargetCompID1" } session { id "session2" targetCompId "TargetCompID2" } } } } } }
Groups
The source from which the FIX message can come may be defined by the resource group. Logical conditions can be used inside.
Instruction | Description |
---|---|
contains <groups list> | The source is all groups from the proposed list. <groups list> - string list with the group names. Example: groups { contains "A", "B", "C" } |
contains <filter action> from <groups list> | The source is the group(s) from the list which satisfies the <filter action>. <filter action> may be set to one of the following values: any | all | none. <groups list> - string list with the group names. Example: groups { contains any from "A", "B", "C" } |
size <num> | The source is the group(s) with the specified size. <num> - integer, the size of the group. Example: groups { size 5 } |
size_more <num> | The source is the group(s) which size is more than the specified value. <num> - integer, the minimum size of the group. Example: groups { size_more 5 } |
size_less <num> | The source is the group(s) which size is less than the specified value. <num> - integer, the maximum size of the group. Example: groups { size_less 5 } |
Example:
The rule will be applied to the messages, which sources meet the following criteria: the source contains all 'A', 'B', and 'C' groups and the group size is 5, or the group list contains one of the 'B', D' groups and the group size is more than 3 and less than 6.
import com.epam.fej.routing.RoutingContext import static dsl.CommonRulesDsl.rulesDSL rulesDSL(routingContext as RoutingContext) { messageRules { messageRule("Filter source by groups") { source { or { groups { contains "A", "B", "C" size 5 } groups { contains any from "B", "D" size_less 6 size_more 3 } } } } } }
Available destinations
The source from which the FIX message can come may be defined by the destination endpoints contained in specified Groups.
Instruction | Description |
---|---|
size <num> | The source is the group(s) for which the number of available destinations equals <num>. <num> - integer, the number of available destinations for the group. Example: available_destinations { size 4 } |
size_more <num> | The source is the group(s) for which the number of available destinations is more than <num>. <num> - integer, the minimum number of available destinations for the group. Example: available_destinations { size_more 2 } |
size_less <num> | The source is the group(s) for which the number of available destinations is less than <num>. <num> - integer, the maximum number of available destinations for the group. Example: available_destinations { size_less 8 } |
quantity <num> by_groups <groups list> | The source is the group(s) from the list for which the number of available destinations equals <num>. <groups list> - string list with the group names. At least one group from the list must belong to the endpoint. Example: available_destinations { quantity 4 by_groups "A", "B", "C" } |
quantity <comparator> than <num> by_groups <groups list> | The source is the group(s) from the list for which the number of available destinations satisfies the <comparator>. <comparator> - may be set to one of the following values: more | less. <groups list> - string list with the group names. At least one group from the list must belong to the endpoint. Example: available_destinations { quantity more than 4 by_groups "A", "B", "C" } |
Example:
The rule will be applied to the messages, which sources meet the following criteria: the number of available destination endpoints by the group is 4, or the number of available resources received by the provided groups 'A', 'B', and 'C' is less than 8.
import com.epam.fej.routing.RoutingContext import static dsl.CommonRulesDsl.rulesDSL rulesDSL(routingContext as RoutingContext) { messageRules { messageRule("Filter source by number of available destinations ") { source { or { available_destinations { size 4 } available_destinations { quantity less than 4 by_groups "A", "B","C" } } } } } }
Custom
If there is a need to write customized source conditions, FIXEdge Java provides the ability to use the Custom block to describe the required behavior. For example, for the customized endpoint this block can be combined with all the previous ones, and several of them can be used.
If any external classes are used, they must be imported.
Syntax:
source {custom {source → <some actions that will return a boolean value in the end> }}
Example:
The rule will be applied to the messages, which source meets the following criteria: the session id is 'session1' or the group size equals 5, and the number of available destinations received through the groups of this resource is greater than or equal to 4.
import com.epam.fej.routing.RoutingContext import static dsl.CommonRulesDsl.rulesDSL rulesDSL(routingContext as RoutingContext) { messageRules { messageRule("Filter source by number of available destinations, endpoint id and groups size, using custom implementation") { source { or { id "session1" groups { size 5 } } custom { source -> Set<String> groups = source.getGroups() routingContext.getAvailableDestinationsByGroup(groups).size() >= 4 } } } } }
Condition section
The message received from the selected Source can be dynamically filtered by:
- field content (single or group conditions)
- header content
- message type
- current date and time
- existence of the field(s)
- non-existence of the field(s)
- existence of the destination(s)
- non-existence of the destination(s)
- message(s) match
- message(s) mismatch
- custom condition
Field content
It is possible to filter messages from the selected Source both by a single field value and a group of values for the same field.
Single construct
For a single construct, it is enough to specify the field number and one condition against which the field should be checked.
Syntax:
field <tagNum> <condition>
where <tagNum> is an integer, field number and <condition> is a condition for checking the value of the field.
The list of available conditions:
Condition | Description |
---|---|
greater <num> | The value of the field is greater than <num>. <num> - integer or double. Example: condition { field 34 greater 2 } |
greater_or_equal <num> | The value of the field is greater or equal to <num>. <num> - integer or double. Example: condition { field 34 greater_or_equal 2 } |
less <num> | The value of the field is less than <num>. <num> - integer or double. Example: condition { field 34 less 2 } |
less_or_equal <num> | The value of the field is less or equal to <num>. <num> - integer or double. Example: condition { field 34 less_or_equal 2 } |
value <value> | The value of the field equals to the <value>. <value> - integer or double or string (regex). Example: condition { field 58 value "TestText" } |
length <num> | The length of the field is equal to the <num>. <num> - integer. Example: condition { field 58 length 5 } |
length <comparator>than <num> | The length of the field is compared to <num>. <num> - integer. <comparator> - 'greater' or 'greater_or_equal' or 'less or less_or_equal'. Example: condition { field 58 length greater than 5 } |
contains <string list> | The tag value contains all elements from the <string list>. <string list> - string, list of values. Example: condition { field 58 contains "Test", "Text" } |
from <string list> contains <action> | The tag value contains <action> elements from the <string list>. <string list> - string, list of values. <action> - 'all' or 'any' or 'none'. Example: condition { field 58 from "Test", "Text" contains none } |
begins_with <prefix> | The tag value begins with <prefix>. <prefix> - string. Example: condition { field 58 begins_with "T" } |
Example:
The received messages are passed to the Condition section. The filtering condition is the following: field 35 equals "D", the value of the 38 field is less than 5 and greater than 2, and 58 field begins with the "Test" and has a length greater than 2.
import com.epam.fej.routing.RoutingContext import static dsl.CommonRulesDsl.rulesDSL rulesDSL(routingContext as RoutingContext) { messageRules { messageRule("Condition to check message type") { field 35 value "D" field 38 less 5 field 38 greater 2 or{ field 58 begins_with "Test" field 58 length greater than 2 } } } }
Group construct
For a group construct, the field number and a list of conditions to be applied to it must be specified.
Syntax:
field ( <num> ) { <condition 1> <condition 2> . . . <condition n> }
The list of available conditions:
Condition | Description |
---|---|
value <comparator> than <num> | The value of the field is compared to <num>. <num> - integer or double. <comparator> - 'greater' or 'greater_or_equal' or 'less' or 'less_or_equal'. Example: condition { field (34) { greater 2 less 10 } } |
value <value> | The value of the field equals to the <value>. <value> - integer or double or string (regex). Example: condition { field (58){ or{ value "Test.*" value 5 value 1.3 } } } |
length <num> | The length of the field equals to the <num>. <num> - integer. Example: condition { field (58) { length 5 } } |
length <comparator> than <num> | The length of the field is compared to <num>. <num> - integer. <comparator> - 'greater' or 'greater_or_equal' or 'less' or 'less_or_equal'. Example: condition { field (58) { length greater than 5 length less than 25 } } |
contains <string list> | The value of the field contains all elements from the <string list>. <string list> - string, list of values. Example: condition { field (58){ contains "Test", "Text" } } |
contains <action> from <string list> | The tag value contains <action> elements from the <string list>. <string list> - string, list of values. <action> - 'all' or 'any' or 'none'. Example: condition { field (58){ contains any from "Test", "Text" } } |
begins_with <prefix> | The value of the field begins with <prefix>. <prefix> - string. Example: condition { field (58) { begins_with "T" } } |
Header content
It is possible to filter messages from the selected Source by header content. Logical conditions And/Or can be applied.
Syntax:
header { <header condition 1> . . . <header condition n> }
Header Condition | Description |
---|---|
field (<name>) {<field condition>} | The value of the header is checked against <name>. More details about the <field condition> is below. The same as field content with group construct. <name> - string, name of the header property. <field condition> - condition to check header content by name. Example: condition { header { field("FieldName") { value "SampleValue" } } } |
exist <names> | The condition returns 'true' if all fields with given <names> exist. <names> - string, the list of header names. Example: condition { header {exist "FieldName1", "FieldName2"} } |
exist <action> from <names> | The <action> header properties from the <names> list exist. <names> - string, list of header names. <action> - 'all' or 'any' or 'none'. Example: condition { header {exist any from "FieldName1", "FieldName2"} } |
not_exist <names> | The condition returns 'true' if all fields from the <names> list do not exist. <names> - string, the list of header names. Example: condition { header { not_exist "FieldName1", "FieldName2"} } |
Message type
It is possible to filter messages from the selected Source by message type.
Syntax:
condition { msgType <list of msg types> }
Example:
The received messages are passed to the Condition section. The filtering condition is the following: the message type must be equal to one of the values in the provided list: "D", 8, "AE".
import com.epam.fej.routing.RoutingContext import static dsl.CommonRulesDsl.rulesDSL rulesDSL(routingContext as RoutingContext) { messageRules { messageRule("Condition to check message type") { condition { msgType "D", 8, "AE" } } } }
Field is today
It is possible to filter messages from the selected Source by checking whether the value of the date/time field is the current date. The condition returns 'false' if the field value has an incorrect format or is not the current day.
Syntax:
condition { is_today <tagNum> }
The is_today condition must be used with the fields with a time format of value, for example, 52 or 60.
Example:
The received messages are passed to the Condition section. The filtering condition is the following: the value of the 52 filed is today's date.
import com.epam.fej.routing.RoutingContext import static dsl.CommonRulesDsl.rulesDSL rulesDSL(routingContext as RoutingContext) { messageRules { messageRule("Condition to check message type") { condition { msgType "D", 8, "AE" } } } }
Field(s) exist
It is possible to filter messages from the selected Source by checking that the field(s) exists in these messages.
Condition | Description |
---|---|
exist <action> from <tagNums> | The <action> tag numbers from the <tagNums> list exist in the message. <tagNums> - the list of tag numbers. <action> - all or any or none. Example: condition { exist any from 35, 49, 56 } |
exist <tagNums> | The condition returns 'true' if all fields with tag numbers from the <tagNums> list exist. <tagNums> - the list of tag numbers. Example: condition { exist 35, 49, 56 } |
Field(s) not exist
It is possible to filter messages from the selected Source by checking that the field(s) does not exist in these messages. The condition returns 'true' if all fields with tag numbers from the <tagNums> list do not exist.
Syntax:
not_exist <tagNums>
where <tagNums> is the list of tag numbers.
Example:
import com.epam.fej.routing.RoutingContext import static dsl.CommonRulesDsl.rulesDSL rulesDSL(routingContext as RoutingContext) { messageRules { messageRule("Condition to check if tags not exist") { condition { not_exist 35, 49, 56 } } } }
Repeating group(s) existence
Checks the existence of repeating group with the specified leading tag. These instructions determine if repeating groupings exist at the relevant level. Nested groupings cannot be recognized until the appropriate path is entered based on the available choices.
Condition | Description |
---|---|
existRgs <leading tags> | Return "true" if all repeating group(s) with leading tag numbers from <leading tags> exist. <leading tags> - list of leading tags Sample: |
not_existRgs <leading tags> | Return "true" if no repeating group with leading tag from <leading tags> exists. <leading tags> - list of leading tags Sample: |
existRgs <action> from <leading tags> | Тhe existence of the tag numbers in the current level from the list of the given <leading tags> <leading tags> - list of leading tags <action> - all/any/none Sample: Sample: |
Destination(s) exist by ID
It is possible to filter messages from the selected Source by checking that the destination(s) with the specified IDs exists. The condition returns 'true' if all destinations with the specified <ids> exist.
Syntax:
exist <ids>
where <ids> is the list of destination IDs, string.
Example:
import com.epam.fej.routing.RoutingContext import static dsl.CommonRulesDsl.rulesDSL rulesDSL(routingContext as RoutingContext) { messageRules { messageRule("Condition to check if destinations by ids exist") { condition { exist "destination1", "destination2" } } } }
Destination(s) not exist by ID
It is possible to filter messages from the selected Source by checking that the destination(s) with the specified IDs does not exist. The condition returns 'true' if all destinations with the specified <ids> do not exist.
Syntax:
not_exist <ids>
where <ids> is the list of destination IDs, string.
Example:
import com.epam.fej.routing.RoutingContext import static dsl.CommonRulesDsl.rulesDSL rulesDSL(routingContext as RoutingContext) { messageRules { messageRule("Condition to check if destinations by ids not exist") { condition { not_exist "destination1", "destination2" } } } }
Message match
It is possible to filter messages from the selected Source by checking that the received message matches a pattern. The condition returns 'true' if the message matches the <regex>.
Syntax:
condition { message_match <regex> }
Example:
The received messages are passed to the Condition section. The filtering condition is the following: the message matches the regular expression ".*38=0.15\u000158=SampleValue\u000110=.*" Where the \u0001 is a tags separator.
import com.epam.fej.routing.RoutingContext import static dsl.CommonRulesDsl.rulesDSL rulesDSL(routingContext as RoutingContext) { messageRules { messageRule("Condition to check message type") { condition { message_match ".*38=0.15\u000158=SampleValue\u000110=.*" } } } }
Message mismatch
It is possible to filter messages from the selected Source by checking that the received message does not match a pattern. The condition returns 'true' if the message does not match the <regex>.
Syntax:
condition { message_mismatch <regex> }
Example:
The received messages are passed to the Condition section. The filtering condition is the following: the message does not match the regular expression ".*38=0.15\u000158=SampleValue\u000110=.*" Where the \u0001 is a tags separator.
import com.epam.fej.routing.RoutingContext import static dsl.CommonRulesDsl.rulesDSL rulesDSL(routingContext as RoutingContext) { messageRules { messageRule("Condition to check message type") { condition { message_mismatch ".*38=0.15\u000158=SampleValue\u000110=.*" } } } }
Custom
It is possible to filter messages from the selected Source using a customized condition. The Custom block can be used to describe the required behaviour.
If any external classes are used, they must be imported.
Syntax:
condition { custom { ctx → <some actions that will return a boolean value in the end> } }
Example:
The received messages are passed to the Condition section. The filtering condition is the following: the value of the 35 tag must equal 'D'.
import com.epam.fej.routing.RoutingContext import static dsl.CommonRulesDsl.rulesDSL rulesDSL(routingContext as RoutingContext) { messageRules { messageRule("Condition to check message type") { condition { custom { ctx -> ctx.getMessage().getTagValueAsString(35) == "D" } } } } }
Action section
The message that was successfully checked against the specified Condition is passed to the Action section.
The main goal of the rule is described in the Action section. It can be transformation, modification, or just resending to the selected destination.
Send to destination(s) by ID
It is possible to send the message that was successfully checked against the specified Condition to the specified endpoint(s) using the Destination ID. If errors occur, they will be logged at the ERROR level.
Syntax:
sendTo <destination ids>
where <destination ids> is the list of endpoint destination IDs.
Example:
import com.epam.fej.routing.RoutingContext import static dsl.CommonRulesDsl.rulesDSL rulesDSL(routingContext as RoutingContext) { messageRules { messageRule("Action. Send message to several endpoints") { action { sendTo "session1", "endpoint1" } } } }
Send by repeating groups to destination ID
It is possible to send a message containing a repeating group that was successfully checked against the specified Condition to the specified endpoint using the Destination ID.
Syntax:
action { send by repeating_groups <repeating group> to <destination id> }
where <repeating_groups> is repeating group tag, and the <destination id> is endpoint destination ID.
Example:
import com.epam.fej.routing.RoutingContext import static dsl.CommonRulesDsl.rulesDSL rulesDSL(routingContext as RoutingContext) { messageRules { messageRule("Action. Send message by repeating groups to endpoint") { action { send by repeating_groups 552 to "endpoint1" } } } }
Send by groups
It is possible to send the message that was successfully checked against the specified Condition to the destination(s) found by the <groups> list.
Syntax:
action { send by groups <groups> }
where <groups> is a list of groups.
Example:
import com.epam.fej.routing.RoutingContext import static dsl.CommonRulesDsl.rulesDSL rulesDSL(routingContext as RoutingContext) { messageRules { messageRule("Action. Send message by groups") { action { send by groups "A", "B" } } } }
Send by TargetIDs
It is possible to send the message that was successfully checked against the specified Condition to the destination(s) found by TargetIDs.
Syntax:
action { send by targetIds <ids> }
where <ids> is a list of target IDs.
Example:
import com.epam.fej.routing.RoutingContext import static dsl.CommonRulesDsl.rulesDSL rulesDSL(routingContext as RoutingContext) { messageRules { messageRule("Action. Send message by target ids") { action { send by targetIds "targetId1", "targetId2" } } } }
Reject message
It is possible to reject the message that was successfully checked against the specified Condition with the specified reason.
Syntax:
action { rejectMessage <user reason> }
Example:
import com.epam.fej.routing.RoutingContext import static dsl.CommonRulesDsl.rulesDSL rulesDSL(routingContext as RoutingContext) { messageRules { messageRule("Action. Send reject message") { action { rejectMessage "User reason" } } } }
Modify fields
It is possible to modify the fields and send the message that was successfully checked against the specified Condition.
Syntax:
action { fields { <action 1> <action n> } }
where the <action n> is the modification that must be applied to change the message.
The list of available actions:
Action | Description |
---|---|
set <fieldNum> value <value> | The field with the <fieldNum> number will be set to the <value>. If the <fieldNum> field doesn't exist in the message, it will be added. <fieldNum> - the tag number. <value> - the new value for the <fieldNum> field. Example: action { fields { set 58 value "New value" } } |
remove <fieldNum> | The <fieldNum> field will be deleted. <fieldNum> - the tag number. Example: action { fields { remove 58 } } |
move <fieldNum1> to <fieldNum2> | The value of the <fieldNum1> field will be moved to the <fieldNum2> field. The source <fieldNum1> field will be deleted. <fieldNum1> - the source field. <fieldNum2> - the destination field. Example: action { fields { move 38 to 58 } } |
modify <fieldNum> value <value> | The value of the <fieldNum> field will be updated. The new field won't be added if the <fieldNum> field doesn't exist. <fieldNum> - the tag number. <value> - value for adding. The following types are valid: string, int, long, boolean or byte. Example: action { fields { modify 58 value "New value" } } |
modify <fieldNum> double_value <value>, <precision> | The double value of the existing <fieldNum> field will be updated. The new field won't be added if the <fieldNum> field doesn't exist. <fieldNum> - the tag number. <value> - the value for adding. <precision> - num precision. Example: action { fields { modify 44 double_value 3.45, 2 } } |
exchange <fieldNum1> with <fieldNum2> | The values of the <fieldNum1> field and <fieldNum2> field will be exchanged. <fieldNum1> - the source field. <fieldNum2> - the destination field. Example: action { fields { exchange 49 with 56 } } |
copy <fieldNum1> to <fieldNum2> | The value of the <fieldNum1> field will be copied to the <fieldNum2> field. The source field will be not deleted. The new destination field will be added if it doesn't exist. <fieldNum1> - source field. <fieldNum2> - destination field. Example: action { fields { copy 38 to 58 } } |
Example:
Received message:
8=FIX.4.4 | 9=146 | 35=D | 49=FIXCLIENT1 | 56=FIXEDGE | 34=4 | 52=20220510-08:44:56.384 | 11=MyNew11Tag | 55=BTC/USD | 60=20220420-12:35:40.970 | 38=1 | 40=2 | 44=2.25 | 10=XXX |
Result message:
8=FIX.4.4 | 9=146 | 35=D | 49=FIXCLIENT1 | 56=FIXEDGE | 34=4 | 52=20220510-08:44:56.384 | 55=BTC/USD | 60=20220420-12:35:40.970 | 38=1 | 40=2 | 44=3.45 | 58=Sample message with type D | 10=XXX |
The message of 'D' message type was received from the 'session2', changed and routed to 'session1'.
import com.epam.fej.routing.RoutingContext import static dsl.CommonRulesDsl.rulesDSL rulesDSL(routingContext as RoutingContext) { messageRules { messageRule("Action. Change received message and route from session2 to session1") { source { id "session2" } condition { msgType "D" } action { fields { set 58 value "Sample message with type D" remove 11 modify 54 value 3 modify 44 double_value 3.45, 2 } sendTo "session1" } } } }
Modify header
It is possible to modify the message header and send the message that was successfully checked against the specified Condition.
Syntax:
action { header { <action 1> <action n> } }
where <action n> is the modification that must be applied to change the message header.
The list of available actions:
Action | Description |
---|---|
set <fieldName> value <value> | The <fieldName> header field will be set to the <value> . If the <fieldNum> field doesn't exist in the message, it will be added. <fieldName> - header field name. <value> - the new value for the <fieldNum> header field. Example: action { header { set 58 value "New value" } } |
Example:
The ManageableQueue.STORE_AND_FORWARD_ENABLED header property will be set to the Boolean.FALSE value.
import com.epam.fej.routing.RoutingContext import com.epam.fej.routing.manageablequeue.ManageableQueue import static dsl.CommonRulesDsl.rulesDSL rulesDSL(routingContext as RoutingContext) { messageRules { messageRule("Action. Set header property") { source { id "session2" } condition { msgType "D" } action { header { set ManageableQueue.STORE_AND_FORWARD_ENABLED value Boolean.FALSE } sendTo "session1" } } } }
If condition
It is possible to separate the execution of some events depending on the conditions described in the corresponding block. It consists of the ifCondition main part, which separates this block from other actions, and 3 subparts: when, then, and otherwise.
Syntax:
action { ifCondition { when { <condition 1> <condition n> } then { <action 1> <action n> } otherwise { <action 1> <action n> } } }
Separation by the ifConditionis required.
Each of the when, then and otherwise sub-parts can be skipped or duplicated. If there are several identical sub-parts in one block, their instructions will be executed as a single condition/action.
The when sub-part describes the condition. <condition n> is any instruction from the message rule Condition section.
The then sub-part describes actions that will be performed if the result of executing when sub-part is 'true'. <action n> is any instruction from the message rule Action section.
The otherwise sub-part describes actions that will be performed if the result of executing when sub-part is 'false'. <action n> is any instruction from message rule Action block.
when | then | otherwise | behavior |
---|---|---|---|
<condition n> | <action 1> | <action 2> | If the <condition n> is 'true' then apply <action 1> will be executed. If <condition n> is 'false' then <action 2> will be executed. Example: rules.groovy import com.epam.fej.routing.RoutingContext import static dsl.CommonRulesDsl.rulesDSL rulesDSL(routingContext as RoutingContext) { messageRules { messageRule("Action. If condition with all blocks") { action { ifCondition { when { or { msgType "D" exist 11, 58 } } then { modify 58 value "Update user text" sendTo "session1" } otherwise { sendTo "session2" } } } } } } |
<condition n> | <action 1> | null | If <condition n> is 'true' then <action 1> will be executed. If <condition n> is 'false' then nothing will be executed. Example: rules.groovy import com.epam.fej.routing.RoutingContext import static dsl.CommonRulesDsl.rulesDSL rulesDSL(routingContext as RoutingContext) { messageRules { messageRule("Action. If condition with all blocks") { action { ifCondition { when { or { msgType "D" exist 11, 58 } } then { modify 58 value "Update user text" sendTo "session1" } } } } } } |
<condition n> | null | <action 2> | If <condition n> is 'true' then nothing will be executed. If <condition n> is 'false' then <action 2> will be executed. Example: rules.groovy import com.epam.fej.routing.RoutingContext import static dsl.CommonRulesDsl.rulesDSL rulesDSL(routingContext as RoutingContext) { messageRules { messageRule("Action. If condition with all blocks") { action { ifCondition { when { or { msgType "D" exist 11, 58 } } otherwise { sendTo "session2" } } } } } } |
null | <action 1> | <action 2> | If when block is missing, it means that there is no 'true' condition, so otherwise block will always be executed, if present. If the otherwise block is absent too, then nothing will be executed. |
Route by field value
Depending on the <value> specified in the <field num> field, the routeByField action redirects the message to the <destination n> destination. The Otherwise block provides the ability to set the default behaviour if none of the specified conditions is met.
Syntax:
routeByField(<field num>) { value <value 1> to <destination 1> value <value 2> to <destination 2> value <value n> to <destination n> otherwise <default behavior> }
where:
<field num> is an integer, the number of the tag which value will be compared.
<value n> is a number or string, value, that the <field num> will be compared to.
<destination n> is a target endpoint to which the message will be redirected if the value of the <field num> field equals <value n>.
<default behavior> is a string or lambda block defining the default action that will be performed if none of the specified cases works. String - by default, used to send messages to the specified destination. Lambda - to specify fully custom behaviour.
Example:
import com.epam.fej.routing.RoutingContext import static dsl.CommonRulesDsl.rulesDSL rulesDSL(routingContext as RoutingContext) { messageRules { messageRule("Action. Route by field") { source { id "session2" } condition { msgType "D" } action { routeByField(11) { value "OR2" to "session1" value "OR1" to "session3" otherwise {ctx -> logger.info("Can't match appropriate value")} } } } } }
Custom
If there is a need to write customized action, FIXEdge Java provides the ability to use the Custom block to describe the required behaviour. For example, for the customized endpoint this block can be combined with all the previous ones, and several of them can be used.
If any external classes are used, they must be imported.
Syntax:
action { custom { ctx → <some actions> } }
Example:
import com.epam.fej.routing.RoutingContext import static dsl.CommonRulesDsl.rulesDSL rulesDSL(routingContext as RoutingContext) { messageRules { messageRule("Action. Set header property") { source { id "session2" } condition { msgType "D" } action { custom { ctx -> routingContext.getDestinationById("session1").send(ctx.messageEvent) logger.info("run custom action") ctx.exit() } } } } }
Context exit
It is possible to perform the RuleContext exit and not follow the rules below.
Syntax:
action { context exit }
Repeating group
It is possible to work in the Action block with RG at the DSL level.
Like in the conditional group, the corresponding structural unit of the RG can be accessed through the following structures:
Syntax | Description | RG sample | Rule sample |
---|---|---|---|
|
The construction is valid for an existing RG. It is used to do some action with a repeating group, its entries through the | Sample 1 Before 453=3 | 448=First | 452=123 | 448=Second | 452=456 | 448=Third | 452=789 | After 453=2 | 448=Second | 452=456 | 448=Third | 452=789 | Sample 2 Before 251=1 | 32=21 | 253=2 | 52=31 | 53=32 | 52=41 | 53=42 | After 251=1 | 32=21 | 253=2 | 52=31 | 53=32 | | Sample 1
Sample 2
52=11.99 | |
|
The construction is valid for an existing RG and specified entry. It is used to do some action with an entry in repeating group, its entries through the | Sample 1 Before 453=3 | 448=First | 452=123 | 448=Second | 452=456 | 448=Third | 452=789 | After 453=3 | 448=First | 452=123 | 448=Second New | 452=456 | 448=Third | 452=789 | Sample 2 Before 251=1 | 32=21 | 253=2 | 52=31 | 53=32 | 52=41 | 53=42 | After 251=1 | 32=21 | 253=2 | 52=31 | 53=32 | 52=41 | 53=1142 | Sample 3 Before 251=1 | 32=21 | 253=2 | 52=31 | 53=32 | 52=41 | 53=42 | After 251=1 | 32=21 | 253=2 | 52=31 | 53=32 | | Sample 1
Sample 2
Sample 3
|
|
The construction is valid for an existing RG. It is used to do some action with a repeating group, its entries through the | Sample 1 Before 251=1 | 32=21 | 253=2 | After 251=1 | 32=21 | Sample 2 Before 251=1 | 32=21 | 252=2 | 42=31 | 43=32 | 253 = 1 | 52=11 | 53=22 | 42=41 | 43=42 | 253 = 1 | 52=33 | 53=44 | After 251=1 | 32=21 | 252=2 | 42=31 | 43=32 | 42=41 | 43=42 | | Sample 1
Sample 2
|
removeRG <leading tag> |
Delete the entire RG by the specified | Sample 1 Before 251=1 | 32=21 | 253=2 | After 251=1 | 32=21 | Sample 2 Before 251=1 | 32=21 | 252=2 | 42=31 | 43=32 | 253 = 1 | 52=11 | 53=22 | 42=41 | 43=42 | 253 = 1 | 52=33 | 53=44 | After 251=1 | 32=21 | 252=2 | 42=31 | 43=32 | 42=41 | 43=42 | | Sample 1
Sample 2
|
|
<create if not exist> - type: bool, create new with If the group already exists, then the construct will work in the same way as a regular one If the group does not exist, it will create a new one. Further in this block, only the call to the | Proposed rule sample will generate: 252=1 | 42=31 | 43=32 | 253 = 1 | 52=11 | |
|
Event rule
eventRule block is intended to build a rule for event processing. The following FIXEdge Java events can be processed:
- FixSessionStateEvent
- NewSessionEvent
- RuleErrorEvent
- ScheduleEndpointEvent
- SchedulerEvent
- SnfEvent
- UnprocessedMessageEvent
- Custom
Syntax:
eventRule(description){ eventType(<ClassName>.class) condition { //instructions, optional } action { //instructions } }
Each eventRule block contains 4 sections:
Section | Description |
---|---|
description | Mandatory section. This section provides a string with a free text - description of the event rule. |
eventType | Optional section. This section defines the type or subtype of the event for which the rule must be applied. This section can be skipped if you use customized types of events. |
condition | Optional section. This section contains a set of pre-defined criteria. The application checks events on event content and source attributes and if the condition is met the instructions will be executed. If the condition section is not specified then all events will be maintained in accordance with the Action section. |
Action | Mandatory section. This section defines the instructions that must be performed when the event rule is being applied. The Action section must contain at least one instruction. |
Description section
The description section contains the line of text used to describe the event rule.
eventType section
The eventType section defines the type or subtype of the event for which the rule must be applied. It is possible to specify custom classes built on the AppEvent interface or use the default ones.
Syntax:
eventRules { eventRule ("Description"){ eventType (FIXSessionStateEvent.class) } }
If any classes are used, they must be imported.
The eventType section can be skipped if the customized default type of events is specified.
The eventType section consists of the list of the default classes, they and their analogue among the default custom types are listed in the table below.
eventType section can be used with customized default rules too. The class passed as a parameter must be an instance or an inheritor of the class that underlies this block.
Class | Import path | Analogous to the default custom type |
---|---|---|
FIXSessionStateEvent.class | com.epam.fej.server.fix.event.FIXSessionStateEvent | FixSessionStateEventRule(name) |
NewSessionEvent.class | com.epam.fej.server.fix.event.NewSessionEvent | NewSessionEventRule(name) |
RuleErrorEvent.class | com.epam.fej.routing.event.RuleErrorEvent | ErrorEventRule(name) |
ScheduleEndpointEvent.class | com.epam.fej.event.ScheduleEndpointEvent | ScheduleEndpointEventRule(name) |
SchedulerEvent.class | com.epam.fej.scheduling.event.SchedulerEvent | SchedulerEventRule(name) |
SnFEvent.class | com.epam.fej.routing.endpoint.snf.events.SnFEvent | SnFEventRule(name) |
UnprocessedMessageEvent.class | com.epam.fej.routing.event.UnprocessedMessageEvent | UnprocessedMessageEventRule(name) |
Examples:
The default event rule structure with default event type.
import com.epam.fej.routing.RoutingContext import com.epam.fej.server.fix.event.FIXSessionStateEvent import com.epam.fixengine.SessionState import static dsl.CommonRulesDsl.rulesDSL rulesDSL(routingContext as RoutingContext) { eventRules { eventRule("FIX Session Disconnect. Default rule structure") { eventType(FIXSessionStateEvent.class) condition { custom { event -> def state = event.sessionState as SessionState return SessionState.isDisconnected(state) && event.sessionId == "session2" } } action { custom { event -> logger.info("[FIX Session Disconnect] Session is disconnected: " + event.sessionId) } } } } }
The customized event rule structure with the default event type:
import com.epam.fej.routing.RoutingContext import static dsl.CommonRulesDsl.rulesDSL rulesDSL(routingContext as RoutingContext) { eventRules { FixSessionStateEventRule("FIX Session Connect") { condition { session "session1" sessionState connected } action { log "[FIX Session Connect] Session is connected: session1" } } } }
The rule with the custom event type:
import com.epam.fej.routing.RoutingContext import com.path.to.custom.event.type.CustomEventType import static dsl.CommonRulesDsl.rulesDSL rulesDSL(routingContext as RoutingContext) { eventRules { eventRule("Custom event type") { eventType(CustomEventType.class) condition { custom { event -> event.condition() } } action { log "Custom event type" } } } }
Condition section
The occurred event can be filtered by the event content and source attributes. This section consists of a number of instructions that can be grouped by logical operators.
FixSessionStateEventRule
It is possible to filter occurred FixSessionStateEvents by session state and session ID.
Syntax:
eventRules { FixSessionStateEventRule("FIX Session Connect") { condition { <condition instruction 1> <condition instruction n> } } }
where the <condition instruction n> is an instruction for this event type.
Instruction | Description |
---|---|
session <regex> | The session ID will be checked against regular expression. <regex> - the regular expression. Example: condition { session "session1" } |
sessionState <state> | The current state of the session will be compared to the specified <state>. <state> - provided session state, 'connected' or 'disconnected' or 'disposed' or 'not_disconnected'. Example: condition { sessionState connected } |
Example:
import com.epam.fej.routing.RoutingContext import static dsl.CommonRulesDsl.rulesDSL rulesDSL(routingContext as RoutingContext) { eventRules { FixSessionStateEventRule("FIX Session Connect") { condition { session "session1" sessionState connected } action { log "[FIX Session Connect] Session is connected: session1" } } } }
NewSessionEventRule
It is possible to filter occurred NewSessionEvents by session event state.
Syntax:
eventRules { NewSessionEventRule("New session event") { condition { <condition instruction 1> <condition instruction n> } } }
where the <condition instruction n> is an instruction for this event type.
Instruction | Description |
---|---|
session <state> | The current state of the session event will be compared to the specified <state>. <state> - the session event state, 'rejected' or 'quietRejected'. |
ErrorEventRule
It is possible to filter occurred RuleErrorEvents.
Syntax:
eventRules { ErrorEventRule("Error event rule") { condition { <condition instruction 1> <condition instruction n> } } }
where <condition instruction n> is an instruction for this event type.
Instruction | Description |
---|---|
session <condition> | The session will be checked against specified <condition>. <condition> - 'mitigated'. |
ScheduleEndpointEventRule
It is possible to filter occurred ScheduleEndpointEvents by endpoint condition and endpoint ID.
Syntax:
eventRules { ScheduleEndpointEventRule("Schedule endpoint event") { condition { <condition instruction 1> <condition instruction n> } } }
where <condition instruction n> is an instruction for this event type.
Instruction | Description |
---|---|
endpoint <condition> | The endpoint condition will be checked against specified <condition>. <condition> - 'startedOnLoad' or 'resetSequence' (if sequence was reset on schedule) |
ids <ids list> | Check if any of given endpoint IDs present. <ids> - string, the list of endpoint IDs. |
SchedulerEventRule
It is possible to filter occurred SchedulerEvents.
Syntax:
eventRules { SchedulerEventRule("Sheduler event") { condition { <condition instruction 1> <condition instruction n> } } }
where <condition instruction n> is an instruction for this event type.
Instruction | Description |
---|---|
ids <ids list> | Check if any of given IDs present. <ids> - string, the list of IDs. |
SnFEventRule
It is possible to filter occurred SnfEvents.
Syntax:
eventRules { SnFEventRule("SnF event") { condition { <condition instruction 1> <condition instruction n> } } }
where <condition instruction n> is an instruction for this event type.
UnprocessedMessageEventRule
It is possible to filter occurred UnprocessedMessageEvents.
Syntax:
eventRules { UnprocessedMessageEventRule("Unprocessed message event") { condition { <condition instruction 1> <condition instruction n> } } }
where <condition instruction n> is an instruction for this event type.
Custom
Custom filtering is available for all event types. It allows to write customized conditions, taking into account the features of the received AppEvent and Source.
Syntax:
eventRule("FIX Session Disconnect. Default rule structure") { eventType(FIXSessionStateEvent.class) condition { custom { event -> <condition instruction> } } }
Example:
import com.epam.fej.routing.RoutingContext import com.epam.fej.server.fix.event.FIXSessionStateEvent import com.epam.fixengine.SessionState import static dsl.CommonRulesDsl.rulesDSL rulesDSL(routingContext as RoutingContext) { eventRules { eventRule("FIX Session Disconnect. Default rule structure") { eventType(FIXSessionStateEvent.class) condition { custom { event -> def state = event.sessionState as SessionState return SessionState.isDisconnected(state) && event.sessionId == "session2" } } } } }
Action section
Custom
If there is a need to write customized action, FIXEdge Java provides the ability to use the Custom block to describe the required behaviour. For example, for the customized endpoint this block can be combined with all the previous ones, and several of them can be used.
If any external classes are used, they must be imported.
Syntax:
action { custom { appEvent→ <some actions> } }
Example:
import com.epam.fej.routing.RoutingContext import com.epam.fej.server.fix.event.FIXSessionStateEvent import com.epam.fixengine.SessionState import static dsl.CommonRulesDsl.rulesDSL rulesDSL(routingContext as RoutingContext) { eventRules { eventRule("FIX Session Disconnect. Default rule structure") { eventType(FIXSessionStateEvent.class) condition { custom { event -> def state = event.sessionState as SessionState return SessionState.isDisconnected(state) && event.sessionId == "session2" } } action { custom { event -> logger.info("[FIX Session Disconnect] Session is disconnected: " + event.sessionId) } } } } }