History implementation in DSL

Overview

FIXEdge Java provides Solutions for handling and storing data on the business layer—Histories. Information can be handled in memory or through database queries, depending on the defined type.  These types are available in the current version:

·        Information can be stored in the memory storage with the help of InMemoryHistory.

·        Information can be stored in the JDBC database via JDBCHistory.

Histories can be used in existing message/event rules builders using routingContext (see History implementation) or in routing rules through the suggested Groovy DSL design.

The DSL Layer's many history types are configured and handled according to the instructions in this manual.

Configuration structure

The configuration of the histories must be done through the histories block in rulesDSL depending on the type.

It’s possible to work with histories at different levels of DSL rules: condition, action, custom blocks.

InMemoryHistory

 

In order to configure InMemoryHistory, it’s required to declare a block memoryHistory in the histories section and describe the list of required parameters:

·        name <history name> - type: String. Must be unique .

·        keyTags <key tags: simple or compose> - type: Integer or List of integers. Tag(s) number(s) in received on BL message (FIXFieldList), which creates a key (simple or compose).

·        valueTags <value tags: simple or compose> type: Integer or List of integers. Tag(s) number(s) in received on BL message (FIXFieldList), that will be stored by prepared key.

Structure template

Main structure

1

2

3

4

5

6

7

8

9

10

11

12

13

14

import com.epam.fej.routing.RoutingContext

 

import static dsl.CommonRulesDsl.rulesDSL

 

rulesDSL(routingContext as RoutingContext) {

    histories {

        memoryHistory {

            name <history name>

            keyTags <key tags: simple or compose>

            valueTags <value tags: simple or compose>

 

        }

    }

}

 

Sample

Main structure

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

import com.epam.fej.routing.RoutingContext

 

import static dsl.CommonRulesDsl.rulesDSL

 

rulesDSL(routingContext as RoutingContext) {

    histories {

        memoryHistory {

            name "SenderTargetCompId History"

            keyTags 34

            valueTags 49, 56

 

        }

        memoryHistory {

            name "ClOrdID History"

            keyTags 34, 35

            valueTags 11

        }    

    }

}

JDBCHistory

For configuring JDBCHistory, its required to declare a block db(<dataSource>) in the histories and describe the list of parameters:

·   DataSource - type: String. Must be unique. Group identifier, that contains parameters for connecting to the database. Its set through the conf/history.properties

o   <dataSource>.url=<connectionUrl> - required. 

o   <dataSource>.username=<username> -  required. 

o   <dataSource>.password=<password> -  required. 

o   <dataSource>.driverClassName=<driverClassName> - required. 

o   <dataSource>.databaseName=<databaseName> - optional.  The name of the catalogue must be specified (the same specified in the URL). Required to be used in case when two databases contain procedures with the same name. 

o   <dataSource>.schemaName=<schemaName> - optional. The name of the schema must be specified. For example, when using it with a MsSQL datasource and a specified catalog name, its required to specify the schema so that the procedure is recognized (dbo is the default).

o   <dataSource>.maximumPoolSize=<maximumPoolSize> - optional, default - 10, type:       Integer. This property controls the maximum size that the pool is allowed to reach, including both idle and in-use connections.

o  <dataSource>.idleTimeout=<idleTimeout> - optional, default - 3000, type: Integer. The maximum duration that a connection may remain idle in the pool is managed by this attribute.

·   Procedures - structure block, that describes the list of stored procedures or custom requests, that can be applied to declared dataSource.

Includes:

o procedure(<procedure name>) - block that describes procedure's configuration:

§  <procedure name> - required, type: String, name of stored procedure. Used as the        identifier of the procedure/request being accessed if no <id> was specified. In this case, the uniqueness of the names for all databases is important. (Otherwise, the <id> is additionally specified). It is also used to generate a stored procedure call request in the "CALL <procedure name> (<InParams template>);" format if a custom template has not been previously specified. 

§ <id> - optional, type: String, is used when it is necessary to set the uniqueness of the    procedure configuration. For example, for two different databases, there are procedures with the same name. To distinguish them when calling, own id is assigned to each structure. It must be unique in comparison with the names/ids of other procedures , regardless of the database being accessed. 

§ inParams - optional, block, that describes and after - prepare expected incoming   params. The order in which the parameters are described is important:

·        fixParam (<tag num>) - get from FIXFieldList (incoming message) value by <tag num> as String. 

·     fixParam (<tag num>, <default value>) - get from FIXFieldList (incoming message) value by <tag num> as String. If the value does not exist, then substitute the specified default <default value>

·   param - general block that describes incoming param structure

o tag - optional, type: int, define tag number, that should take and save from incoming FIXFieldList

o defaultValue - optional, type: any serializable object. This value will be substituted if the specified tag is not found.

o dataType - optional, type: String, default: String. Possible values (case insensitive):  INT, LONG, FLOAT, DOUBLE, BIGDECIMAL,BYTE, BYTES, BOOL, STRING, TIMESTAMP, LOCALMKTTIME, TENOR, TIMEONLY. Unknown type - get from FIXFieldList as bytes. 

o saveFullMessage - optional, type: boolean. Declare to save full incoming message as byte array (db blob type)

§  outParams - optional, block, that describes out parameters/result set

·     param - expect name of OUT param for stored procedure and its java.sql.Types 

·     rowMapper - type: RowMapper. Custom implementation of method to map each row of data in the ResultSet. 

·     dbHistory - structure block, that describes the configuration of the view on selected table. Describes by:

o   name - required, type: String, must be unique over ALL declared histories. This parameter is used as an identifier for accessing the described structure.

o   updateOnSave - optional, type: boolean, default: false. Determines whether to update database records with the same key or ignore them.

o   table - required, structure block, that describes selected table (in whole or in part):

§ name - required, type: String. Declared table name

§ keys - required, structure block, that describes the list of key columns in the table. Consist of one or a list of described column structures:

·     column - structure block, that describes the table column:

o  tag - optional, type: int, define tag number, that should take and save from incoming FIXFieldList

o  defaultValue - optional, type: any serializable object. This value will be substituted if the specified tag is not found.

o  dataType - optional, type: String, default: String. Possible values (case insensitive):  INT, LONG, FLOAT, DOUBLE, BIGDECIMAL,BYTE, BYTES, BOOL, STRING, TIMESTAMP, LOCALMKTTIME, TENOR, TIMEONLY. Unknown type - get from FIXFieldList as bytes. 

o  saveFullMessage - optional, type: boolean. Declare to save full incoming message as byte array (db blob type). It is not recommended to use the type as a key.

o  customFormatter - optiona, type: Functional callback. Allows the user to define the format for saving a value to a column using a callback. Accepts FIXFieldList as input, returns HistoryObject (HistoryKey/HistoryValue) as output: HistoryObject apply(FIXFieldList list);

§   values - required, structure block, that describes the value columns in the table. Consist of one or a list of described column structures (see keys column description)

 

Structure template

Main structure

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

import com.epam.fej.routing.RoutingContext

 

import static dsl.CommonRulesDsl.rulesDSL

 

rulesDSL(routingContext as RoutingContext) {

    histories {

        db(<dataSource>){

            procedures {

                procedure (<procedure name>)

                procedure (<procedure name>){

                    id <identifier>

                    inParams {

                        param {

                            tag <tag num>

                            dataType <data type>

                            defaultValue <default value>

                            saveFullMessage <true/false>

                        }

                    }

                    outParams {

                        param <procedure param name>, <java.sql.Types.<var>>

                        rowMapper { <RowMapper implementation> }

                    }

                }

            }

            dbHistory {

                name <history name>

                table {

                    name <table name>

                    keys {

                        column {

                            name <column name>

                            tag <tag num>

                            dataType <data type>

                            defaultValue <default value>

                            saveFullMessage <true/false>

                            customFormatter <custom formatter, callback>

                        }

                    }

                    values {

                        column {

                            name <column name>

                            tag <tag num>

                            dataType <data type>

                            defaultValue <default value>

                            saveFullMessage <true/false>

                            customFormatter <custom formatter, callback>

                        }

                        column {

                            name <column name>

                            tag <tag num>

                            dataType <data type>

                            defaultValue <default value>

                            saveFullMessage <true/false>

                            customFormatter <custom formatter, callback>

                        }

                         

                    }

                }

            }

            dbHistory {

                name <history name>

                table {

                    name <table name>

                    updateOnSave <true/false>

                    keys {

                        column {

                            name <column name>

                            tag <tag num>

                            dataType <data type>

                            defaultValue <default value>

                            saveFullMessage <true/false>

                            customFormatter <custom formatter, callback>

                        }

                        column {

                            name <column name>

                            tag <tag num>

                            dataType <data type>

                            defaultValue <default value>

                            saveFullMessage <true/false>

                            customFormatter <custom formatter, callback>

                        }

                    }

                    values {

                        column {

                            name <column name>

                            tag <tag num>

                            dataType <data type>

                            defaultValue <default value>

                            saveFullMessage <true/false>

                            customFormatter <custom formatter, callback>

                        }

                    }

                }

            }

        }

    }

}

Sample

conf/rules.groovy

DB configuration        

                                                                                       Collapse source

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

import com.epam.fej.routing.RoutingContext

 

import static dsl.CommonRulesDsl.rulesDSL

import static java.sql.Types.INTEGER

 

class Product {

    String name

    BigDecimal price

     

    public Product(String name, BigDecimal price) {

        this.name = name

        this.price = price

    }

}

rulesDSL(routingContext as RoutingContext) {

    histories {

        db("datasource1"){

            procedures {

                procedure ("insert_default_value")

                procedure ("insert_ClOrdId"){

                    id "saveClOrdId"

                    inParams {

                        tag 11

                        defaultValue "default Order id"

                    }

                }

                procedure ("select_by_price"){

                    id "selectProducts"

                    inParams {

                        tag 58

                        defaultValue "default value"

                    }

                    outParams {

                        param "counter", Types.INTEGER

                        rowMapper {

             rs, num -> return new Product(rs.getString("productName"), rc.getBigDecimal("price"))}

                        }

                }

            }

            dbHistory {

                name "SenderTargetHistory"

                table {

                    name "SenderTargetCompId"

                    keys {

                        column {

                            name "SeqNum"

                            tag 34

                            dataType "Int"

                        }

                    }

                    values {

                        column {

                            name "SenderCompId"

                            tag 49

                        }

                        column {

                            name "TargetCompId"

                            tag 56

                        }

                         

                    }

                }

            }

            dbHistory {

                name "ClOrdIdHistory"

                table {

                    name "ClOrdID_table"

                    updateOnSave true

                    keys {

                        column {

                            name "SeqNum"

                            tag 34

                            dataType "Int"

                        }

                        column {

                            name "MsgType"

                            tag 35

                        }

                    }

                    values {

                        column {

                            name "ClOrdID"

                            tag 11

                            defaultValue "None"

                        }

                    }

                }

            }

        }

    }

}

 

 

conf/history.properties

history.properties

datasource1.url=jdbc:mysql://localhost:3306/custom_db

datasource1.username=user

datasource1.password=

datasource1.driverClassName=com.mysql.cj.jdbc.Driver

datasource1.maximumPoolSize=3

 

datasource2.url=jdbc:h2:~/test;DB_CLOSE_DELAY=0

datasource2.username=user

datasource2.password=

datasource2.driverClassName=org.h2.Driver

datasource2.maximumPoolSize=3

datasource2.idleTimeout=3000

In the FIXEdge Java from the package, the following SQL databases are supported:

§  MySql

§  PostgreSQL

§  H2

§  HSQL


DSL Instructions

MessageRule

Condition section

To work with a History in a Condition section, its required to declare the corresponding block and pass the name of the history in which check is needed as a parameter.

Comparably to other conditional blocks, additional and/or blocks can be used inside so that  to facilitate communication between tasks. By extension, and.

Template

messageRule {

condition {

Name

Description

Template

Example

Key exists

Prepare a key based on a given structure and an incoming message. Check whether the corresponding one exists in the history.

key exists

condition { key exists }

Prepare a key based on a given value list. Check whether the corresponding one does not exist in the history.

keyParams <key values> value exists

condition { keyParams "Sender",

 "Target" value exists }

Key not exists

Prepare a key based on a given structure and an incoming message. Check whether the corresponding one not exists in the saved in the history.

key not_exists

condition { key not_exists }

Prepare a key based on a given value list. Check whether the corresponding one does not exist in the history.

keyParams <key values> value not_exists

condition { keyParams "Sender",

 "Target" value not_exists }

Value by key is empty

Checks if there is such a key, prepared on a given value list, and nothing is stored by it (value equal to null).

keyParams <key values> value empty

condition { keyParams  "Sender",

 "Target" value empty }

Checks if there is such a key, on a given structure and an incoming message, and nothing is stored by it (value equal to null).

key empty

condition { key empty}

Value by key is not empty

Checks if there is a key,  prepared on a given structure and an incoming message, and the value stored by the key is not empty (not equal to null)

keyParams <key values> value not_empty

condition { keyParams "Sender",

 "Target" value not_empty }

Checks if there is such a key, prepared on a given value list, and nothing is stored by it (value equal to null).

key not_empty

condition { key not_empty }

Value exists

Prepare a key based on a given structure and an incoming message. Check whether the corresponding one exists in the WHOLE history.

value exists

condition { value exists }

Value not exists

Prepare a key based on a given structure and an incoming message. Check whether the corresponding one does not exist in the WHOLE history.

value not_exists

condition { value not_exists }

Check part of values by indexes

Since the HistoryValue itself can be folded from several tags and stored as an array of objects,  check of each

of the fields of the folded value has been added.

Checks if there is a value for a given key that stores some HistoryValue, it is necessary to check only some parts of this value by indexes (tag's ordinal number in the configuration). Not_empty - value is not null, empty - value null.

keyParams <key values>

indexes <list of indexes> are not_empty 

condition {

keyParams "Sender", "Target"

indexes 0, 1 are not_empty

keyParams <key values>

indexes <list of indexes> are empty 

condition {

keyParams "Sender", "Target"

indexes 0, 1 are empty

history(<history name>) {
    // set of instructions
}

}

}

Common instructions

Action section

To work with history in actions, its required to either use the main commands or declare the corresponding block and pass the name of the history as a parameter for which the action is performed. Additional instructions for working with histories are provided in the previously presented DSL blocks header, fields.

 
 
 
 
 
 
 
Main commands

 

Name

Description

Template

Sample

Save to

Stores the prepared key and values in the storage in accordance with the described structure.

saveTo <history name>

action { saveTo "MemoryHistory" }

Remove

Deletes a record from the History by the generated key from the provided values

remove <key params> from

 

 

 

 

<history name>

action {

ifCondition {

when { msgType "D" }

then { remove "ClearCommand" from "MemoryHistory" }

otherwise { saveTo "MemoryHistory" }

}}

RemoveFromHistory

Deletes a record from the History by the the prepared key by incoming message using described structure.

removeFromHistory <history name>

action { removeFromHistory "MemoryHistory" }

Clear

Clean the entire histories

clear <history name>

action {

ifCondition {

when { msgType "AE" }

then { saveTo "MemoryHistory" }

otherwise { clear "MemoryHistory" }

} }

Show

Use the logger to print all values

show <history name>

action {  show "MemoryHistory"  }

Update

Update the value, if it existed, with the generated key and the new value

update <history name>

action {  update "MemoryHistory"  }

Call procedure

Execute the procedure(update/insert) declared in the database history structure, passing parameters. As a result, it returns nothing. Use only for methods for updating data in a table.

callProcedure <procedure name>

action { callProcedure "saveClOrdId"  }

Execute the procedure(update/insert) declared in the database history structure, pass as parameters declared object list . As a result, it returns nothing. Use only for methods for updating data in a table.

callProcedure <procedure name>,

 <object list>

action { callProcedure "saveClOrdId", 34, 5, "Text58"  }

Execute the procedure(update/insert) declared in the database history structure, pass as parameters declared closure object list. As a result, it returns nothing. Use only for methods for updating data in a table.

callProcedure(<procedure name>){

fixParam <param from incoming message>

param {

defaultValue <some static/default

value>

//tag <declare tag num to take

//value from incoming message>

dataType <data type, the same as

inParams for column>

//saveFullMessage <bool to save

full message>

}

}

callProcedure("procedure"){

fixParam 56

param {

defaultValue 5

//tag 34

dataType "Int"

//saveFullMessage true

}

}

Route by DBTable

A narrowly targeted command to quickly routing the key formed by the structure and send a message according to the value in the destination column.

routeByDBTable(<history name>){

destinationColumn <column name>

}

action { 

routeByDBTable("DB History"){

destinationColumn "Destination"

}

}

History block

If you need to work with a specific History, by analogy to Condition, you can use the history section.

Template

messageRule {

action {

history(<history name>) {
    // set of instructions
}

}

}

 

List of available instructions:

Show

Get the value by the key generated pared from the specified parameters and show it in full or in part

get <key params> show full
get <key params> show <value tag (simple/list)>
get <key params> showByIndex <value tag index (simple/list)>

 

Get the value by the key generated from the incoming message and the declared history structure and display it all together.

getValue and show full

 

Get from history and set to message field 

Get the value by the key generated from the specified parameters and set it to message field fully or in partially.

get <key params> setToField <message tag num>

get <key params> items <value tag index (simple/list)> setToField <message tag num>

get <key params> tags <value tag (simple/list)> setToField <message tag num>

 

Get the value by the key generated from the incoming message and the declared history structure and set it to message field in fully or in partially.

getValue and setToField <message tag num>

getValue and byItems <value indexes> setToField <message tag num>

getValue and byTags <tags> setToField <message tag num>

 

Set static value

Fill a specific column with the specified static value.

setToColumn <column name> value <static value>

setToColumn <column name> value obtainedFrom tag <tag num of incoming message> 

 

Fields block

Using in fields block (set to field indirect from history):

set <message tagNum> value from history <history name> by key

set <message tagNum> value from history <history name> key <key params>

set <message tagNum> value from history <history name> items <value tag index (simple/list)>

set <message tagNum> value from history <history name> tags <value tag (simple/list)>

 

More samples

Example. Save to history

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

package rulesDsl.rulesWithHistories

 

import com.epam.fej.routing.RoutingContext

 

import static dsl.CommonRulesDsl.rulesDSL

 

rulesDSL(routingContext as RoutingContext) {

    histories {

        memoryHistory {

            name "MemoryHistory"

            keyTags 34, 56

            valueTags 58, 56, 49

        }

    }

    messageRules {

        messageRule("Save items from all messages and all sources to history MemoryHistory") {

            action {

                saveTo "MemoryHistory"

                context exit

            }

        }

    }

}

Example. Remove from history

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

package rulesDsl.rulesWithHistories

 

import com.epam.fej.routing.RoutingContext

 

import static dsl.CommonRulesDsl.rulesDSL

 

rulesDSL(routingContext as RoutingContext) {

    histories {

        memoryHistory {

            name "MemoryHistory"

            keyTags 58

            valueTags 58, 56, 49

        }

    }

    messageRules {

        messageRule("Save to history all B messages, otherwise remove one item with

ClearCommand key") {

            action {

                ifCondition {

                    when { msgType "D" }

                    then { remove "ClearCommand" from "MemoryHistory" }

                    otherwise { saveTo "MemoryHistory" }

                }

                context exit

            }

        }

    }

}

Example. Clear history

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

package rulesDsl.rulesWithHistories

 

import com.epam.fej.routing.RoutingContext

 

import static dsl.CommonRulesDsl.rulesDSL

 

rulesDSL(routingContext as RoutingContext) {

    histories {

        memoryHistory {

            name "MemoryHistory"

            keyTags 58

            valueTags 58, 56, 49

        }

    }

    messageRules {

        messageRule("Save to history all AE message, otherwise clear full history")

 {

            source { id "session4" }

            action {

                ifCondition {

                    when { msgType "AE" }

                    then { saveTo "MemoryHistory" }

                    otherwise { clear "MemoryHistory" }

                }

                context exit

            }

        }

    }

}

Example. Show history

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

package rulesDsl.rulesWithHistories

 

import com.epam.fej.routing.RoutingContext

 

import static dsl.CommonRulesDsl.rulesDSL

 

rulesDSL(routingContext as RoutingContext) {

    histories {

        memoryHistory {

            name "MemoryHistory"

            keyTags 58

            valueTags 58, 56, 49

        }

    }

    messageRules {

        messageRule("Save to history all AE message, otherwise clear full history")

{

            source { id "session4" }

            action {

                show "MemoryHistory"

                context exit

            }

        }

    }

}

Example. Message handling rules using DSL instructions and custom code in History

import com.epam.fej.routing.RoutingContext import com.epam.fej.routing.history.History import com.epam.fej.routing.history.HistoryKey import com.epam.fej.routing.history.HistoryValue import static dsl.CommonRulesDsl.rulesDSL String historyName = "ClOrdIdRoutingTable" rulesDSL(routingContext as RoutingContext) { histories { db("mysql") { dbHistory { name historyName table { name "ClOrdIdTable" keys { column { name "MsgType" tag 34 } column { name "SeqNum" tag 35 dataType "Int" } } values { column { name "ClOrdId" tag 11 } column { name "SenderCompID" tag 49 } column { name "TargetCompID" tag 54 } } } } } } messageRules { messageRule("Rule1. Save to the history") { condition { msgType "D" } action { saveTo historyName context exit } } messageRule("Rule2. Replace the incoming message tag with a value from the history") { condition { msgType "B" } action { history(historyName) { getValue and byTags 11 setToField 58 } sendTo "session1" context exit } } messageRule("Rule3. Retrieve value from the history and redirect an incoming message based on the received data") { condition { msgType "AE" } action { custom { ctx -> History history = routingContext.getHistory(historyName) HistoryKey key = history.prepareKey(ctx.message) HistoryValue value = history.get(key) //Get saved ClOrdId value String savedClOrdId = value.get().get(0) //Send an incoming message through the destination endpoint with the same identifier as the received ClOrdId routingContext.getDestinationById(savedClOrdId).send(ctx.message) history.releaseResources(key, value) } context exit } } } }



Stored procedures

A comprehensive description of the procedure, its structural placement within the declared history, has already been provided in this section.

In this block, we will review the general structure, delve more deeply into the API and invocation methods, and discuss customization possibilities.

Structure template

In the code block below, variants of storage procedure configurations are provided again.

According to the given templates, every template will be discussed further, followed by practical implementation. Each type of parameter (IN/OUT, returned value) can be combined according to the procedure structure.

Procedure template

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

import com.epam.fej.routing.RoutingContext

  

import static dsl.CommonRulesDsl.rulesDSL

  

rulesDSL(routingContext as RoutingContext) {

    histories {

        db(<dataSource>){

            procedures {

                //Template 1. Procedure without parameters, with unique name

                procedure (<procedure name>)

                //Template 2. Procedure without parameters and not unique name.

 Identify it by id.

                procedure (<procedure name>){

                    id <identifier, String>

                }

                //Template 3. Procedure with incoming parameters

                procedure (<procedure name>){

                    id <identifier>

                    inParams {

                        //Incoming parameter description, template 3.1.

                        //Get value from incoming FIXFieldList as String,

                        //and pass as inParam to the procedure

                        fixParam <tagNum, data type - String>

 

                        //Incoming parameter description, template 3.2.

                        //Get value from incoming FIXFieldList as String if exists or set default value,

                        //and pass as inParam to the procedure

                        fixParam <tagNum, data type - String>, <default value, String>

 

                        //Incoming parameter general description, template 3.3.

                        param {

                            //Define the tag number of the incoming fix message, which will be retrieved and passed as a procedure parameter

                            tag <tag num, optional>

                             

                            //All possible meanings in the detailed description above. Optional, default - String.

                            dataType <data type>

                             

                            //This value is used if the determined value by tag number was not found. Optional.

                            defaultValue <default value, optional>

                             

                            //Pass the full incoming fix message as a byte array to the procedure parameter.

                            //The highest priority, other instructions for this parameter will be ignored.

                            saveFullMessage <value: true/false, optional>

                        }

                    }

                }

                //Template 4. Procedure with OUT parameter

                procedure (<procedure name>){

                    id <identifier>

                    outParams {

                        //Declared procedure OUT name and type (see java.sql.Types.*)

                        param <procedure param name>, <java.sql.Types.<var>>

                    }

                }

                // Template 5. Procedure, that return ResultSet

                procedure (<procedure name>){

                    id <identifier>

                    outParams {

                        //Lambda. Custom implementation of method to map each row of data in the ResultSet.

                        rowMapper { <RowMapper implementation> }

                    }

                }

            }

 

}

 

Name

Description

Procedure sample, MsSQL

Declaration

Call

Procedure without parameter(-s)

 

  1. Specify procedure name

  1. Add id if name is not unique

  1. Use name or id (if specified) to call

USE [stored_procedures]
GO

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[truncateStored]
AS
BEGIN
    truncate table  simpleTable;
END

 

// Template 1:

procedure("truncateStored") 

action { callProcedure

"truncateStored" }

// Template 2

procedure("truncateStored")

{ id "truncate" }

// Import section

import com.epam.fej.routing.RoutingContext
import com.epam.fej.routing.history.db.

procedure.Procedure
import static dsl.CommonRulesDsl.rulesDSL

// Action block

custom { ctx →

  Procedure procedure =

 routingContext.getProcedure

("truncate")

  int affRow =

procedure.executeCustomUpdate

()

}

action { callProcedure "truncate" }

Procedure with incoming parameter(-s)

  1. Specify procedure name

  1. Add id if name is not unique

  1. Describe incoming params using appropriate structure

  1. Order is important and have to match with declared procedure description

  1. Use name or id (if specified) to call

USE [stored_procedures]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[insert_default]
    @num INT,
    @typeMsg VARCHAR(255)
AS
BEGIN
    INSERT INTO simpleTable(SeqNum, MsgType, SenderCompID, TargetCompID, Text58)
    VALUES(@num, @typeMsg, 'DefaultSender', 'DefaultTarget', 'Default58');
END

// Template 3

procedure("insert_default") {

  id "saveDefault"

  inParams {

        // Template 3.3

    param {

      tag 34

      dataType "Int"

    }

         // Template 3.1

    fixParam 56

  }

}

// Get from incoming FIXFieldList 34 and 56 tag, pass as procedure param

action { callProcedure "saveDefault" }

// Pass static value. Scope - current message

rule's action

action { callProcedure "saveDefault",

 26, "TargetID" }

//override template for incoming message.

 Scope - current message rule's

// action

action {

  callProcedure ("saveDefault") {

    param {

      tag 34

      dataType "Int"

      defaultValue 77

    }

         // Template 3.2

    fixParam 58, "DefaultValue"

  }

}

Procedure with OUT parameter (-s)

  1. Specify procedure name

  1. Add id if name is not unique

  1. Describe OUT params using appropriate structure

  1. Order is important and have to match with declared procedure description

  1. See available types from java.sql.Types

  1. Use name or id (if specified) to call

USE [stored_procedures]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[select_as_out]
    @countByType INT OUTPUT
AS 
BEGIN
    SELECT @countByType = count(*) from simpleTable;
END

// Template 4

procedure("select_as_out") {

  id "outHistory"

  outParams {

    param "countByType", Types.INTEGER

  }

}

//Import part

import com.epam.fej.routing.RoutingContext
import com.epam.fej.routing.history.db.

procedure.Procedure
import static dsl.CommonRulesDsl.rulesDSL
import java.sql.Types

//action block

action {

  custom { ctx →

    Procedure procedure = routingContext.

getProcedure("outHistory")

    Map resMap = procedure.

executeRequestReturnOutParams()

    int res = resMap.get("countByType")

    logger.info("Count by type res: $res")

  }

}

Procedure,  that return result set

  1. Specify procedure name

  1. Add id if name is not unique

  1. Implement RowMapper or write expession for extracting data from received ResultSet (see 

RowMapper)

  1. Use custom block, get procedure by name or id (if specified), call appropriate method for getting data.

USE [stored_procedures]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER procedure [dbo].[select_to_result_set]
AS
BEGIN
    SELECT * from simpleTable;
END

// Template 5

procedure("select_to_result_set") {

  id "selectWithRowMapper"

outParams {rowMapper { rs, num →

      InnerSimpleTable row = new InnerSimpleTable()

      row.msgType = rs.getString("MsgType")

      row.seqNum = rs.getInt("SeqNum")

      row.senderCompId = rs.getString("SenderCompID")

      row.targetCompId = rs.getString("TargetCompID")

      row.text58 = rs.getString("Text58")

      return row

    }

  }

}

//import block

import com.epam.fej.routing.

RoutingContext
import com.epam.fej.routing.

history.db.procedure.Procedure
import static dsl.CommonRulesDsl.rulesDSL

//row entity (can be declared before/after

 rulesDsl block in rules.groovy or imported)

class InnerSimpleTable {

  public String msgType

  public int seqNum

  public String senderCompId

  public String targetCompId

  public String text58

  @Override

   String toString() {

    return "SimpleTableRow

{msgType=$msgType;\t" +

    "senderCompID=$senderCompId;\t" +

    "targetCompID=$targetCompId;\t" +

    "seqNum=$seqNum;\t" +

    "text58=$text58}\n"

  }

}

//action block

action {
  custom { ctx ->
    Procedure procedure = routingContext

 .getProcedure("selectWithRowMapper")

    List resList = procedure.executeRequest()

    logger.info("Size: ${resList.size()}")

    logger.info("Res: $resList")
  }
}

Configuration and call procedures

 

 

Procedure class API

The object described in the corresponding configuration block can be obtained through the routingContext by calling it by the appropriate name or id.

Procedure procedure = routingContext.getProcedure("outHistory")

All methods are available to the user for  selecting the one that meets the requirements.

The table below lists the methods existing in the Procedure class. They are used through customized blocks depending on the declared structure.

Method

Description 

Procedure configuration example

Custom block example

Procedure#

executeUpdate()

Executes a procedure request for updates (e.g., UPDATE, INSERT, TRUNCATE, etc.).

@return int - the quantity of affected rows

procedure("truncateStored") 

 

Example

1

2

3

4

5

6

7

8

9

10

11

12

// Imports

import com.epam.fej.routing.RoutingContext

import com.epam.fej.routing.history.db.procedure.Procedure

import static dsl.CommonRulesDsl.rulesDSL

 

/***/

//Action block

 

custom { ctx →

  Procedure procedure = routingContext.getProcedure("truncate")

  int affRow = procedure.executeCustomUpdate()

}

Procedure#

executeUpdate(Object[] objects)

Executes a procedure request for updates (e.g., UPDATE, INSERT, TRUNCATE, etc.).

@param objects - an array of incoming parameters

@return int - the quantity of affected rows

procedure("insert_default") {

  id "saveDefault"

  inParams {

    param {

      tag 34

      dataType "Int"

    }

    fixParam 56

  }

}

 

Example

1

2

3

4

5

6

7

8

9

10

11

12

// Imports

import com.epam.fej.routing.RoutingContext

import com.epam.fej.routing.history.db.procedure.Procedure

import static dsl.CommonRulesDsl.rulesDSL

 

/***/

//Action block

 

custom { ctx →

  Procedure procedure = routingContext.getProcedure("insert_default")

  int affRow = procedure.executeCustomUpdate(17, "Target default")

}

Procedure#

executeUpdate(FIXFieldList msg)

Executes a procedure request for updates (e.g., UPDATE, INSERT, TRUNCATE, etc.).

According to the described configuration, it receives

and transmits values from the input message.

@param msg - an incoming FIX message

@return int - the quantity of affected rows

procedure("insert_default") {

  id "saveDefault"

  inParams {

    param {

      tag 34

      dataType "Int"

    }

    fixParam 56

  }

}

 

Example

1

2

3

4

5

6

7

8

9

10

11

12

// Imports

import com.epam.fej.routing.RoutingContext

import com.epam.fej.routing.history.db.procedure.Procedure

import static dsl.CommonRulesDsl.rulesDSL

 

/***/

//Action block

 

custom { ctx →

  Procedure procedure = routingContext.getProcedure("insert_default")

  int affRow = procedure.executeCustomUpdate(ctx.message)

}

Procedure#

executeRequest()

Executes a procedure request which returns some values (SELECT). 

@return a list of objects according to the mapper

procedure("selectRS") {

    outParams {

      rowMapper { rs, num →

      return rs.getString("Text58")

    }

  }

}

 

Example

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

// Imports

import com.epam.fej.routing.RoutingContext

import com.epam.fej.routing.history.db.procedure.Procedure

import static dsl.CommonRulesDsl.rulesDSL

 

/***/

//Action block

 

action {

  custom { ctx ->

    Procedure procedure = routingContext.getProcedure("selectRS")

    List resList = procedure.executeRequest()

    logger.info("Size: ${resList.size()}")

    logger.info("Res: $resList")

  }

}

Procedure#

executeRequest(Object[] objects)

Executes a procedure request which returns some values (SELECT).

@param objects - an array of incoming parameters

@return a list of objects according to the mapper

procedure("select_to_result_set") {

  id "selectWithRowMapper"

  inParams { fixParam 58 } 

  outParams {

    rowMapper { rs, num →

      return rs.getString("Text58")

    }

  }

}

 

Example

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

// Imports

import com.epam.fej.routing.RoutingContext

import com.epam.fej.routing.history.db.procedure.Procedure

import static dsl.CommonRulesDsl.rulesDSL

 

/***/

//Action block

 

action {

  custom { ctx ->

    Procedure procedure = routingContext.getProcedure("selectRS")

    List resList = procedure.executeRequest("Default text")

    logger.info("Size: ${resList.size()}")

    logger.info("Res: $resList")

  }

}

Procedure#

executeRequest(FIXFieldList msg)

Executes a procedure request which returns some values (SELECT).

According to the described configuration, it receives

and transmits values from the input message.

@param msg - an incoming FIX message

@return a list of objects according to the mapper

procedure("select_to_result_set") {

  id "selectWithRowMapper"

  outParams {

    rowMapper { rs, num →

      return rs.getString("Text58")

    }

  }

}

 

Example

// Imports

import com.epam.fej.routing.RoutingContext

import com.epam.fej.routing.history.db.procedure.Procedure

import com.epam.fix.message.RawFIXUtil

 

import static dsl.CommonRulesDsl.rulesDSL

import java.sql.Types

 

/***/

// Action block

 

action {

  custom { ctx ->

    List resList = procedure.executeRequest(ctx.message)

    logger.info("Size: ${resList.size()}")

    logger.info("Res: $resList")

  }

}

Procedure#

executeRequestReturnOutParams()

Executes a procedure request that returns some values via OUT parameters.

 

@return a map of named objects

procedure("select_fullMsg") {

  outParams {
    param "fullMsg", Types.BINARY
  }
}

 

Example

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

// Imports

import com.epam.fej.routing.RoutingContext

import com.epam.fej.routing.history.db.procedure.Procedure

import com.epam.fix.message.RawFIXUtil

 

import static dsl.CommonRulesDsl.rulesDSL

import java.sql.Types

 

/***/

//Action block

 

action {

  custom { ctx ->

   Procedure pr1 = routingContext.getProcedure("select_fullMsg")

   byte[] msgBytes = (byte[]) pr1.executeRequestReturnOutParams()

                                                  .get("fullMsg")

    FIXFieldList msgFromDB = RawFIXUtil.getFIXFieldList(msgBytes)

    logger.info("Saved msg: $msgFromDB")

  }

}

Procedure#

executeRequestReturnOutParams (Object[] objects)

Executes a procedure request that returns some values via OUT parameters.

@param objects - an array of incoming parameters

@return a map of named objects

procedure("select_as_out") {

  inParams { fixParam 35 }
  outParams {
   param "countByType", Types.INTEGER
  }
}

 

Example

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

// Imports

import com.epam.fej.routing.RoutingContext

import com.epam.fej.routing.history.db.procedure.Procedure

import static dsl.CommonRulesDsl.rulesDSL

 

/***/

//Action block

 

action {  

  custom { ctx ->    

    Procedure pr1 = routingContext.getProcedure("select_as_out")

    int counter= (int) pr1.executeRequestReturnOutParams("B")                                                       .get("countByType")

    logger.info("Quantity of msgs with type B : $counter")  

  }

}

Procedure#executeRequestReturnOutParams

(FIXFieldList msg )

Executes a procedure request that returns some values via OUT parameters.

@param msg - an incoming FIX message

@return a map of named objects

procedure("select_as_out") {

  inParams { fixParam 35 }
  outParams {
   param "countByType", Types.INTEGER
  }
}

example

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

// Imports

import com.epam.fej.routing.RoutingContext

import com.epam.fej.routing.history.db.procedure.Procedure

import static dsl.CommonRulesDsl.rulesDSL

 

/***/

//Action block

 

action {

  custom { ctx ->

    Procedure procedure = routingContext.getProcedure("select_as_out")

    Map resMap = procedure.executeRequestReturnOutParams(ctx.message)

    logger.info("Res: $resMap ")

  }

}

Full rules.groovy with some samples

 

rules.groovy                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             Collapse source

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

import com.epam.fej.routing.RoutingContext

import com.epam.fej.routing.history.db.procedure.Procedure

 

import java.sql.Types

 

import static dsl.CommonRulesDsl.rulesDSL

 

class InnerSimpleTable {

    public String msgType

    public int seqNum

    public String senderCompId

    public String targetCompId

    public String text58

 

    @Override

    String toString() {

        return "SimpleTableRow{msgType=$msgType;\t" +

                "senderCompID=$senderCompId;\t" +

                "targetCompID=$targetCompId;\t" +

                "seqNum=$seqNum;\t" +

                "text58=$text58}\n"

    }

}

 

rulesDSL(routingContext as RoutingContext) {

    histories {

        db("msSql") {

            procedures {

                procedure("truncateStored") {

                    //procedure without in/out params

                    id "truncate"

                }

                procedure("insert_default") {

                    //procedure only with inParam

                    id "saveDefault"

                    inParams {

                        fixParam 56

                        param {

                            tag 34

                            dataType "Int"

                        }

                    }

                }

                procedure("select_as_out") {

                    outParams {

                        param "fullMsg", Types.BINARY

                    }

                }

 

                procedure("select_as_out") {

                    //procedure only with OUT param

                    id "outHistory"

                    outParams {

                        param "countByType", Types.INTEGER

                    }

                }

 

                procedure("select_to_result_set") {

                    //procedure only with RS

                    id "selectWithRowMapper"

                    outParams {

                        rowMapper{ rs, num ->

                            InnerSimpleTable row = new InnerSimpleTable()

                            row.msgType = rs.getString("MsgType")

                            row.seqNum = rs.getInt("SeqNum")

                            row.senderCompId = rs.getString("SenderCompID")

                            row.targetCompId = rs.getString("TargetCompID")

                            row.text58 = rs.getString("Text58")

                            return row

                        }

                    }

                }

            }

        }

    }

 

    messageRules {

        messageRule("Fill history") {

            condition {

                msgType "D"

            }

            action {

                callProcedure "saveDefault"

                context exit

            }

        }

        messageRule("Truncate history") {

            condition {

                msgType "AE"

            }

            action {

                callProcedure("truncate"){

 

                }

                context exit

            }

        }

        messageRule("Message name count") {

            action {

                custom { ctx ->

                    Procedure procedure = routingContext.getProcedure("outHistory")

                    Map resMap = procedure.executeRequestReturnOutParams()

                    int res = resMap.get("countByType")

                    logger.info("Count by type res: $res")

                }

                context exit

            }

        }

        messageRule("Custom call truncate"){

            action {

                custom {

                    ctx ->

                        Procedure procedure = routingContext.getProcedure("truncate")

                        procedure.executeUpdate()

                }

                context exit

            }

        }

        messageRule("Custom. Get object map") {

            condition {

                msgType "AE"

            }

            action {

                custom { ctx ->

                    Procedure procedure = routingContext.getProcedure("selectWithRowMapper")

                    List resList = procedure.executeRequest()

                    logger.info("Size: ${resList.size()}")

                    logger.info("Res: $resList")

                }

                context exit

            }

        }

    }

}

 

Variables storage

DSL creation provides first description of the builder to create a set of instructions and then to execute them. At the same time, like in ordinary functions, these instructions do not have the classical work with variables.

Sometimes, it can be needed to use them, for example, for checking in several places or writing to several tags. For such cases, it is more convenient to save the value once obtained and then reuse it later.
Of course, for complex cases customizable blocks can always be used where its possible to work with dsl directly. 

In cases of get/set - for the action block a variable storage is implemented with the scope of the same action execution.

 

History configuration for examples: 

histories {
  memoryHistory {
    name "testHistory"
    keyTags 49, 38
    valueTags 11, 55, 44
  }
}

Rule part

Description

Template

Example

Set to varStorage

You can set values through the following instructions.

History block

From the received list of values <key params>, prepare the corresponding key for the history. Retrieve the value and save it to the variable storage under the specified object key <var name> (typically using String ).

<key params> - a list of objects for which the necessary key will be prepared to get the value

<var name> - the name of the variable in varStorage

history (<history name>) {

  get <key params> asVariable <var name>

}

example

1

2

3

4

5

action {

  history("testHistory") {

    get "TestCompID1", "10" asVariable "Value"

  }

}

From the received list of values <key params>, prepare the corresponding key for the history. Retrieve the value, get it's items by indexes <value tag index (simple/list)> and save them as list to the variable storage under the specified object key <var name> (typically using String ).

<key params> - a list of objects for which the necessary key will be prepared to get the value

<value tag index (simple/list)> - index number in the part of HistoryValue

<var name> - the name of the variable in varStorage

history (<history name>) {

  get <key params> items <value tag index (simple/list)>

asVariable <var name>

}

example

1

2

3

4

5

action {

  history("testHistory") {

    get "TestCompID1", "10"  items 0, 3 asVariable 

"Value"

  }

}

From the received list of values <key params>, prepare the corresponding key for the history. Retrieve the value, get it's items by tags <value tag (simple/list)> and save them as list to the variable storage under the specified object key <var name> (typically using String ).

<key params> - a list of objects for which the necessary key will be prepared to get the value

<var name> - the name of the variable in varStorage

<value tag (simple/list)> - tag number in the part of HistoryValue

history(<history name>){

  get <key params> tags <value tag (simple/list)>

asVariable <var name>

}

example

1

2

3

4

5

action {

  history("testHistory") {

    get "TestCompID1", "10" tags 11, 55, 44 asVariable "Value"

  }

}

From the incoming message, prepare the corresponding key for the history. Retrieve the history value and save it to the variable storage under the specified object key <var name> (typically using String).

<var name> - the name of the variable in varStorage

history (<history name>) {

  getValue and saveAsVariable <var name>

}

example

1

2

3

4

5

action {

  history("testHistory") {

    getValue and saveAsVariable "Value"

  }

}

From the incoming message, prepare the corresponding key for the history. Retrieve the value?, get it's items by indexes and save them as list to the variable storage under the specified object key <var name> (typically using String).

<value tag index (simple/list)> - index number in the part of HistoryValue

<var name> - the name of the variable in varStorage

history (<history name>){

  getValue and byItems <value tag index (simple/list)>

saveAsVariable <var name>

}

example

1

2

3

4

5

action {

  history("testHistory") {

    getValue and byItems 0, 3 saveAsVariable "Value"

  }

}

From the incoming message, prepare the corresponding key for the history. Retrieve the value, get it's items by tags <value tag (simple/list)> and save them as list to the variable storage under the specified object key <var name> (typically using String).

<var name> - the name of the variable in varStorage

<value tag (simple/list)> - tag number in the part of HistoryValue

history (<history name>){

  getValue and byTags <value tag (simple/list)>

saveAsVariable <var name>

}

example

1

2

3

4

5

action {

  history("testHistory") {

    getValue and byTags 11,  55, 44 saveAsVariable "Value"

  }

}

 

 

 

 

Get from varStorage

You can get values through the following instructions

Fields block

Retrieve the value from the variable storage, stored under the specified object key <var name>,  and assign it to the corresponding field defined by the specified tag <msg tag num> in the input message.

<var name> - the name of the variable in varStorage

<msg tag num> - tag of incoming message

fields {

  set <msg tag num> value from variables <var name>

}

example

1

2

3

4

5

action {

  fields {

    set 58 value from varStorage "Value"

  }

}

Root action block

Fill a specific table column with the specified static value.

Set this value if declared by config was not founded. 

Only for JDBC history.

<var name> - the name of the variable in varStorage

<column name> - name of table column

setToColumn <column name> value obtainedFrom

varStorage <var name>

example

1

2

3

action {

  setToColumn "Text58" value obtainedFrom varStorage "Value"}

Remove var from varStorage

Root action block

Remove specified item from var storage

<var name> - the name of the variable in varStorage

remove <var name> from variables

example

1

2

3

action {

  remove "Value" from varStorage

}

Clear varStorage

Root action block

Clear variable storage

clear variables

example

1

2

3

action {

  clear varStorage

}

Custom block

Custom block

Get varStorage as map and save key-value pair.

action {
  custom { ctx ->
    ctx.getVarStorage().put(<var name>, <object>)
  }
}

example

1

2

3

4

5

action {

  custom { ctx ->

    ctx.getVarStorage().put("varName", "ValueFromVarStorage")

  }

}

Custom block

Get varStorage as map and get saved value by key.

action {
  custom { ctx ->
   String value = ctx.getVarStorage().get(<var name>)
  }
}