Spel Language
The Spring Expression Language (SpEL) is a powerful expression language that supports querying and manipulating an object graph at runtime. It can be used with becpg at several places
There are several operators available in the language:
Type | Operators |
---|---|
Arithmetic | +, -, *, /, %, ^, div, mod |
Relational | <, >, ==, !=, <=, >=, lt, gt, eq, ne, le, ge |
Logical | and, or, not, &&, |
Conditional | ?: |
Regex | matches |
Operators
Arithmetic Operators
All basic arithmetic operators are supported.
19 + 1 // 20
'String1 ' + 'string2' // "String1 string2"
20 - 1 // 19
10 * 2 // 20
36 / 2 // 19
36 div 2 // 18, the same as for / operator
37 % 10 // 7
37 mod 10 // 7, the same as for % operator
2 ^ 9 // 512
(2 + 2) * 2 + 9 // 17
Divide and modulo operations have alphabetic aliases, div for / and mod for %. The + operator can also be used to concatenate strings.
Relational and Logical Operators
All basic relational and logical operations are also supported.
1 == 1 // true
1 eq 1 // true
1 != 1 // false
1 ne 1 // false
1 < 1 // false
1 lt 1 // false
1 <= 1 // true
1 le 1 // true
1 > 1 // false
1 gt 1 // false
1 >= 1 // true
1 ge 1 // true
All relational operators have alphabetic aliases, as well. For example, in XML-based configs we can't use operators containing angle brackets (<, <=, >, >=). Instead, we can use lt (less than), le (less than or equal), gt (greater than), or ge (greater than or equal).
Logical Operators
SpEL supports all basic logical operations:
250 > 200 && 200 < 4000 // true
250 > 200 and 200 < 4000 // true
400 > 300 || 150 < 100 // true
400 > 300 or 150 < 100 // true
!true // false
not true // false
As with arithmetic and relational operators, all logical operators also have alphabetic clones.
Conditional Operators
Conditional operators are used for injecting different values depending on some condition:
2 > 1 ? 'a' : 'b' // "a"
The ternary operator is used for performing compact if-then-else conditional logic inside the expression. In this example we tried to check if it was true or not.
Another common use for the ternary operator is to check if some variable is null and then return the variable value or a default:
someBean.someProperty != null ? someBean.someProperty : 'default'
The Elvis operator is a way of shortening the ternary operator syntax for the case above used in the Groovy language. It is also available in SpEL. The code below is | the code above:
someBean.someProperty ?: 'default' // Will inject provided string if someProperty is null
Using Regex in SpEL
The matches operator can be used to check whether or not a string matches a given regular expression.
'100' matches 'd+' // true
'100fghdjf' matches 'd+' // false
'valid alphabetic string' matches '[a-zA-Zs]+' // true
'invalid alphabetic string #nodeRef' matches '[a-zA-Zs]+' // false
someBean.someValue matches '\d+' // true if someValue contains only digits
Variables
Variables can be referenced in the expression using the syntax #variableName. Variables are set using the var keyword
var myVar = "This is a test";
#myVar // This is a test
The #this and #root variables
The variable #this is always defined and refers to the current evaluation object (against which unqualified references are resolved). The variable #root is always defined and refers to the root context object. Although #this may vary as components of an expression are evaluated, #root always refers to the root.
Accessing beCPG objects
With the help of SpEL, we can access the contents of any Java Object in the formulation context. For example in case of product formulation we have access to ProductData object:
public class ProductData {
NodeRef hierarchy1;
NodeRef hierarchy2;
MLText legalName;
MLText pluralLegalName;
String title;
String erpCode;
SystemState state | SystemState.Simulation;
ProductUnit unit | ProductUnit.kg;
ProductData entityTpl;
List<NodeRef> plants | new ArrayList<>();
Double qty;
Double density;
Double yield;
Double manualYield;
Double secondaryYield;
Double yieldVolume;
Double netWeight;
Double weightPrimary;
Double netWeightSecondary;
Double weightSecondary;
Double netWeightTertiary;
Double weightTertiary;
Boolean dropPackagingOfComponents;
/*
* DataList
*/
List<AllergenListDataItem> allergenList;
List<CostListDataItem> costList;
List<PriceListDataItem> priceList;
List<IngListDataItem> ingList;
List<NutListDataItem> nutList;
List<OrganoListDataItem> organoList;
List<MicrobioListDataItem> microbioList;
List<PhysicoChemListDataItem> physicoChemList;
List<LabelClaimListDataItem> labelClaimList;
List<ControlDefListDataItem> controlDefList;
List<LabelingListDataItem> labelingList;
List<ResourceParamListItem> resourceParamList;
List<ReqCtrlListDataItem> reqCtrlList;
List<PackMaterialListDataItem> packMaterialList;
/*
* View
*/
CompoListView compoListView | new CompoListView();
ProcessListView processListView | new ProcessListView();
PackagingListView packagingListView | new PackagingListView();
LabelingListView labelingListView | new LabelingListView();
/*
* Variants
*/
List<VariantData> localVariants;
VariantPackagingData defaultVariantPackagingData;
VariantData defaultVariantData;
/*
* Product specifications
*/
List<ProductSpecificationData> productSpecifications;
List<ClientData> clients;
/*
* Origin geo
*/
List<NodeRef> geoOrigins | new ArrayList<>();
/*
* Completion scores
*/
String entityScore;
List<String> reportLocales;
List<ProductData> compareWithEntities;
}
To access members variable in SpEL just indicate the variable name:
title // Sample
erpCode = "0001" // 0001
clients[0] // nodeRef
compoListView.compoList[0] // CompoListDataItem
nutList.?[nut.toString() == 'nodeRef'].value // Nut list value
You can also call any method available in the ProductData object:
isLiquid()
isRawMaterial()
isPackaging()
isPackagingKit()
isSemiFinished()
isLocalSemiFinished()
Collection Selection
Selection is a powerful expression language feature that allows you to transform some source collection into another by selecting from its entries.
Selection uses the syntax .?[selectionExpression]. This will filter the collection and return a new collection containing a subset of the original elements. For example, selection would allow us to easily get a list of CostListDataIteltem :
costList.?[cost == 'Cost MP'] // Array of costs where cost nodeRef = 'Cost MP'
costList.?[value<27] // Array of costs where value < 27
In addition to returning all the selected elements, it is possible to retrieve just the first or the last value. To obtain the first entry matching the selection the syntax is ^[…]
whilst to obtain the last matching selection the syntax is $[…]
.
costList.^[cost == 'Cost MP'] // First cost matching
costList.$[cost == 'Cost MP'] // Last cost matching
Collection Projection
Projection allows a collection to drive the evaluation of a sub-expression and the result is a new collection. The syntax for projection is ![projectionExpression]. Most easily understood by example, suppose we have a list of costs but want the list of cost value. Effectively we want to evaluate 'value' for every entry in the cost list. Using projection:
costList.![value] // returns [ '5', '25' ]
Advanced
Accessing java type
The special 'T' operator can be used to specify an instance of java.lang.Class (the 'type'). Static methods are invoked using this operator as well.
T(java.util.Date)
T(String)
T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR
Constructors
Constructors can be invoked using the new operator. The fully qualified class name should be used for all but the primitive type and String (where int, float, etc, can be used).
Expression templating
Essentially used in labeling Render formula. Expression templates allow a mixing of literal text with one or more evaluation blocks. Each evaluation block is delimited with prefix and suffix characters that you can define, a common choice is to use #{ }
as the delimiters. For example,
"random number is #{T(java.lang.Math).random()}"
Inline lists, maps and array construction
Lists can be expressed directly in an expression using {}
notation. Maps can also be expressed directly in an expression using {key:value}
notation. Arrays can be built using the familiar Java syntax, optionally supplying an initializer to have the array populated at construction time.
"{1,2,3,4}" // List construction
"{name:'Nikola',dob:'10-July-1856'}" // Map construction
"new int[]{1,2,3}" // Array construction
Understanding Spel context in beCPG
As seen previously in SPEL formulation formula you have access to entities being formulated (ProductData for product formulation). There exists several contexts where SPEL formulas are being used.
Formulation
Formulation is the main context where SPEL formula is used, it's important to understand what is the formulation chain and when formulas are triggered. The formulation chain's main goal is to load an entity from alfresco models and apply several transformations on it at the end the entity object is saved.
There are several formulation chains, one for a project and one for a product for example. On the product formulation chain we load an inherited ProductData JAVA object (RawMaterialData/ SemiFinishedProductData/ FinishedProductData), then we apply the corresponding formulation chain. At the end the ProductData is saved.
Important at the end of the formulation the engine can decide to save the ProductData only in memory (it's the case for OM simulation for example). You should take care of not saving data during the process it's why it's important to only manipulate the productData during SPEL formula.
Pre/post formulation context:
Context variable | Description |
---|---|
#root | entity being formulated |
access to all beCPG spel custom function
Pre/post composition/packaging/process column formulation context:
Context variable | Description |
---|---|
entity | entity being formulated |
dataListItem | item row being formulated (compoList/packagingList/processList) |
dataListItemEntity | entity associated with the list (compoListProduct/packagingListProduct/ resourceProduct) |
access to all beCPG spel custom function
Cost/Nut/Physico/Claim formulation context
Context variable | Description |
---|---|
entity | entity being formulated |
dataListItem | item row being formulated (compoList/packagingList/processList) |
access to all beCPG spel custom function
Labeling context
Prefs or Render rules:
Context variable | Description |
---|---|
#root | LabelingFormulaContext JAVA object |
entity | entity being formulated |
Labeling filter rules:
Context variable | Description |
---|---|
#root | LabelingFormulaFilterContext JAVA object |
compoListDataItem | CompoListItem JAVA object to test filter on |
ingListDataItem | IngListItem JAVA object to test filter on |
ingTypeItem | IngTypeItem JAVA object to test filter on |
entity | entity being formulated |
dataListItem | Alias for compoListDataItem |
isClaimed(claimNodeRef) | Check for claim on ingredient or product |
access to all beCPG spel custom function
Catalog Spel context
Same context than formulation
JXLS report context
Same context than formulation
Export search context
Context variable | Description |
---|---|
#root | DefaultExcelReportSearchPlugin.FormulaContext JAVA object |
prop | Array of properties extracted, ie props["bcpg:code"] |
No access to entity neither custom spel fonction
Connector Spel context
Context variable | Description |
---|---|
#root | StandardCSVVisitor.FormulaContext JAVA object |
prop | Array of properties extracted, ie props["bcpg:code"] |
No access to entity neither custom spel fonction
beCPG Spel custom functions
beCPG defines a set of customs functions accessible from the SPEL formula.
Syntax to use this functions is @beCPG.function
Functions | Description | Example |
---|---|---|
propValue | Displays a property. | @beCPG.propValue(#this, ""cm:name"") |
setValue | Set a value to a property. | @beCPG.setValue(#this, "bcpg:productUnit", "kg") |
assocValue | Displays the nodeRef value of an association | @beCPG.assocValue(#this, "bcpg:trademarkRef") |
assocValues | Displays the values of a multiple associations in table. | @beCPG.assocValues(#this, "bcpg:productGeoOrigin") |
setAssocs | Set one or several assoc values. | var clients = @beCPG.assocValues(#this, "bcpg:clients") ; #clients.add("workspace://SpacesStore/844aa160-f344-43be-a659-434d319e584f"); @beCPG.setAssocs(#this ,"bcpg:clients", #clients) |
assocPropValues | Displays properties from the associations extracted. | @beCPG.assocPropValues(#this, "bcpg:client", "cm:title") |
assocAssocValues | Displays associations from the associations extracted. | @beCPG.assocAssocValues(#this, "bcpg:plant", "bcpg:plantCertifications") |
propMLValue | Extract a multilingual property in the specified locale. | @beCPG.propMLValue(#this, "bcpg:legalName" ,"en_US") |
updateMLText | Set a locale value to a multilingual field. | @beCPG.updateMLText(legalName,"fi","Finish Legal Name") |
copy | Copy associations and/or lists from a source to the current product. | @beCPG.copy(compoList[0].product, {"bcpg:trademarkRef","bcpg:subsidiaryRef}, {"bcpg:compoList"}) |
applyFormulaToList | Applicate a formula to a list, like a dynamic column. | @beCPG.applyFormulaToList(getCompoList(), "@beCPG.setValue(dataListItem, 'bcpg:compoListQtySubFormula', 0d)") |
filter | Use to filter. Is generally combined with other formulas, like min, max, avg or sum. | @beCPG.filter(getCompoList(new fr.becpg.repo.variant.filters.VariantFilters(true)), "dataListItem.qty > 0.5") |
max | Use to find the maximum value of an array. | @beCPG.max(compoList.![qty]) |
min | Use to find the minimim value of an array. | @beCPG.min(@beCPG.filter(reqCtrlList, "reqMaxQty!=null && reqType.toString() !='Info'").![reqMaxQty]) |
avg | Use to find the average value of an array. | @beCPG.avg(compoList.![qty]) |
sum | Use to sum the values of an array. | @beCPG.sum(getCompoList(new fr.becpg.repo.variant.filters.VariantFilters(true)), dataListItem.qty) |
children | Used to get the children of entity , used to go down in the composition levels. |
@beCPG.children(entity) |
findOne | Used to get the entity designed with the nodeRef |
@beCPG.findOne(nodeRef) |
sourcesAssocValues | Displays the where-used nodeRef array, based on a target association. | @beCPG.sourcesAssocValues(#this,"pjt:projectEntity") |
beCPG Alias
Some aliases are provided to simplify Collection Selection on common datalists.
Alias | Expression |
---|---|
cost['nodeRef'] | costList.^[cost.toString() == 'nodeRef'] |
nut['nodeRef'] | nutList.^[nut.toString() == 'nodeRef'] |
allergen['nodeRef'] | allergenList.^[allergen.toString() == 'nodeRef'] |
ing['nodeRef'] | ingList.^[ing.toString() == 'nodeRef'] |
organo['nodeRef'] | organoList.^[organo.toString() == 'nodeRef'] |
physico['nodeRef'] | physicoChemList.^[physicoChem.toString() == 'nodeRef'] |
microbio['nodeRef'] | microbioList.^[microBio.toString() == 'nodeRef'] |
compo['nodeRef'] | compoListView.compoList.^[product.toString() == 'nodeRef'] |
process['nodeRef'] | processListView.processList.^[resource.toString() == 'nodeRef'] |
resParam['nodeRef'] | resourceParamList.^[param.toString() == 'nodeRef'] |
pack['nodeRef'] | packagingListView.packagingList.^[product.toString() == 'nodeRef'] |
packaging['nodeRef'] | packagingListView.packagingList.^[product.toString() == 'nodeRef'] |
compoVar['name'] | compoListView.dynamicCharactList.^[title == 'name']?.value |
packVar['name'] | packagingListView.dynamicCharactList.^[title == 'name']?.value |
packagingVar['name'] | packagingListView.dynamicCharactList.^[title == 'name']?.value |
processVar['name'] | processListView.dynamicCharactList.^[title == 'nodeRef']?.value |
labelClaim['nodeRef'] | labelClaimList.^[labelClaim.toString() == 'nodeRef'] |
labeling['nodeRef'] | labelingListView.ingLabelingList.^[grp.toString() == 'nodeRef'] |