Skip to content

Commit 067333e

Browse files
authored
Merge pull request #800 from orchestracities/expression-lib-alternative
Provide an alternative expression plugin
2 parents 56ccfdf + ae53333 commit 067333e

19 files changed

+2328
-16
lines changed

CHANGES_NEXT_RELEASE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ Add NGSIv2 metadata support to device provisioned attributes
33
Fix: Error message when sending measures with unknown/undefined attribute
44
Add Null check within executeWithSecurity() to avoid crash (#829)
55
Add NGSIv2 metadata support to attributeAlias plugin.
6+
Add support for JEXL as expression language (#801)

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ decommission devices.
100100
## Testing
101101

102102

103-
Contribitions to development can be found [here](doc/development.md) - additional contributions are welcome.
103+
Contributions to development can be found [here](doc/development.md) - additional contributions are welcome.
104104

105105
### Agent Console
106106

doc/expressionLanguage.md

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616

1717
The IoTAgent Library provides an expression language for measurement transformation, that can be used to adapt the
1818
information coming from the South Bound APIs to the information reported to the Context Broker. Expressions in this
19-
language can be configured for provisioned attributes as explained in the Device Provisioning API section in the main
20-
README.md.
19+
language can be configured for provisioned attributes as explained in the Device Provisioning API section in the
20+
main README.md.
2121

2222
## Measurement transformation
2323

@@ -301,3 +301,94 @@ two possible types of expressions: Integer (arithmetic operations) or Strings.
301301
- update (of type "Boolean"): true -> ${@update * 20} -> ${ 1 \* 20 } -> $ { 20 } -> $ { "20"} -> False
302302
- update (of type "Boolean"): false -> ${@update * 20} -> ${ 0 \* 20 } -> $ { 0 } -> $ { "0"} -> False
303303
- update (of type "Boolean"): true -> ${trim(@updated)} -> ${trim("true")} -> $ { "true" } -> $ { "true"} -> True
304+
305+
## JEXL Based Transformations
306+
307+
As an alternative, the IoTAgent Library supports as well [JEXL](https://github.com/TomFrost/jexl).
308+
To use JEXL, you will need to either configure it as default
309+
language using the `defaultExpressionLanguage` field to `jexl`
310+
(see [configuration documentation](installationguide.md)) or configuring
311+
the usage of JEXL as expression language for a given device:
312+
313+
```
314+
{
315+
"devices":[
316+
{
317+
"device_id":"45",
318+
"protocol":"GENERIC_PROTO",
319+
"entity_name":"WasteContainer:WC45",
320+
"entity_type":"WasteContainer",
321+
"expressionLanguage": "jexl",
322+
"attributes":[
323+
{
324+
"name":"location",
325+
"type":"geo:point",
326+
"expression": "..."
327+
},
328+
{
329+
"name":"fillingLevel",
330+
"type":"Number",
331+
"expression": "..."
332+
}
333+
]
334+
}
335+
]
336+
}
337+
```
338+
339+
In the following we provide examples of using JEXL to apply transformations.
340+
341+
### Quick comparison to default language
342+
343+
* JEXL supports the following types: Boolean, String, Number, Object, Array.
344+
* JEXL allows to navigate and [filter](https://github.com/TomFrost/jexl#collections) objects and arrays.
345+
* JEXL supports if..then...else... via [ternary operator](https://github.com/TomFrost/jexl#ternary-operator).
346+
* JEXL additionally supports the following operations:
347+
Divide and floor `//`, Modulus `%`, Logical AND `&&` and
348+
Logical OR `||`. Negation operator is `!`
349+
* JEXL supports [comparisons](https://github.com/TomFrost/jexl#comparisons).
350+
351+
352+
For more details, check JEXL language details
353+
[here](https://github.com/TomFrost/jexl#all-the-details).
354+
355+
### Examples of expressions
356+
357+
The following table shows expressions and their expected outcomes taking into
358+
account the following measures at southbound interface:
359+
360+
* `value` with value 6 (number)
361+
* `name` with value `"DevId629"` (string)
362+
* `object` with value `{name: "John", surname: "Doe"}` (JSON object)
363+
* `array` with value `[1, 3]` (JSON Array)
364+
365+
366+
| Expression | Expected outcome |
367+
|:--------------------------- |:----------------------- |
368+
| `5 * value` | `30` |
369+
| `(6 + value) * 3` | `36` |
370+
| `value / 12 + 1` | `1.5` |
371+
| `(5 + 2) * (value + 7)` | `91` |
372+
| `value * 5.2` | `31.2` |
373+
| `"Pruebas " + "De Strings"` | `"Pruebas De Strings"` |
374+
| `name + "value is " +value` | `"DevId629 value is 6"` |
375+
376+
Support for `trim`, `length`, `substr` and `indexOf` transformations was added.
377+
378+
| Expression | Expected outcome |
379+
|:--------------------------- |:----------------------- |
380+
| <code>" a "&vert;trim</code> | `a` |
381+
| <code>name&vert;length</code> | `8` |
382+
| <code>name&vert;indexOf("e")</code>| `1` |
383+
| <code>name&vert;substring(0,name&vert;indexOf("e")+1)</code>| `"De"` |
384+
385+
The following are some expressions not supported by the legacy expression
386+
language:
387+
388+
| Expression | Expected outcome |
389+
|:----------------------------------- |:----------------------- |
390+
| `value == 6? true : false` | `true` |
391+
| <code>value == 6 && name&vert;indexOf("e")>0</code> | `true` |
392+
| `array[1]+1` | `3` |
393+
| `object.name` | `"John"` |
394+
| `{type:"Point",coordinates: [value,value]}`| `{type:"Point",coordinates: [6,6]}` |

doc/installationguide.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,10 @@ used for the same purpose. For instance:
232232
the IoTAgent runs in a single thread. For more details about multi-core functionality, please refer to the
233233
[Cluster](https://nodejs.org/api/cluster.html) module in Node.js and
234234
[this section](howto.md#iot-agent-in-multi-thread-mode) of the library documentation.
235+
- **defaultExpressionLanguage**: the default expression language used to
236+
compute expressions, possible values are: `legacy` or `jexl`. When not set or
237+
wrongly set, `legacy` is used as default value.
238+
235239

236240
### Configuration using environment variables
237241

@@ -289,3 +293,4 @@ overrides.
289293
| IOTA_POLLING_DAEMON_FREQ | `pollingDaemonFrequency` |
290294
| IOTA_AUTOCAST | `autocast` |
291295
| IOTA_MULTI_CORE | `multiCore` |
296+
| IOTA_DEFAULT_EXPRESSION_LANGUAGE | defaultExpressionLanguage |

lib/commonConfig.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,8 @@ function processEnvironmentVariables() {
9191
'IOTA_APPEND_MODE',
9292
'IOTA_POLLING_EXPIRATION',
9393
'IOTA_POLLING_DAEMON_FREQ',
94-
'IOTA_MULTI_CORE'
94+
'IOTA_MULTI_CORE',
95+
'IOTA_DEFAULT_EXPRESSION_LANGUAGE'
9596
],
9697
iotamVariables = [
9798
'IOTA_IOTAM_URL',
@@ -355,6 +356,11 @@ function processEnvironmentVariables() {
355356
} else {
356357
config.multiCore = config.multiCore === true;
357358
}
359+
360+
if (process.env.IOTA_DEFAULT_EXPRESSION_LANGUAGE) {
361+
config.defaultExpressionLanguage = process.env.IOTA_DEFAULT_EXPRESSION_LANGUAGE;
362+
}
363+
358364
}
359365

360366
function setConfig(newConfig) {

lib/fiware-iotagent-lib.js

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,16 @@ function doActivate(newConfig, callback) {
8484
commandRegistry,
8585
securityService;
8686

87+
logger.format = logger.formatters.pipe;
88+
89+
config.setConfig(newConfig); //moving up here because otherwise env variable are not considered by the code below
90+
91+
if (!config.getConfig().dieOnUnexpectedError) {
92+
process.on('uncaughtException', globalErrorHandler);
93+
}
94+
95+
newConfig = config.getConfig();
96+
8797
if (newConfig.contextBroker) {
8898
if (! newConfig.contextBroker.url && newConfig.contextBroker.host && newConfig.contextBroker.port) {
8999
newConfig.contextBroker.url = 'http://' + newConfig.contextBroker.host + ':' + newConfig.contextBroker.port;
@@ -113,7 +123,16 @@ function doActivate(newConfig, callback) {
113123
}
114124
}
115125

116-
config.setConfig(newConfig);
126+
if (newConfig.defaultExpressionLanguage &&
127+
(newConfig.defaultExpressionLanguage === 'legacy' ||
128+
newConfig.defaultExpressionLanguage ==='jexl')){
129+
logger.info(context, 'Using ' + newConfig.defaultExpressionLanguage + ' as default expression language');
130+
} else {
131+
logger.info(context, 'Default expression language not set, or invalid, using legacy configuration');
132+
newConfig.defaultExpressionLanguage = 'legacy';
133+
}
134+
135+
config.setConfig(newConfig); //after chaging some configuration, we re apply the configuration
117136

118137
logger.info(context, 'Activating IOT Agent NGSI Library.');
119138

@@ -156,10 +175,6 @@ function doActivate(newConfig, callback) {
156175
], callback);
157176
};
158177

159-
if (!config.getConfig().dieOnUnexpectedError) {
160-
process.on('uncaughtException', globalErrorHandler);
161-
}
162-
163178
config.setSecurityService(securityService);
164179
config.setRegistry(registry);
165180
config.setGroupRegistry(groupRegistry);

lib/model/Device.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ var Device = new Schema({
4848
internalId: String,
4949
creationDate: { type: Date, default: Date.now },
5050
internalAttributes: Object,
51-
autoprovision: Boolean
51+
autoprovision: Boolean,
52+
expressionLanguage: String
5253
});
5354

5455
function load(db) {

lib/model/Group.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ var Group = new Schema({
4141
lazy: Array,
4242
attributes: Array,
4343
internalAttributes: Array,
44-
autoprovision: Boolean
44+
autoprovision: Boolean,
45+
expressionLanguage: String
4546
});
4647

4748
function load(db) {

lib/plugins/expressionPlugin.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,14 @@
2222
* please contact with::daniel.moranjimenez@telefonica.com
2323
*
2424
* Modified by: Daniel Calvo - ATOS Research & Innovation
25+
* Modified by: Federico M. Facca - Martel Innovate
2526
*/
2627

2728
'use strict';
2829

2930
var _ = require('underscore'),
30-
parser = require('./expressionParser'),
31+
legacyParser = require('./expressionParser'),
32+
jexlParser = require('./jexlParser'),
3133
config = require('../commonConfig'),
3234
/*jshint unused:false*/
3335
logger = require('logops'),
@@ -62,7 +64,27 @@ function mergeAttributes(attrList1, attrList2) {
6264

6365

6466
function update(entity, typeInformation, callback) {
67+
68+
function checkJexl(typeInformation){
69+
if (config.getConfig().defaultExpressionLanguage === 'jexl' &&
70+
typeInformation.expressionLanguage &&
71+
typeInformation.expressionLanguage !== 'legacy') {
72+
return true;
73+
} else if (config.getConfig().defaultExpressionLanguage === 'jexl' &&
74+
!typeInformation.expressionLanguage) {
75+
return true;
76+
} else if (config.getConfig().defaultExpressionLanguage === 'legacy' &&
77+
typeInformation.expressionLanguage && typeInformation.expressionLanguage === 'jexl') {
78+
return true;
79+
}
80+
return false;
81+
}
82+
6583
function processEntityUpdateNgsi1(entity) {
84+
var parser = legacyParser;
85+
if (checkJexl(typeInformation)){
86+
parser = jexlParser;
87+
}
6688
var expressionAttributes = [],
6789
ctx = parser.extractContext(entity.attributes);
6890

@@ -76,6 +98,10 @@ function update(entity, typeInformation, callback) {
7698
}
7799

78100
function processEntityUpdateNgsi2(attributes) {
101+
var parser = legacyParser;
102+
if (checkJexl(typeInformation)){
103+
parser = jexlParser;
104+
}
79105
var expressionAttributes = [],
80106
ctx = parser.extractContext(attributes);
81107

0 commit comments

Comments
 (0)