Groovy Examples

This section shows several Groovy language constructions that you should be aware of when writing dynamic logic.

Accessing Field Values

This section describes how to access fields of an object. Each subsection describes a different field type. The value of a fixed field can be accessed in dynamic logic using this syntax: <object>.<field name>. Following example returns the value of the claimed amount field of a claim line:

claimLine.claimedAmount

Of course, this only works when the claimLine object is available in the dynamic logic, either passed in as a parameter, or retrieved in the dynamic logic using the search method. The same applies to the next examples. The syntax for accessing a fixed field can also be used for accessing the value of a single value dynamic field.

Suppose that the claim has been extended by a single value dynamic field with the field usage name "dischargeDate". Following example returns the discharge date of a claim:

claim.dischargeDate

A multi-value dynamic field will appear as a set (an array) in your Dynamic Logic. The individual elements can be accessed like this:

relation.medicalConditions[0].code == '123.45'

If you would want to test whether this relation has a medical condition with code '123.45', the syntax to use is:

relation.medicalConditions.any { it.code == '123.45' }

Read this as: does the relation have a medical condition with code '123.45'. The method any is used in this example. Groovy has many such additional methods. The official Groovy documentation provides an extensive overview of their functionality and can be an extremely useful source of information. For example, the http://docs.groovy-lang.org/latest/html/groovy-jdk/java/lang/Iterable.html#any (groovy.lang.Closure)[any] method.

Closures

Many of the Groovy methods (such as the aforementioned any method) use a so-called closure. As the Groovy documentation on http://docs.groovy-lang .org/docs/latest/html/documentation/#closures[Closures] says, a closure is simply a block of code that can take arguments and return a value. Such blocks of code generally specify the behaviour of the method to which it is passed, for example the predicate for the _any method, specifying which item to search for. By default, a closure’s input parameter is called "it". For more elaborate closures, a descriptive name might improve readability:

relation.medicalConditions.any { condition -> condition.code == '123.45' }

Accessing Insurable Entity

Suppose the following insurable entity types are configured :

Code Plural Display Name Singular Display Usage Name

CAR

Cars

Car

servicedCar

PERSON

Persons

Person

servicedPerson

HOME

Homes

Home

servicedHome

In dynamic logic in order to access the attributes of an insurable entity; the sample groovy is:

// Insurable Person
if (claimLine.servicedEntity.insurableEntityType.code == 'PERSON'){
  entityCode = claimLine.servicedEntity.person.code
}

// Insurable Object
if (claimLine.servicedEntity.insurableEntityType.code == 'CAR'){
  entityCode = claimLine.servicedEntity.code
}

Alternately, the code of the person can also be accessed with the configured usage name:

claimLine.servicedPerson.code

The code of a car can be access by:

claimLine.servicedCar.code

Assigning Attributes

Assigning a value to a variable or attribute can be done with a single equals-sign (=), not to be mixed up with the double equals-sign (==), which is used for comparisons:

if (object.attribute == 123) {
  variable = 'abc'
}

Every dynamic logic has a specific purpose. Some are meant to determine a certain value, while others are meant to modify data, by assigning attributes. If an attribute is not intended to be modified, any assignments will lead to a technical error. A logic’s signature description will explain a logic’s purpose and which attributes are allowed to be modified.

When assigning a value to an attribute, the value needs to be of the correct class. For example, assigning the parameterAlias attribute of a ParameterValue object, needs to be an instance of a ParameterAlias, and cannot be e.g. a String. The following will not work:

parameterValue.parameterAlias = 'ALIASCODE'

To resolve such a situation, a reference to an actual ParameterAlias instance is required. This can be obtained either via another attribute, if available, or by using a search expression, based on the alias' code.

Creating a new object instance and assigning all attributes, can be done in one go, using a Named argument constructor:

def parameterValue = new ParameterValue( parameterAlias : PARD.parameterAlias
                                       , numberOfUnits  : PARV.numberOfUnits
                                       , amount         : PARV.amount
                                       , percentage     : PARV.percentage
                                       , startDate      : POCP.startDate
                                       , endDate        : POCP.endDate
                                       )

Assigning Sets of Attributes

When creating Claim Transactions, you will be writing Dynamic Logic that transfers values from a claim to a ctrClaim. For example:

ctrClaim.claimDate = claim.claimDate
ctrClaim.objectVersionNumber = claim.objectVersionNumber
ctrClaim.authorizationCode = claim.authorizationCode

This can be written in several different ways. First, using the "with":

ctrClaim.with {
  claimDate = claim.claimDate
 objectVersionNumber = claim.objectVersionNumber
  authorizationCode = claim.authorizationCode
}

Knowing that we can access an attribute as a map entry in Groovy, we can also rewrite this to:

[ 'claimDate'
, 'objectVersionNumber'
, 'authorizationCode'
].each { fieldname ->
  ctrClaim[fieldname] = claim[fieldname]
}

A variation on the above is:

[ 'claimDate'
, 'objectVersionNumber'
, 'authorizationCode'
].each { fieldname ->
  ctrClaim."$fieldname" = claim."$fieldname"
}

Comparing Lists

If you want to compare a list (e.g. a set of modifiers), then be aware that the Groovy == operator will only see the lists as equivalent if they are in the same order. If the order doesn’t matter (logically a set rather than a list) then you need to use an alternative. See the example below:

list0 = ["a", "b"]
list1 = ["a", "b"]
list2 = ["b", "a"]

assert list0 == list1
assert list1 != list2
assert list1-list2 == []
assert list2-list1 == []

// Test for list equality
assert ((list1-list2)+(list2-list1)) == []
// Alternative syntax
assert list1.containsAll(list2) && list2.containsAll(list1)

Omitting the Return Statement

Consider the following validation that checks that a numerical values is less than 50:

if (value < 50) {
 return true
} else {
 return false
}

Although the check specified above is perfectly valid, we can make it shorter because groovy allows a logical statement, amounting to a boolean, to be a return value as well:

return value < 50

If you do not specify a return statement, Groovy defaults to interpreting the value of the last executed line as the return value. So we can simplify the validation even further:

value < 50

Return Statement in a Closure

Note that you cannot return from a closure. A closure is a predefined code block in Groovy. An example is the "each" method list. Consider the following example:

prefix = triggeringClaimLine.procedure.code.substring(0,3)
claimLines.each { claimLine ->
 // the same first three digits in the procedure code
 if ( claimLine.procedure.code.startsWith(prefix) ) {
   return claimLine
 }
}
return null

Although the return statement in the closure will terminate the current iteration, it will not break from the closure nor actually return a value. The execution will simply continue with the next iteration. This example script will always reach the last line and return null.

If the desired behavior is that the return statement breaks the iteration, an explicit for-loop construct can be used:

prefix = triggeringClaimLine.procedure.code.substring(0,3)
for (claimLine in claimLines){
  // the same first three digits in the procedure code
  if ( claimLine.procedure.code.startsWith(prefix) ) {
    return claimLine
 }
}
return null

Alternatively, use a closure with the find method. The behavior of this closure is that it terminates as soon as it finds a list item that meets the scripted condition:

prefix = triggeringClaimLine.procedure.code.substring(0,3)
return claimLines.find { claimLine ->
  // the same first three digits in the procedure code
  claimLine.procedure.code.startsWith(prefix)

Logging

From dynamic logic it is possible to write information to the application’s log file. This can be a useful tool for debugging logic and to gain more insight into its execution. It can be used to figure out what the value of a certain variable is at runtime, or to follow the code path that is taken.

For this purpose, each dynamic logic has an input bind variable called log. That variable refers to a logger instance, which has methods that are named after the different log levels. For more information about log levels, see the Logging and Auditing Operations chapter of the Operations Guide. For example, an INFO level message can be logged using the info method:

log.info("Hello world!")

Each log method allows further input parameters for message variables, starting from 0, like so:

log.debug("Value for member {0} is: {1}", person.code, val)

A log statement from dynamic logic will only result in a message being written to the application log file, if the configured log level threshold is equal to or lower than the log level of the log statement. If the configured log level threshold has been set to for example WARN, then only errors and warnings will be written to the log file, so a log.info statement will have no effect. The log level threshold can be configured per dynamic logic, with the logger’s name set to "ohi.dynamiclogic." followed by the logic’s code in lowercase:

<logger name="ohi.dynamiclogic.my_dylo_code_123" level="debug"/>

Note: be careful not to end your dynamic logic with a log statement, as this might interfere with the default way Groovy implicitly handles return values.

Message Placeholder Values

Validation dynamic logic, and certain conditions, can be associated with a message. These are the messages that appear when the condition or validation is violated, for example by an integration point or operator who is trying to set a dynamic field value. For example, it is possible to create a validation that checks if a numerical value is less than 50, which is tied to the message:"Value must be less than 50". When a claims operator would try to enter a value for that field equal or greater than 50, the entry would fail and that message would appear.

Dynamic logic that is directly tied to a message can actually set that message’s placeholder values. So, rather than including the "50" as message text, you could alternatively create a message with a placeholder instead: "Value must be less than {0}". Using a placeholder allows for greater re-usability of that particular message. The actual value of "{0}" is then determined at run time, during the execution of the dynamic logic to which it is tied.

To make this work, the dynamic logic validation would have to look like this:

// setting the max value that will be used both to validate and to appear in the message
maxValue = 50
// saying that placeholder {0} must be replaced by the value of maxValue
errors[0] = maxValue
// the actual validation: the field's value is less than maxValue
return value < maxValue

Regular Expressions

Let’s assume we have a license plate format which looks like this: 99-XXX-99. This can easily be implemented using a regular expression and the match (==~) operator:

value ==~ /\d{2}-[A-Z]{3}-\d{2}/

This looks complicated until we break it down:

\d{2}

\d means a digit. {2} means exactly 2 occurences.

[A-Z]{3}

[A-Z] means a character in the range A-Z. [A-Z]{3} means exactly 3 uppercase characters. Now suppose we would want to check whether a value is a valid email address. This check can also be implemented with a regular expression:

value ==~ /[_A-Za-z0-9-]+(\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*(\.[_A-Za-z0-9-]+)/

Suppose a country knows two formats for its bank accounts. One starts with a P followed by 3-7 digits. The other consists of 9-10 digits with a check sum of 11. The dynamic logic that validates a value to be one of these two formats:

// Match for first  format
if (value ==~ /P\d{3,7}/) {
  return true
}
// Match for second format
if (value ==~ /\d{9,10}/) {
  // Perform the checksum calculation
  j = value.length()
  total = 0
    for( i in 0 .. value.length()-1 ) {
        total += j * value[i].toInteger();
        j--
    }
  return (total % 11) == 0
} else {
  return false
}

Suppose a country knows a bank account format that enforces that either the last two positions of the bank account should be equal to the rest of the division of the earlier positions by 97 OR the last two positions of the bank account should be equal to 97 if the rest of the division of the earlier positions by 97 equals zero.

Example: 884 2337123 65 is a correct bank account because: 8842337123 modulus 97 is 91158114 with rest 65

Integer checksum = value[-2..-1].toInteger()  // Last 2 digits
Long base = value[0..-3].toLong()
Integer mod = base%97
if (mod==0) {
  return checksum == 97
} else {
  return checksum == mod
}

Consider a validation for IBAN numbers. An example IBAN to be validated is NL69PSTB0001234567.

IBAN consists of a country code (2 positions, alphanumeric), a check number (2 positions, numeric) and an identification (maximum of 30 positions, alphanumeric). The check number should be equal to the number that is arrived at by the following algorithm:

  • take the identification (the input value with the first 4 characters taken off)

  • add the country code to the right

  • replace all characters by their position in the alphabet +9 (so A=10, B=11 etcetera)

  • add two zeros to the right

  • modulus97, determine rest

  • subtract this rest from 98

Example: the identification of a Dutch IBAN bank account number from the Postbank is PSTB0001234567

  • PSTB0001234567

  • PSTB0001234567NL

  • 2528291100012345672321

  • 252829110001234567232100

  • 252829110001234567232100 modulus 97 has rest 29

  • 98 - 29 = 69

So the complete IBAN should be NL69PSTB0001234567

// length is at least 8 if (value.size() < 8) { return false } def base =
value.substring(4) + value[0..1] Integer checkNumber = value[2..3].toInteger()
def expand = "" base.each { char c = it if (Character.isDigit(c)) { expand += c
} else { expand += (int)c-55 } } expand += "00" rest = expand.toBigInteger()%97
return (checkNumber == 98-rest)

Checking Starting Characters

Procedure and diagnosis conditions are ways to group codes based on properties of those procedures/diagnoses. A common way is to group these codes based on the starting characters of their codes. To clarify the way to write this, consider the dynamic logic to test if a value starts with '123':

value.startsWith('123')

Alternatively, you can use the "find" operator (=~)

value =~ /^123/

The characters between the slashes (//) form a regular expression. Regular expressions provide a powerful way to match strings. The ^ means the beginning of the string, so in our example, the value needs to have "123" immediately after the beginning of the value. If we wanted to check if the value contained "123" at any position, the check would be:

value =~ /123/

Using Packages

If the dynamic logic does not define a package of its own, then it is by default put in package ohi.dynamiclogic. This package can also be declared directly in the script to make the standalone testing easier. It is also possible to further structure the scripts using nested packages with the condition that the packages should always be nested inside ohi.dynamiclogic. This is done to make that these scripts are ohi specific and can security checked properly.

Using Classes

It is also possible to use the classes in the dynamic logic script. There are few caveats as compared to normal groovy scripting. You can only define one class in a script. The name of the class is same as the code of the script(uppercase). Example, script with code ADD_NUMBER.groovy

package ohi.dynamiclogic
class ADD_NUMBER {
    def add(num1, num2) {
        num1 + num2
    }
}

This can then be used in another script like following

package ohi.dynamiclogic
log.debug(new ADD_NUMBER().add(1,2))

Reusing Dynamic Logic

There may be occasions where it is desirable to reuse a piece of logic across multiple other units of dynamic logic. This can be accomplished by defining the logic that you want to reuse inside of a static method:

static def absoluteValue(value) {
  if (value < 0) {
    return value * -1;
  } else {
    return value;
  }
}

The static keyword is used to denote that the method is not tied to that particular instance of dynamic logic, but can also be used stand-alone outside of the dynamic logic that it is defined in. A static method can be referred to from another dynamic logic. To do that, the method name needs to be prefixed with the package name "ohi.dynamiclogic"(if the package was not already declared in the reusable script) and the code of the owning dynamic logic. That complete reference can be stored at the top of the dynamic logic via an import statement:

import static ohi.dynamiclogic.EXAMPLE_DYLO_CODE_123.absoluteValue;

assert absoluteValue(-10) == 10

Defining Reusable Constants

There may be occasions where it is desirable to define constants that can be used in the application across many dynamic logic. This can be accomplished by defining a reusable logic with interfaces. Suppose the following is defined in dynamic logic with code CONSTANTS.

interface SCALE {
  static final String COUPLE 		= "Couple"
  static final String ERROR 		= "Error"
  static final String FAMILY 		= "Family"
  static final String SINGLE 		= "Single"
}
interface DATES {
  static final Date HIGHDATE 	= java.sql.Date.valueOf('2999-12-31')
  static final Date LOWDATE 	= java.sql.Date.valueOf('1900-01-01')
}

interface INSURABLE_TYPES {
  static final String ADULT 		= "ADULT"
  static final String DEPENDANT 	= "DEPENDANT"
}

return

Please note the return statement in the end. This is necessary for the compiler to view the interfaces being separate classes, else it does not compile successfully.

The above reusable constant file can then used in any logic where needed by importing it. Example:

import ohi.dynamiclogic.CONSTANTS
return "abc " + SCALE.FAMILY

Extensions to dynamic logic

When dynamic logic is delivered as part of country pack deliverable, the dynamic logic scripts can be extended by customer for customer specific code. This is made possible by creating a dynamic logic with signature 'Extension' and reference to the base dynamic logic that it extends. The methods declared in the dynamic logic should be static in nature and the name of the method and parameters number and type should be matching the method that the extension is extending.

Example: Adding numbers in a list

Base Logic: ADD_NUMBERS

def customerList = getCustomerList(inputList)  // inputList is the binding to the logic
return list*.value.sum()

//Returns the customer list by using the input list or providing a totally new list.
//By default returns back the input list
def List getCustomerList(List inputList) {
   return inputList
}

Extension: ADD_NUMBERS_EXT: This logic has signature: 'Extension' and has reference to the above logic

//Customer override
def static List getCustomerList(List inputList) {
def newList = new ArrayList<>(inputList)
   newList.add(5)
   return newList
}