Groovy Examples

This page mentions several Groovy language constructions important when writing Dynamic Logic.

To configure dynamic logic, consider the following recommendations:

  1. Check the dynamic logic signature for information on input, throughput, and output restrictions.

  2. Check if there are predefined methods applicable to the use case.

  3. Use the View Objects page to determine the correct field name for an object.

    • The attribute type from the Groovy examples tables for assigning and accessing values must correlate with the View Objects page in the application. This helps in finding a specific example for a field.

Accessing Field Values

There are different ways to access fields of an object. The Oracle Health Insurance data model comprises Native Fields that are native to the system. A user can extend these fields using Dynamic Fields. The way to access both types of fields using Dynamic Logic is the same. This section describes the different methods for accessing each field type.

Table 1. Accessing Field Values
Attribute Type Type of Field Time-valid Multi-value Data Type Example

BigDecimal

Native and Dynamic

N

N

BigDecimal

person.adjustmentRate >= 0.071

Boolean

Native

N

N

Boolean

person.bankAccountNumberRelationList[0].preferred == true
// Using Groovy true
person.bankAccountNumberRelationList[0].preferred

Date

Native and Dynamic

N

N

Date

person.dateOfBirth.compareTo(date)

Datetime

Native

N

N

Timestamp

import java.time.LocalDateTime
nowMinus1yr = LocalDateTime.now().plusYears(-1)
person.objectLastUpdatedDate > java.sql.Timestamp.valueOf(nowMinus1yr)

DynamicRecord

Dynamic

N

N

All

person.reportingCategory.type == 2750

FlexCode

Dynamic

N

N

Character

person.wearsGlasses.code == "Y"

Integer

Native and Dynamic

N

N

Integer

person.leftEyePower == -5

List<BigDecimal>

Native and Dynamic

N

Y

List of Big Decimals

person.payments[0].round() == 25000

List<Date>

Native and Dynamic

N

Y

List of Dates

person.contactDate.compareTo(date)

List<DynamicFieldPeriodBigDecimal>

Dynamic

Y

Y

List of Big Decimals

person.height[0].value >= 6.2
person.height.asOf(date)[0] >= 6.2

List<DynamicFieldPeriodBigDecimal>

Dynamic

Y

N

List of Big Decimals

person.height[0].value >= 6.2
person.height.asOf(date) >= 6.2

List<DynamicFieldPeriodFlexCode>

Dynamic

Y

Y

List of codes

person.paymentFrequencies[0].value.code == "SP01"
person.paymentFrequency.asOf(date)[0].code == "SP01"

List<DynamicFieldPeriodFlexCode>

Dynamic

Y

N

List of codes

person.paymentFrequencies[0].value.code == "SP01"
person.paymentFrequency.asOf(date).code == "SP01"

List<DynamicFieldPeriodString>

Dynamic

Y

Y

List of Strings

person.dependentNames[0].value == "John"
person.dependentNames.asOf(date)[0] == "John"

List<DynamicFieldPeriodString>

Dynamic

Y

N

List of Strings

person.dependentNames[0].value == "John"
person.dependentNames.asOf(date) == "John"

List<DynamicRecord>

Dynamic

N

Y

List of Dynamic Records

person.reportingCategories[0].type== 2750

List<DynamicRecordPeriod>

Dynamic

Y

Y

List of Dynamic Records

person.healthInformation[0].tabaccoStatus == "Y"
person.healthInformation.asOf(date)[0].tabaccoStatus == "Y"
person.healthInformation.asOf(date).any{ it.tabaccoStatus == "Y" && it.weight >= 75}

List<DynamicRecordPeriod>

Dynamic

Y

N

List of Dynamic Records

person.healthInformation.asOf(date)[0].tabaccoStatus == "Y" person.healthInformation[0].tabaccoStatus == "Y"
person.healthInformation.asOf(date)[0].any{ it.tabaccoStatus == "Y" && it.weight >= 75}

List<FlexCode>

Dynamic

N

Y

List of codes

person.hobbies[0].code == "Cricket"

List<Object>

Native

N

Y

List of codes

person.maritalStatusList.any{ it.maritalStatusType == "Single"}

List<String>

Native and Dynamic

N

Y

List of Strings

person.attendedSchools[0] == "Harvard"

Long

Native

N

N

Long

person.objectLastUpdatedBy != 10 //last updated by user

OhiMoney

Native and Dynamic

N

N

Amount

person.Money == 45.00

Reference Object

Native

N

N

Code

person.prefix.code == "VON"

String

Native and Dynamic

N

N

String

person.firstName == "Bob"

Optional Attributes

Optional attributes are the attributes that are left blank and have NULL values. Accessing such an attribute or a method often results in a technical error. You must check your logic before accessing an attribute that can have possible NULL values. Here is a code you can use:

if (claimLine.condition != null) {
  claimLine.condition.code == "REGULAR"
}

The Safe Navigation Operator- or ? symbol- is an alternative by Groovy to check for NULL. Here is an example of how to use the operator:

claimLine.condition == null
claimLine.condition?.code == null
claimLine.condition?.code?.toLowerCase() == null

The operator bypasses a NULL value to return a NULL value. This avoids a technical error. The operator reduces code complexity but can lead to undesirable results. It is important to use the safe navigation operator cautiously and not to use it needlessly.

For example, assume a claim line with a condition attribute. Adding a value to a condition attribute is optional. The condition attribute can have REGULAR, IRREGULAR, or NULL values.

We want to apply a bonus of 10% if a line’s condition is IRREGULAR. The following two logics check the claim line for a condition with a NULL value and later check the type of the condition:

if (claimLine.condition != null && claimLine.condition.type == "IRREGULAR") {
  claimLine.bonusPercentage = 10
} else {
  claimLine.bonusPercentage = 0
}
if (claimLine.condition?.type == "IRREGULAR") {
  claimLine.bonusPercentage = 10
} else {
  claimLine.bonusPercentage = 0
}

In both cases, only claim lines with an IRREGULAR condition receive a bonus. Lines with a REGULAR condition and without a condition do not receive a bonus.

In the second logic, when claimLine.condition?.code encounters a condition attribute without a value, it returns a NULL. This is unequal to the string IRREGULAR and thus receives a bonus.

Now, check the following two logic that apply a bonus to any claim line having a condition other than REGULAR:

if (claimLine.condition != null && claimLine.condition.type != "REGULAR") {
  claimLine.bonusPercentage = 10
} else {
  claimLine.bonusPercentage = 0
}
if (claimLine.condition?.type != "REGULAR") {
  claimLine.bonusPercentage = 10
} else {
  claimLine.bonusPercentage = 0
}

The two logic are no longer the same.

The first logic applies a bonus to claim lines that have an IRREGULAR condition.

The second logic applies the bonus to claim lines having an IRREGULAR condition and to claim lines without a condition.

Two results are different.

Accessing Insurable Entity

Suppose the following insurable entity types are configured :

Table 2. Accessing Insurable Entity
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

Following are the different methods to assign values to an attribute depending on the type:

Table 3. Assigning Attributes
Attribute Type Type of Field Time-valid Multi-value Data Type Examples

BigDecimal

Native and Dynamic

N

N

BigDecimal

newPerson.adjustmentRate = 3.456

claimLine.LegLength = 3.4

Boolean

Native

N

N

Boolean

newPerson.bankAccountNumberRelationList[0].preferred = true

claimLine.emergency = false

Date

Native and Dynamic

N

N

Date

newPerson.endDate = java.sql.Date.valueOf("2018-07-26")

claimLine.endDate = java.sql.Date.valueOf("2018-07-26")

DynamicRecord

Dynamic

N

N

All

Use the Predefined methods
addDynamicRecord, updateDynamicRecord, and deleteDynamicRecord to write Dynamic Records.
Refer to the Predefined Methods chapter for more details and examples.

FlexCode

Dynamic

N

N

Character

newPerson.wearsGlasses.code = "Y"

claimLine.addiction ="1"

Integer

Native and Dynamic

N

N

Integer

newPerson.leftEyePower = -5

claimLine.leftEyePower = -3

List<BigDecimal>

Native and Dynamic

N

Y

List of Big Decimals

newPerson.payments =[300.65,234.09] //set

newPerson.payments = 300.45 //add

newPerson.payments[2] = 112.89 //update

newPerson.payments[0] = null //remove

claimLine.payments =[334.78,12.68] //set

claimLine.payments = 112.39 //add

claimLine.payments[4] = 29.45 //update

claimLine.payments[0] = null //remove

List<Date>

Native and Dynamic

N

Y

List of Dates

newPerson.contactDate = [java.sql.Date.valueOf("2014-07-26"), java.sql.Date.valueOf("2018-08-26")] //set

newPerson.contactDate.add(java.sql.Date.valueOf("2014-08-26")) //add

newPerson.contactDate = java.sql.Date.valueOf("2014-09-21") //update

newPerson.contactDate = null //remove

claimLine.contactDate = [java.sql.Date.valueOf("2014-07-26"), java.sql.Date.valueOf("2018-08-26")] //set

claimLine.contactDate.add(java.sql.Date.valueOf("2014-08-26")) //add

claimLine.contactDate[0] = java.sql.Date.valueOf("2014-09-21") //update

claimLine.contactDate[0]=null //remove

List<DynamicFieldPeriodBigDecimal>

Dynamic

Y

Y

List of Big Decimals

newPerson.height = [new DynamicFieldPeriod(5.11, java.sql.Date.valueOf("2014-08-26"), java.sql.Date.valueOf("2018-08-26"))
, new DynamicFieldPeriod(5.12, java.sql.Date.valueOf("2014-04-26"), java.sql.Date.valueOf("2018-04-26"))] //set

newPerson.height = null
newPerson.setDynamicField("height", new DynamicFieldPeriod(5.5, java.sql.Date.valueOf("2014-08-26"), java.sql.Date.valueOf("2018-08-26"))) //nullify and replace

newPerson.height = new DynamicFieldPeriod(6.5, java.sql.Date.valueOf("2014-08-26"), java.sql.Date.valueOf("2018-08-26")) //add

newPerson.height[0].value = 6.2
newPerson.height[0].startDate = java.sql.Date.valueOf("2014-08-26") //update

Not applicable for Claims.

List<DynamicFieldPeriodBigDecimal>

Dynamic

Y

N

List of Big Decimals

newPerson.height = [new DynamicFieldPeriod(5.4, java.sql.Date.valueOf("2014-08-26"), java.sql.Date.valueOf("2015-04-26"))
, new DynamicFieldPeriod(5.5, java.sql.Date.valueOf("2015-04-27"), java.sql.Date.valueOf("2016-04-26"))] //set

newPerson.height = null
newPerson.setDynamicField("height", new DynamicFieldPeriod(6.2, java.sql.Date.valueOf("2014-08-26"), java.sql.Date.valueOf("2018-08-26"))) //nullify and replace

newPerson.height = new DynamicFieldPeriod(6969, java.sql.Date.valueOf("2014-08-26"), java.sql.Date.valueOf("2018-08-26")) //add

newPerson.height[0].value = 89
newPerson.height[0].startDate = java.sql.Date.valueOf("2014-08-26") //update

Not applicable for Claims.

List<DynamicFieldPeriodFlexCode>

Dynamic

Y

Y

List of codes

newPerson.paymentFrequencies = [["endDate": java.sql.Date.valueOf("2015-07-20"), "startDate": java.sql.Date.valueOf("2014-07-20"), "value": "MONTHLY"], ["endDate":java.sql.Date.valueOf("2016-07-20"), "startDate": java.sql.Date.valueOf("2015-07-20"), "value": "WEEKLY"]] //set

newPerson.paymentFrequencies = null
newPerson.paymentFrequencies = ["endDate": java.sql.Date.valueOf("2015-07-20"), "startDate": java.sql.Date.valueOf("2014-07-20"), "value": "QUARTERLY"] //nullify and replace

newPerson.paymentFrequencies = ["endDate": java.sql.Date.valueOf("2015-07-20"), "startDate": java.sql.Date.valueOf("2014-07-20"), "value": "BI-WEEKLY"] //add

newPerson.paymentFrequencies[0].value ="YEARLY" //update

Not applicable for Claims.

List<DynamicFieldPeriodFlexCode>

Dynamic

Y

N

List of codes

newPerson.paymentFrequencies = [["endDate": java.sql.Date.valueOf("2015-07-20"), "startDate": java.sql.Date.valueOf("2014-07-20"), "value": "MONTHLY"], ["endDate":java.sql.Date.valueOf("2016-07-20"), "startDate": java.sql.Date.valueOf("2015-07-21"), "value": "WEEKLY"]] //set

newPerson.paymentFrequencies = null
newPerson.paymentFrequencies = ["endDate": java.sql.Date.valueOf("2015-07-20"), "startDate": java.sql.Date.valueOf("2014-07-20"), "value": "QUARTERLY"]//nullify and replace

newPerson.paymentFrequencies = ["endDate": java.sql.Date.valueOf("2018-07-20"), "startDate": java.sql.Date.valueOf("2017-07-20"), "value": "BI-WEEKLY"]//add

newPerson.paymentFrequencies[0].value ="YEARLY"

Not applicable for Claims.

List<DynamicFieldPeriodString>

Dynamic

Y

Y

List of Strings

newPerson.dependentNames = [ new DynamicFieldPeriod("John", java.sql.Date.valueOf("2014-08-26"), java.sql.Date.valueOf("2018-08-26"))
, new DynamicFieldPeriod("Bob", java.sql.Date.valueOf("2014-04-26"), java.sql.Date.valueOf("2018-04-26"))] //set

newPerson.dependentNames = null
newPerson.setDynamicField("dependentNames", new DynamicFieldPeriod("John", java.sql.Date.valueOf("2014-08-26"), java.sql.Date.valueOf("2018-08-26"))) //nullify and replace

newPerson.dependentNames = new DynamicFieldPeriod("John", java.sql.Date.valueOf("2014-08-26"), java.sql.Date.valueOf("2018-08-26")) //add

newPerson.dependentNames[0].value = "James"
newPerson.dependentNames[0].startDate = java.sql.Date.valueOf("2014-08-26") //update

Not applicable for Claims.

List<DynamicFieldPeriodString>

Dynamic

Y

N

List of Strings

newPerson.dependentNames = [new DynamicFieldPeriod("John", java.sql.Date.valueOf("2014-08-26"), java.sql.Date.valueOf("2015-04-26"))
, new DynamicFieldPeriod("Bob", java.sql.Date.valueOf("2015-04-27"), java.sql.Date.valueOf("2016-04-26"))] //set

newPerson.dependentNames = null
newPerson.setDynamicField("dependentNames", new DynamicFieldPeriod("James", java.sql.Date.valueOf("2014-08-26"), java.sql.Date.valueOf("2018-08-26"))) //nullify and replace

newPerson.dependentNames = new DynamicFieldPeriod("John", java.sql.Date.valueOf("2016-08-27"), java.sql.Date.valueOf("2017-08-26")) //set

newPerson.dependentNames[0].value = "James"
newPerson.dependentNames[0].startDate = java.sql.Date.valueOf("2013-08-26") //update

Not applicable for Claims.

List<DynamicRecord>

Dynamic

N

Y

List of Dynamic Records

Use the Predefined methods
addDynamicRecord, updateDynamicRecord, and deleteDynamicRecord to write Dynamic Records.
Refer to the Predefined Methods chapter for more details and examples.

List<DynamicRecordPeriod>

Dynamic

Y

Y

List of Dynamic Records

Use the Predefined methods
addDynamicRecord, updateDynamicRecord, and deleteDynamicRecord to write Dynamic Records.
Refer to the Predefined Methods chapter for more details and examples.

List<DynamicRecordPeriod>

Dynamic

Y

N

List of Dynamic Records

Use the Predefined methods
addDynamicRecord, updateDynamicRecord, and deleteDynamicRecord to write Dynamic Records.
Refer to the Predefined Methods chapter for more details and examples.

List<FlexCode>

Dynamic

N

Y

List of codes

newPerson.hobbies = ["Cricket","Trekking"] //set

newPerson.hobbies.add("Reading")
newPerson.hobbies[3] ="Internet Surfing" //add

newPerson.hobbies[0] = null //remove

newPerson.hobbies[1] = "Video Games"//update

claimLine.hobbies = ["Cricket","Trekking"] //set

claimLine.hobbies.add("Reading")
claimLine.hobbies[3] = "Internet Surfing //add

claimLine.hobbies[0] = null //remove

claimLine.hobbies[1] = "Video Games"//update

List<Object>

Native

N

Y

List of codes

newPerson.contractAlignmentList[0].code = "PS167"

claimLine.diagnosis = "125"

List<String>

Native and Dynamic

N

Y

List of Strings

newPerson.attendedSchools = "Harvard"

claimLine.attendedSchools = "Harvard"

OhiMoney

Native and Dynamic

N

N

Amount

newPerson.totalAmount = 45.00

claimLine.totalAmount = 65

Reference Object

Native

N

N

Code

newPerson.prefix = lookUp(Prefix.class, ['code': 'DE'])

claimLine.procedure1 = "01.01"

String

Native and Dynamic

N

N

String

newPerson.suffix = "Mr"

claimLine.colorCode = "Red"

Assigning Sets of Attributes

When creating Claim Transactions, A Dynamic Logic is written 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 different ways. First, using the "with":

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

Knowing that an attribute can be accessed as a map entry in Groovy, this can be rewritten as:

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

A variation on the above is:

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

Checking Starting Characters

Procedure and diagnosis conditions are ways to group codes based on properties of those procedures or 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, 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. To check if the value contained 123 at any position, the check would be:

value =~ /123/

Closures

Many of the Groovy methods (such as the aforementioned any method) use a so-called closure. As the Groovy documentation on 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" }

Comparing Lists

To compare a list (for example, a set of modifiers), be aware that the Groovy == operator only views 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 an alternative is used. See the example below:

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

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

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

Defining Reusable Constants

There may be occasions where it is desirable to define constants that can be used in the application across many dynamic logics. 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 be used in any logic where needed by importing it. For 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 must be static in nature and the name of the method and parameters number and type must be matching the method that the extension is extending.

For 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
}

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 System Administration 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 results 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 are written to the log file, so a log.info statement has 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"/>
Be careful not to end the dynamic logic with a log statement, as this might interfere with the default way Groovy implicitly handles return values.

When using a log statement in a static method, make sure to pass the log statement as parameter. Otherwise, an error is displayed saying that it is not possible to access a non-static variable from static method. See the following example:

static def logInfo(log) {
  log.info("Hello world!")
}

Message Placeholder Values

Validation dynamic logic, and certain conditions, can be associated with a message. These messages 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 tries to enter a value for that field equal or greater than 50, the entry fails and that message appears.

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, 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 has to look like this:

// setting the max value that is 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

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, it can be made shorter because groovy allows a logical statement, amounting to a boolean, to be a return value as well:

return value < 50

If a return statement is not specified, Groovy defaults to interpreting the value of the last executed line as the return value. So the validation can be simplified even further:

value < 50

Regular Expressions

Let’s assume 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}/

Breaking it down:

\d{2}

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

[A-Z]{3}

[A-Z] means a character in the range A-Z. [A-Z]{3} means exactly 3 uppercase characters. 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 must be equal to the rest of the division of the earlier positions by 97 OR the last two positions of the bank account must be equal to 97 if the rest of the division of the earlier positions by 97 equals zero.

For 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 International Bank Account Number (IBAN). 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 must 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

For example: the identification of a Dutch IBAN from the Postbank is PSTB0001234567

  • PSTB0001234567

  • PSTB0001234567NL

  • 2528291100012345672321

  • 252829110001234567232100

  • 252829110001234567232100 modulus 97 has rest 29

  • 98 - 29 = 69

So the complete IBAN must 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)

Return Statement in a Closure

Note that there is no returning 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 terminates the current iteration, it does not break from the closure nor actually return a value. The execution simply continues with the next iteration. This example script always reaches the last line and returns 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)

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 to reuse inside a static method:

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

A 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 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;

absoluteValue(-10) == 10

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. Only one class can be defined in a script. The name of the class is same as the code of the script(uppercase). For 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))

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 must always be nested inside ohi.dynamiclogic. This is done to make that these scripts are Oracle Health Insurance specific and can security checked properly.