Skip to content

Commit f223691

Browse files
committed
add support for jexl as additional expression lanaguage
* JEXL (https://github.com/TomFrost/jexl) a library that provides a reich javascript expression language. * update docs * add tests for ngsi & ngsi v2
1 parent 1eb4630 commit f223691

File tree

8 files changed

+1427
-3
lines changed

8 files changed

+1427
-3
lines changed

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -485,15 +485,19 @@ attributes:
485485

486486
- **expression**: indicates that the value of the target attribute will not be
487487
the plain value or the measurement, but an expression based on a combination
488-
of the reported values. See the
489-
[Expression Language definition](doc/expressionLanguage.md) for details
488+
of the reported values. IoT Agent Lib supports two expression languages:
489+
i) the first one is a native expression language; ii) the second one is
490+
an wrapper around [JEXL](https://github.com/TomFrost/jexl). For the details
491+
on the native see: [Expression Language definition](doc/expressionLanguage.md).
492+
For details on JEXL see: [Javascript Expression Language](https://github.com/TomFrost/jexl#all-the-details).
490493
- **entity_name**: the presence of this attribute indicates that the value
491494
will not be stored in the original device entity but in a new entity with an
492495
ID given by this attribute. The type of this additional entity can be
493496
configured with the `entity_type` attribute. If no type is configured, the
494497
device entity type is used instead. Entity names can be defined as
495498
expressions, using the
496-
[Expression Language definition](doc/expressionLanguage.md).
499+
[Expression Language definition](doc/expressionLanguage.md) or
500+
[Javascript Expression Language](https://github.com/TomFrost/jexl#all-the-details).
497501
- **entity_type**: configures the type of an alternative entity.
498502
- **reverse**: add bidirectionality expressions to the attribute. See the
499503
**bidirectionality** transformation plugin in the

doc/expressionLanguage.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,16 @@ The IoTAgent Library provides an expression language for measurement transformat
1717
information coming from the South Bound APIs to the information reported to the Context Broker. Expressions in this
1818
language can be configured for provisioned attributes as explained in the Device Provisioning API section in the
1919
main README.md.
20+
As an alternative, the IoTAgent Library supports as well [JEXL](https://github.com/TomFrost/jexl).
21+
In this section we provide only details for the native IoTAgent Library
22+
expression language. To use JEXL, you will need to replace the native language
23+
plugin with the JEXL one, e.g.:
24+
25+
```js
26+
iotAgentLib.addUpdateMiddleware(iotAgentLib.dataPlugins.mozjexlTransformation.update);
27+
```
28+
29+
JEXL language details are discussed [here](https://github.com/TomFrost/jexl#all-the-details).
2030

2131
## Measurement transformation
2232

lib/fiware-iotagent-lib.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,7 @@ exports.dataPlugins = {
295295
addEvents: require('./plugins/addEvent'),
296296
timestampProcess: require('./plugins/timestampProcessPlugin'),
297297
expressionTransformation: require('./plugins/expressionPlugin'),
298+
jexlTransformation: require('./plugins/jexlPlugin'),
298299
multiEntity: require('./plugins/multiEntity'),
299300
bidirectionalData: require('./plugins/bidirectionalData')
300301
};

lib/plugins/jexlParser.js

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
/*
2+
* Copyright 2016 Telefonica Investigación y Desarrollo, S.A.U
3+
*
4+
* This file is part of fiware-iotagent-lib
5+
*
6+
* fiware-iotagent-lib is free software: you can redistribute it and/or
7+
* modify it under the terms of the GNU Affero General Public License as
8+
* published by the Free Software Foundation, either version 3 of the License,
9+
* or (at your option) any later version.
10+
*
11+
* fiware-iotagent-lib is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14+
* See the GNU Affero General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Affero General Public
17+
* License along with fiware-iotagent-lib.
18+
* If not, seehttp://www.gnu.org/licenses/.
19+
*
20+
* For those usages not covered by the GNU Affero General Public License
21+
* please contact with::[email protected]
22+
*
23+
* Modified by: Daniel Calvo - ATOS Research & Innovation
24+
*/
25+
26+
'use strict';
27+
28+
const jexl = require('jexl');
29+
var errors = require('../errors'),
30+
config = require('../commonConfig');
31+
32+
33+
jexl.addTransform('indexOf', (val, char) => String(val).indexOf(char));
34+
jexl.addTransform('length', (val) => String(val).length);
35+
jexl.addTransform('trim', (val) => String(val).trim());
36+
jexl.addTransform('substr', (val, int1, int2) => String(val).substr(int1, int2));
37+
38+
function extractContext(attributeList) {
39+
var context = {};
40+
var value;
41+
42+
for (var i = 0; i < attributeList.length; i++) {
43+
if (Number.parseInt(attributeList[i].value)) {
44+
value = Number.parseInt(attributeList[i].value);
45+
} else if (Number.parseFloat(attributeList[i].value)) {
46+
value = Number.parseFloat(attributeList[i].value);
47+
} else if (String(attributeList[i].value) === 'true') {
48+
value = true;
49+
} else if (String(attributeList[i].value) === 'false') {
50+
value = false;
51+
} else {
52+
value = attributeList[i].value;
53+
}
54+
context[attributeList[i].name] = value;
55+
}
56+
57+
return context;
58+
}
59+
60+
function applyExpression(expression, context, typeInformation) {
61+
return jexl.evalSync (expression, context);
62+
}
63+
64+
function expressionApplier(context, typeInformation) {
65+
return function(attribute) {
66+
67+
/**
68+
* Determines if a value is of type float
69+
*
70+
* @param {String} value Value to be analyzed
71+
* @return {boolean} True if float, False otherwise.
72+
*/
73+
function isFloat(value) {
74+
return !isNaN(value) && value.toString().indexOf('.') !== -1;
75+
}
76+
77+
var newAttribute = {
78+
name: attribute.name,
79+
type: attribute.type
80+
};
81+
82+
/*jshint camelcase: false */
83+
if (config.checkNgsi2() && attribute.object_id) {
84+
newAttribute.object_id = attribute.object_id;
85+
}
86+
87+
newAttribute.value = applyExpression(attribute.expression, context, typeInformation);
88+
89+
if (attribute.type === 'Number' && isFloat(newAttribute.value)) {
90+
newAttribute.value = Number.parseFloat(newAttribute.value);
91+
}
92+
else if (attribute.type === 'Number' && Number.parseInt(newAttribute.value)) {
93+
newAttribute.value = Number.parseInt(newAttribute.value);
94+
}
95+
else if (attribute.type === 'Boolean') {
96+
newAttribute.value = (newAttribute.value === 'true' || newAttribute.value === '1');
97+
}
98+
else if (attribute.type === 'None') {
99+
newAttribute.value = null;
100+
} else {
101+
newAttribute.value = String(newAttribute.value);
102+
}
103+
104+
return newAttribute;
105+
};
106+
}
107+
108+
function isTransform(identifier) {
109+
return jexl.getTransform(identifier) === ( null || undefined) ? false : true;
110+
}
111+
112+
function contextAvailable(expression, context) {
113+
var error;
114+
try{
115+
var lexer = jexl._getLexer();
116+
var identifiers = lexer.tokenize(expression).filter(function(token) {
117+
return token.type === 'identifier';
118+
});
119+
var keys = Object.keys(context);
120+
var validContext = true;
121+
identifiers.some(function(element) {
122+
if (!keys.includes(element.value) && !isTransform(element.value)) {
123+
validContext = false;
124+
}
125+
return validContext === false;
126+
});
127+
if(validContext) {
128+
jexl.evalSync (expression, context);
129+
}
130+
return validContext;
131+
} catch (e) {
132+
error = new errors.InvalidExpression(expression);
133+
throw error;
134+
}
135+
}
136+
137+
function processExpressionAttributes(typeInformation, list, context) {
138+
return list
139+
.filter(function(item) {
140+
return item.expression && contextAvailable(item.expression, context);
141+
})
142+
.map(expressionApplier(context, typeInformation));
143+
}
144+
145+
exports.extractContext = extractContext;
146+
exports.processExpressionAttributes = processExpressionAttributes;
147+
exports.applyExpression = applyExpression;

lib/plugins/jexlPlugin.js

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
2+
/*
3+
* Copyright 2016 Telefonica Investigación y Desarrollo, S.A.U
4+
*
5+
* This file is part of fiware-iotagent-lib
6+
*
7+
* fiware-iotagent-lib is free software: you can redistribute it and/or
8+
* modify it under the terms of the GNU Affero General Public License as
9+
* published by the Free Software Foundation, either version 3 of the License,
10+
* or (at your option) any later version.
11+
*
12+
* fiware-iotagent-lib is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
15+
* See the GNU Affero General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU Affero General Public
18+
* License along with fiware-iotagent-lib.
19+
* If not, seehttp://www.gnu.org/licenses/.
20+
*
21+
* For those usages not covered by the GNU Affero General Public License
22+
* please contact with::[email protected]
23+
*
24+
* Created by: Federico M. Facca @ Martel Innovate
25+
*/
26+
27+
'use strict';
28+
29+
var _ = require('underscore'),
30+
parser = require('./jexlParser'),
31+
config = require('../commonConfig'),
32+
/*jshint unused:false*/
33+
logger = require('logops'),
34+
/*jshint unused:false*/
35+
context = {
36+
op: 'IoTAgentNGSI.mozjexPlugin'
37+
},
38+
utils = require('./pluginUtils');
39+
40+
function mergeAttributes(attrList1, attrList2) {
41+
var finalCollection = _.clone(attrList1),
42+
additionalItems = [],
43+
found;
44+
45+
for (var i = 0; i < attrList2.length; i++) {
46+
found = false;
47+
48+
for (var j = 0; j < finalCollection.length; j++) {
49+
if (finalCollection[j].name === attrList2[i].name) {
50+
finalCollection[j].value = attrList2[i].value;
51+
found = true;
52+
}
53+
}
54+
55+
if (!found) {
56+
additionalItems.push(attrList2[i]);
57+
}
58+
}
59+
60+
return finalCollection.concat(additionalItems);
61+
}
62+
63+
64+
function update(entity, typeInformation, callback) {
65+
66+
function processEntityUpdateNgsi1(entity) {
67+
var expressionAttributes = [],
68+
ctx = parser.extractContext(entity.attributes);
69+
70+
if (typeInformation.active) {
71+
expressionAttributes = parser.processExpressionAttributes(typeInformation, typeInformation.active, ctx);
72+
}
73+
74+
entity.attributes = mergeAttributes(entity.attributes, expressionAttributes);
75+
76+
return entity;
77+
}
78+
79+
function processEntityUpdateNgsi2(attributes) {
80+
var expressionAttributes = [],
81+
ctx = parser.extractContext(attributes);
82+
83+
if (typeInformation.active) {
84+
expressionAttributes = parser.processExpressionAttributes(typeInformation, typeInformation.active, ctx);
85+
}
86+
87+
attributes = mergeAttributes(attributes, expressionAttributes);
88+
return attributes;
89+
}
90+
91+
try {
92+
if (config.checkNgsi2()) {
93+
var attsArray = utils.extractAttributesArrayFromNgsi2Entity(entity);
94+
attsArray = processEntityUpdateNgsi2(attsArray);
95+
entity = utils.createNgsi2Entity(entity.id, entity.type, attsArray, true);
96+
} else {
97+
entity.contextElements = entity.contextElements.map(processEntityUpdateNgsi1);
98+
}
99+
100+
callback(null, entity, typeInformation);
101+
} catch (e) {
102+
callback(e);
103+
}
104+
}
105+
106+
exports.update = update;

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"body-parser": "~1.19.0",
4343
"command-shell-lib": "1.0.0",
4444
"express": "~4.16.4",
45+
"jexl": "2.1.1",
4546
"jison": "0.4.18",
4647
"logops": "2.1.0",
4748
"moment": "~2.24.0",

0 commit comments

Comments
 (0)