Expression language is a powerful special language syntax used for dynamically injecting, substituting, building, querying or manipulating data during the execution of events, workflows, forms and any other place in the application that can incorporate logic.


It can be used for a wide variety of purposes in Blue Relay, ranging from the simple use case of obtaining the title of a file/folder to the more advanced use case of using item/user attributes to query a dataset, finding a specific/list of values and then subsequently use the results to further perform another action.


Basic Syntax and Structure


To demonstrate the basic syntax and structure of expression languages, let's consider the following expression to access the title of a newly created file within an event with the "On File Create" trigger:

(( #file.title ))


Whenever writing an expression language (with the exception of a couple of places), it must always be enclosed with preceding and succeeding double parenthesis. This tells the system that the text within should be evaluated as an expression language and not interpreted as regular text. 


The # prefix is a special notation used to indicated the attached preceding text is a variable/object/data with potentially various properties and functions. In the example above, #file is used to access the newly created/uploaded file, which has many properties including the title. So the full expression #file.title is used to access the title of the new file.


 Available Data within Expression Languages


Depending on where and the context of the written expression language, they are different data available and accessible for use within your written expression. The type of data available can essentially be broken down into three categories: "always available" data, "helper/utility" data and finally situational/contextual data. 



Always Available Data


Regardless of where you specific an expression language, the user and company objects will always be available. Both are contextual in the sense that they will always be the user (in some cases "system") and company that resulted in the execution of the expression. 


For example, if there was an event with an "On File Create" trigger and User A uploaded a new file in the system, then the expression #user.name would evaluate to User A's full name and if another User B uploaded a file then #user.name would evaluate to their full name and so on for different users. 


This always available contextual data is great for if you need to action on specific Users performing specific actions. The same also applies to the company object which may not be useful if there's only one company within the system but very useful if there's sub-companies involved.


(( #user.propery ))

where some common useful properties are: id, name, firstName, lastName, defaultEmail, username, activeTasks


(( #company.property ))

where some common useful properties are: id, name, shortName



Available Helper/Utility Functions


This category of data almost exactly the same as the previous category with the exception that it is not contextual but it instead provides helper and utility functions to perform specific operations within expression languages. These operations can range from searching for an item to sorting a list. 


There are currently three utility functions: ExpressionUtil (#expr), ListUtil (#list) and MapUtil (#map)


- ExpressionUtil: provides a set of functions for a variety of purposes. There are functions for performing data set lookups, file/folder solr searches (#expr.search), finding items by path (#expr.findByPath), user/group searches (#expr.entitySearch), and converting a string to time (#expr.stringToTime) to name a few common ones. Refer to the Common and advanced section below for detailed examples and uses.


- ListUtil: provides a set of functions for operating on a set of objects within expression language. For example suppose you had obtained a list of files within a folder (via the expression #folder.items) and you wanted to get the list of titles of all those files. To achieve that, you could leverage the function explodeProperty (#list.explodeProperty) to obtain such a list (i.e #list.explodeProperty( #folder.items, 'title' ) ). Refer to the Common and advanced section below for detailed examples and uses.


- MapUtil: provides a set of functions for operating on a mapping of data. Usage of this is less common but not unrare. Refer to the Common and advanced section below for detailed examples and uses. 



Situational/Contextual Data


This is data only available in the context in which the expression language is written in. As stated previously, expression language can be used within workflows, tasks (actions) , forms, events (triggers and actions)


- Tasks/Workflows: task and item and option. Also collection criteria

- Events: depends on the type of trigger

- Can dynamically search and inject datasets, items, solr search, tasks

- form question rules




Some example expressions, their meanings and common use cases:


Expression

Meaning

Common Uses

#item.title

The title of the item

Everywhere. Item is typically either the file or folder.

#item.ancestors[0]
#item.ancestors[1]
#item.ancestors[0].title

If item is a file, [0] is stub, [1] is folder.

If item is a folder, [0] is parent folder

If item is a form of a file, [0] is file, [1] is stub and [2] is parent folder

If item is a form of a folder, [0] is parent folder.

Basically just need to account for stub if a file is involved.

Lots of places. If the event is on a file, but you want to get the folder.

#item.creator.username

The creator of the item

For sending notifications to the creator of the file/folder.

#answer.display

#answer.content

The user-friendly form of answer to a form question.

The “raw” form answer saved in the DB 

With OnQuestionAnswer triggers. Useful to do something with the answer someone just made on a form.

#item.attributes.get('Attribute Name').value

The value of the attribute “Attribute Name”

Lots of places.

#item.hasSystemAttribute(“Attribute Name)
#item.getSystemAttributeValue(“Attribute Name”)

A boolean of whether or not a system attribute Attribute Name exists.
The actual value in a null safe fetch.

For system processing. System attributes are different than attributes in that they’re not displayed on the UI. These are convenience methods.

#item.title.split("_")[2]
#item.title.split("_")[2].substring(0,4)
#item.title.replaceAll(“X”, “Y”)

String functions that can be used for further parsing/processing.

Parsing system generated names like filenames and using it to extract meaningful content to rename a file or save an attribute.

dataSet:123:#dataSet.data.?[#item.attributes.get("Attribute Name").value == #this.cells['Column1']].![cells['Column2']]

OR (the non-shorthand version)

#expr.dataSetSearch('123', ‘#dataSet.data.?[#item.attributes.get("Attribute Name").value == #this.cells['Column1']].![cells['Column2']]')

A dataset look. Uses dataset 123 to find a row where the value of Attribute Name matches the value in Column1. For the matching row, returns the value in Column2.

Lots by ESI. They import files with system generated names that have a unique key. This expression does a lookup in a data set to find the row which contains information like which workflow to activate and which assignees to use. Allows the system generated filenames to remain constant, but a BR admin can dynamically “operate” on them differently.

#item.title == ‘ABC’ ? ‘Yes’ : ‘No’

A ternary statement. If the title is ABC, the value Yes is returned. Otherwise, No is returned.

Becoming more common. Just provides some conditional logic when needed.

#form.questions[10].answers[0].display

Gets the value of question #11 (zero-based index) on a single dimensional form. Multi dimensional forms don’t have as clean a way to operate on at the moment.

With OnFormSubmit triggers. Useful to pull multiple questions in a single event when the form is submitted.

#user.username
#user.name

#user.id
#company.name

Some session specific data. Note that some events occur in the background by the system (e.g. Local File Create), but most will have this data

To use/store the current user for whatever need.

#list.findBy( #item.items, ‘status’, ‘New’ )
#list.findUniqueBy( #item.items, ‘status’, ‘New’ )

Finds one or all the child items that have a status of New

To pick a file/folder for the user to process based on status.

#list.implode( #list.explodeProperty( #list.reverse( #item.ancestors ), 'title'), '/' )

Return item’s file path

Doesn’t work. See below row.

((#list.implode(#list.explodeProperty( #list.reverseList(#item.ancestors), 'title' ), '/')))

Save above to Context Variable then use the below to remove “Home/” and filename from path:

((#path_long.substring(5, #path_long.length()-#filename.length())))

“list.reverse” doesn’t work.  Created new function “list.reverseList” to make it work. 

Used by Advantasure

(( #form.questions[11].answers[0].display != null && #form.questions[11].answers[0].displayValues.?[#this == 'Current Members']?.size() == #form.questions[11].answers[0].displayValues?.size() ))

This format is for lists with mutli-select. The expressions says - the form question with ordinal 12 is not null and the selected answer is only 'Current Members'. Please note - it means Current Members is the only selected option and does not mean that “Current Members“ is one of the selected options.

Since there can be multiple possible selections, we cannot do answer[0].matches….. format. That’s why #form.questions[11].answers[0].displayValues returns the list of all the selected options for that question. Then displayValues.?[#this == 'Current Members']?.size() here #this is used to represent each selected option (similar to a foreach loop). Then we make the comparison and get the length of the array.

Then using this #form.questions[11].answers[0].displayValues.?[#this == 'Current Members']?.size() == #form.questions[11].answers[0].displayValues?.size() we can make sure that “Current Members“ is the only option selected by comparing the size on both the sides.

Used by BCBSM for creating attributes and also at other places wherever a mutli-select list is involved.

#expr.getOwnerAnswers(#forms[0], #ownerId).get(2).content== "Approve"

 #forms is a reference to all forms belonging to a particular task. You can use #forms[0] if there is just one form, or #list.findUniqueBy( #forms, 'name', 'Review Form' ) to find a form by name.

#expr.getOwnerAnswers(#forms[0], #ownerId) returns a map of question ordinals to question answers, answered by the form owner with user id = #ownerId. For example, you can say #expr.getOwnerAnswers(#forms[0], #ownerId).get(2) to get the owner’s answer to question 2. The map will be empty if the owner didn’t answer the form at all.

A similar expression to this will be used by ESI in the future (when they switch to new relay) in the “advance” section of workflow task options.

This will help prevent users from accidentally clicking the wrong task option and proceeding down the wrong series of next tasks in the workflow. 

(( new java.text.SimpleDateFormat("yyyy-MM-dd").format(T(org.apache.commons.lang.time.DateUtils).addDays(new java.text.SimpleDateFormat("yyyy-MM-dd").parse(#item.attributes.get('Usage End Date')), -60)) ))

Date Manipulation. The example results in a date = #item.attributes.get('Usage End Date') - 60 days

addDays can be replaced with addMonths, addYears, etc

(( new java.text.SimpleDateFormat("yyyy-MM-dd").format( new java.util.Date() ) ))

((#expr.dateFormat( new java.util.Date(), 'yyyy-MM-dd HH:mm:ssZ' ) ))

SPEL expression to evaluate to current date

Adding an attribute with a value set to current 

(( #item.attributes != null && #item.attributes.get('Complete Refreshes Counter').value != null && ? new Integer(#item.attributes.get('Complete Refreshes Counter').value) + 1 : 1 ))

Terniary Operator inside expression

(( new Integer(#item.attributes.get('Counter').value) ))

Parse String as Integer

/api/dataSet/101/column/Group or Individual/values?unique=true&Plan={{form.questions[1].answers[0].content | urlEncode}}

This is the string that can be used as a “Source” on a Form Question.

The given example returns all the unique values for the column “Group or Individual“ from dataset id 101 where the column “Plan“ = answer of the second question of the form.

Form Question Source field.

#item.attributes.get('Plan and Type').value.match('(Anthem-Out-of-Area Materials|Arkansas-Out-of-Area Materials|BCBSM & BCNA Joint-Provider Communication \(Part C\)|BCBSM & BCNA Joint-Provider Communication \(Parts B & D\))')

FORM QUESTION VISIBILTY CONDITION

This checks if the attribute “Plan and Type“ is either of those values separated by the pipe simple.

Form Question Visiblity/Required conditions

#expr.entitySearch( 'NAME|ID', '#entities' )

Search for a user or group by either the name or the ID. The result can be further filtered by applying an additional condition to the second parameter

#expr.all( list, 'condition' )

#expr.any( list, 'condition' )

#expr.none( list, 'condition' )

#expr.count( list, 'condition' )

Helper functions to check if a list of items satisfy a condition. Returns boolean

Checking if all items in a folder have the same status

or if any or none do

#expr.search( ‘searchTerm', ‘queryField' )

#expr.find( String id )

#expr.findByPath( ‘path’ )

#expr.findByPath( ‘path’, true )

Programmatic solr query search

Get an item by id

Find item by path in BR

Try to find item by path in BR and/or create item and path if non-existent 

#expr.isMemberOf('Developers')

Returns true if the current user logged in is a member of the listed groups, in this case the group “Developers”. False otherwise.

Will also return true if the user is not directly a member of Developers, but is a member of a sub-team of Developers. 

Will also return true if the user is not directly a member of Developers, but is a backup for another user who is directly a member of Developers.

BCBSM, hide Question 4 of the Request Form from users who are not Developers. 

((#item.getSystemAttributeValue('Developer')))

This is found on most reassignments in BCBSM in the “To” field. Useful to know.

((#statusCode))

((#result))

Web Service event action error details.

Web Service event actions have the option to call downstream events on Success and on Error.

In the downstream event, you can access the error message using these expressions

For event action create Attributes, the expression format needs to be

(( attribute_name))=((attribute_value))

((another attribute name))=((value))

Actual example (from BCBSM use case): 

Individual=(( #answer.display.contains('Individual') ? 'Yes' : 'No' ))
MPSERS=(( #answer.display.contains('MPSERS') ))
National Michigan Group (NMG)=(( #answer.display.contains('National Michigan Group (NMG)') ))
State of Michigan=(( #answer.display.contains('State of Michigan') ))
URMBT (UAW Trust)=(( #answer.display.contains('URMBT (UAW Trust)') ))
University of Michigan=(( #answer.display.contains('University of Michigan') ))
Group=(( #answer.display.contains('Group') ))
MyBlue Medigap=(( #answer.display.contains('MyBlue Medigap') ))
Legacy Medigap=(( #answer.display.contains('Legacy Medigap') ))
MESSA - PDP Only=(( #answer.display.contains('MESSA - PDP Only') ))

Sets 10 attributes based on a “answer question“ trigger. This question is a multiple answer type question where value can be one or more. 

New line in the value of the event action means a new attribute hence the multiple attributes in the example. 

For creating multiple attributes using one event action. 

TODO: add example expression for adding a condition to event actions.