Variables and Scopes
Variables
A variable is used to "save" or "reference" a value. The value may either be a simple value like a number, a text or a date, or it may be a complex object like a person, a contract or an order. Variables can be used in Expressions and Scripts.
See the documentation on Script Language Introduction for further information.
Variable Names
A variable is identified by its name. A variable's name must conform to the following rules:
- The name must start with a character (upper or lower case) or an underscore
- The name must only contain characters, digits and underscores
Valid variable names include:
accountpersonTypeinvoice01_tempText
Examples of invalid variable names:
007headeraccount.numberorder-idUser Name
If a variable is used within an Expression or a Script, the variable's name has to be prefixed with a dollar sign, "$": $order_id := 123456
A variable's name should be chosen to reflect whether it contains a single value or a collection of values. Variable names for collections should use the plural form: "persons", "orders" or "contracts." Variables for single objects should use the singular form: "person", "order" or "contract." While this is not enforced by the Expression Language, it is good practice as it makes your Expressions and Scripts more readable.
Variable Types
Every variable must have a declared type. A type declaration always consists of a base type declaration and a collection type declaration. The base type defines what kind of values the variable can save.
- A variable of type "Integer" may contain only integer numbers (such as "-12", "0", "2", "17290")
- A variable of type "Date" may contain only valid dates
- A variable of a complex type, such as "Person", may contain only references to objects of the given type
There are some built-in simple types like "String", "Integer", "Date" and "Boolean". Complex types can be defined by creating Data Classes.
Type information is used by the system in many situations:
- The Expression Editor uses this information to present code completion suggestions. If the variable
$personis of type "Person" and the Data Class "Person" defines properties like "firstName" and "lastName", the Expression Editor will show these properties as soon as the user enters a period after the variable name. - The Business Object Repository uses this information to find dependencies from arbitrary Business Objects to Data Classes.
- The Screen Service may perform an automatic validation of the data entered by the user.
Collections
Variables may also be used to save collections of values. While the aforementioned base type denotes the type of objects contained in the variable, the collection type denotes if the variable can contain only one instance of this base type, or if it can store many instances of the base type.
There are two different kind of collections: a "Collection with Indexed Entries" and a "Collection with Named Entries."
If you are familiar with other programming languages, a "Collection with Indexed Entries" is what is usually called an "array", a "list", or a "vector" in other languages.
A "Collection with Named Entries" is what is often called a "map", "hash", "hash table", or "associative array".
Collections may also be empty. This means that they do not contain any instances at all.
Variable Scopes
A scope is a collection of variables. For every variable, the scope contains the variable's name, the variable's type and the value currently assigned to the variable. Variable names must be unique within a scope. A scope can have a "parent scope." This chapter explains how Scopes are used by the Expression Language. To find out when they are created, refer to the section, Variable Definition and Assignment.
Whenever an expression is evaluated, a scope is selected. This scope is called the current scope. If the expression contains a variable, the expression service checks whether the current scope contains this variable. If this is the case, the variable value will be used in the expression. If the current scope doesn't contain this variable, the current scope's parent scope is checked (if any). This continues until either the variable is found, or a scope is reached that doesn't have a parent scope.
If the variable is not found, an error occurs in the expression evaluation.
In the figure above, there are three scopes numbered from 1 to 3.
- Scope 1 is the parent scope of Scope 2
- Scope 2 is the parent scope of Scope 3
- Scope 3 is the currently-active scope
Let's say that the following script should get evaluated:
1: Script
2: If $income Then
3: $balance := $balance + $amount;
4: Else
5: $balance := $balance - $amount;
6: End
7: End
The script references three variables: $income, $balance and $amount. Depending on whether $income is true or false, $amount should get added to or subtracted from $balance.
Assume that the script execution has reached line 3. The execution engine needs to find values for the variables $balance and $amount and add them together. Searching for the variables' values starts in Scope 3. Neither $balance nor $amount is defined in Scope 3. Therefore the search continues in the parent scope: Scope 2.
In Scope 2, $amount is found and has a value of 250. But $balance is still not found, therefore search for $balance continues in Scope 1, where it is finally found with the value 15000. The two numbers get added and the result has to be saved in the variable $balance. Again, the execution engine has to find the variable $balance to save the result. Search starts at Scope 3 again and continues to Scope 2 and finally to Scope 1, where the variable is defined. There, the variable's current value of 15000 will be replaced with the result of the addition, that is, with 15250.
What would happen if Scope 2 also contained a variable, $balance? Well, then the variable value from Scope 2 would be used. The variable $balance in scope 2 is said to "shadow" the variable in Scope 1. If one of the scopes 2 or 3 is the active scope, there is no way to access the variable $balance in Scope 1. In other words: the variable "nearest" to the active scope is used.
However, if the script is executed with Scope 1 as the current active scope, the variable $income would not get found and the evaluation of the script would stop at line 2 with an error.
Variable Definition and Assignment
We have now seen how the Expression Language looks through scopes to find where a variable is defined. But where do scopes come from?
Variable Definition
Some Business Objects, namely Process, Sub-Process, Screen and PDF Output have so-called Variable Definitions. Variable Definitions are a list of variable declarations (name and type) together with a default initialization expression for the variable. All variables used within one of these Business Objects have to be declared in the object's Variable Definition section. These Variable Definitions are used to initialize scopes.
An example:
When a new Process Instance is started, a new scope is generated for this Process Instance. The scope is initialized using the Process' Variable Definitions. This scope has no parent scope, it is what we call the root scope. When the Process contains a Sub-Process, a new Sub-Process Instance is started. The Sub-Process has its own set of variables declared in its Variable Definition section. Therefore, a new scope is generated for the Sub-Process Instance. The scope is initialized using the Sub-Process Variable Definitions. The scope of the Process Instance is set to be the parent scope of the Sub-Process Instance's scope.
If an Expression within the Sub-Process has to be executed, the Sub-Process Instance's scope is used as the active scope. If an Expression within the Process has to be executed, the Process Instance's scope is used as the active scope. Therefore, Expressions and Scripts executed in the Sub-Process may also use variables defined in the Process, but not viceversa.
Now, if the Sub-Process contains a Screen Task, a new scope is generated for this Screen. The scope is initialized using the Screen's Variable Definitions. The Sub-Process Instance's scope is set to be the parent to this Screen's scope.
In the example in the previous chapter, Scope 1 could be the scope of the Process Instance, while Scope 2 is the scope of the Sub-Process Instance and Scope 3 the one for the Screen.
A single Variable Definition contains the following information:
- Variable name
- Variable base type and collection type
- Variable visibility (global or local)
- Default Expression
More on Variable Definitions and how they are used is covered later in this document.
Variable Assignment
Variable Assignments are closely related to Variable Definitions. A Variable Assignment can be used to assign values to variables declared in a Variable Definition.
If you are familiar with a programming language, you can think of a Business Object as a function. The Variable Definitions are the parameter definitions of this function. The variable assignments would then be the arguments passed to this function when the function has to be executed.
An example:
A Screen is used to display details about a person. Therefore, the Screen defines the variables $lastName and $firstName, both of type String. The Screen is used twice in a Sub-Process, once to show information about the account holder of a bank account and a second time to show information about the account manager. In the Sub-Process, the first name and last name of the account holder are saved in the variables $holderFirst and $holderLast, while the account manager's name is saved in the variables $managerFirst and $managerLast.
Figure 3: Variable Assignment
Scope 2 corresponds to the Sub-Process Instance's scope while Scope 3 corresponds to the included Screen's scope. There are two Label Components on the Screen, one bound to the variable $firstName and the other bound to the variable $lastName (Label Components are used to display simple text on a Screen).
How does the Screen 'know' what names it has to show? The Screen can't know it by itself; it has to 'be told' at runtime. The idea is to assign values to the Screen's variables $firstName and $lastName the moment the Screen's scope is initialized. This is done using Variable Assignments.
Variable Assignments do not get stored within the Screen, but at the location where the Screen is used. In the example above, the Screen is used twice in a Sub-Process. The Sub-Process contains two Screen Tasks referring this Screen. The Variable Assignments are stored within the Screen Task properties and therefore get saved in the Sub-Process Model.
The first Screen Task in the Sub-Process has the following Variable Assignments:
$firstName := $holderFirst
$lastName := $holderLast
The second Screen Task has the following Variable Assignments:
$firstName := $managerFirst
$lastName := $managerLast
If one of these Screen Tasks is executed, the Variable Assignments in the Screen Task have to be "merged" with the Variable Definitions of the Screen in order to initialize the Screen's scope.
But Variable Assignments are not restricted to map from one variable to an other. Instead, complex expressions could be used to define the value to be assigned to the variable.
A single Variable Assignment contains the following information:
- Variable name
- Value expression
Here's another example, showing how a single Screen is used in two different Sub-Processes using different Variable Assignments.
Figure 4: Variable Assignment Example: Sub-Process 1 using Screen A
Screen A has three Variable Definitions: two local definitions and one global definition. Sub-Process 1 uses Screen A. It has to set the values of Screen A's two local variables using Variable Assignments. The global variable will inherit Sub-Process 1's value automatically.
Another Sub-Process can use Screen A, too. This time, the two local variable of Screen A are set two different values:
Figure 5: Variable Assignment Example: Sub-Process 2 using the same Screen A
In short, Variable Definitions specify which variables can be set and which are inherited when a Business Object is used, and Variable Assignments set the values of these variables using Expressions.
Scope Initialization
The procedure how Variable Assignments and Variable Definitions are used to initialize a scope is always the same, no matter what types of Business Objects are involved: a Process including a Sub-Process, a Sub-Process including a Screen, a Screen including another Screen (either as an Include or as a Template), etc.
First of all, a new empty scope is created and added to the end of the "scope chain." The new scope becomes the current active scope, and the scope being previously active becomes the parent scope of the new scope. This is called "push a scope on the stack." The opposite operation, the deletion of the scope and making its parent the currently active scope is called "pop a scope from the stack."
Figure 6: Pushing and Popping Scopes
The Variable Definitions are processed one after the other. The next steps depend on the variable's visibility.
Global Variables
Variable Definition for a global variable is usually only an informal declaration. It is used by the system to perform background activities like dependency analysis or code completion in the expression editor. A Variable Definition for a global variable states that "there should already be a variable with this name and type defined in one of the parent scopes." By default, the variable is therefore not added to the new scope.
If, however, the variable doesn't exist in any of the parent scopes, the variable is added to the new scope, but a warning is generated in the log files that a global variable hasn't been found as expected. In this case, the default expression supplied with the Variable Definition is used to determine an initial value for the variable in the scope. If the Variable Definition doesn't contain a default expression, the variable's value is initialized to null.
Local Variables
Local variables always get an entry in the new scope. The next step is to check if there is a Variable Assignment for this local variable. If there is a Variable Assignment, its value expression is evaluated and the result is used as value for the new variable in the scope. If there is no Variable Assignment, the default expression in the Variable Definition is used instead. If there is no default expression either, the variable's value is initialized to null.
The order of the Variable Definitions is important. Variables get initialized in the scope in the order they appear in the list of Variable Definitions. And because value expressions and default expressions get evaluated with the scope just getting initialized as the currently active scope, these expressions may depend on variables declared by earlier Variable Definitions in the same list.
You can change the order of Variable Definitions in Business Object editors by dragging and dropping them.
An example: There are three Variable Definitions for variables named $a, $b and $c. There are Variable Assignments for $a and $b, therefore these two variables are initialized with the results of the value expressions in their Variable Assignments. There is no Variable Assignment for $c, therefore the Default Expression of its Variable Definition is used. In this Default Expression, the variables $a and $b may also be used (because they have already been defined). If the Variable Definition for $c would be before the other two Variable Definitions, execution of the default expression of $c would result in a "variable not found" error.
Default Expression
Although already explained above, I would like to repeat the rules about the usage of Default Expressions once again. Default Expressions are used in two cases:
- If a global variable doesn't already exist in one of the parent scopes
- If there is no Variable Assignment for a local variable
Type Conflicts
A type conflict exists in two situations:
- If there is a Variable Definition for a global variable and the global variable exists in a parent scope, the type information within the Variable Definition is compared to the type information stored in the global variable's scope. There is probably something wrong with one of the two variables if they are named the same, but their types do not match.
Example: There is a Variable Definition for a global variable
$amountof type "Float". The variable can be found in a parent scope. But there, the variable is defined as type "Integer." Although everything might work as expected at runtime because many functions can gracefully convert Integers to Floats, errors could occur due to this conflict. - If a variable's value gets evaluated based on a value expression (from a Variable Assignment) or based on a default expression (from a Variable Definition), the result of this evaluation is inspected. If the result's type doesn't match the type declared in the Variable Definition, this is probably an error as well.
Example: There is Variable Definition for a local variable
$dateOfBirthof type "Date". There is also a Variable Assignment for this variable. The value expression of the Variable Assignment is evaluated and the result is a value of type "Integer."
Currently, type conflicts to not abort scope initialization. Instead, an warning message is added to the log files. It's still a good practice to fix these errors since they might cause non-obvious issues if not resolved.
Unused Variable Assignments
A Variable Assignment is ignored if
- There is no Variable Definition for the variable denoted by the Variable Assignment
- It refers to a variable for which the Variable Definition states that it is a global variable. You can't use Variable Assignments to assign a value to a global variable.
In other words: Variable Assignments can only be used to assign values to local variables for which a Variable Definition exists.
Unused Variable Assignments also are recorded in the log files and displayed in the editors.
Variable Assignment Overview
The following flowchart shows where variables get their values for all common cases:
Figure 7 : Where Variables get their Values
Scopes in "real life"
The examples in this document have used a simplified view of scope structures. There are two important differences between the examples in this document and "real life." First of all, scopes usually do not build a "chain," but a "tree." If there are parallel activities within a Process Instance, each activity gets its own scope, because each activity potentially has different local variables. The scope of the Process Instance is the parent scope of the activity scopes. The second main difference is that there are many more scopes.
A real life example could look like this:
Figure 8 : Snapshot of a scope tree in real life
The root scope is usually the scope of a Process Instance created when a new instance of a Process is created. Below this root scope, there are scopes for Process Tokens. There can be multiple child scopes of the root scope because there can be multiple active Process Tokens at a time (perhaps assigned to different users). And because a Process may reference other Processes as subworkflows, there may be multiple levels of Process Token scopes.
A Sub-Process Task is one special kind of Process Task. When a Sub-Process Task is executed, a Sub-Process Instance is created and a new scope is initialized for this Sub-Process Instance. Below the Sub-Process Instance's scope, there may be multiple Sub-Process Session scopes and Sub-Process Token scopes. Again, a Sub-ProcessInstance may include multiple parallel Sub-Process Sessions, and one Sub-Process Session may contain multiple (stacked) Sub-Process Tokens.
The Screen Data scope is the last "persistent" scope in the scope tree. All other scopes get created and destroyed at runtime when a Screen is processed. In addition to these runtime scopes, there may be additional scopes generated when an expression or a script is executed (to prevent a "pollution" of the currently active scope).