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:

https://www.becpg.fr/hg/becpg-community/file/tip/becpg-plm/becpg-plm-core/src/main/java/fr/becpg/repo/product/data/ProductData.java

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']

results matching ""

    No results matching ""