iotagent-node-lib 2.26.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +1 -0
- package/CHANGES_NEXT_RELEASE +2 -0
- package/docker-compose-dev.yml +1 -1
- package/lib/fiware-iotagent-lib.js +0 -5
- package/lib/plugins/compressTimestamp.js +2 -7
- package/lib/plugins/expressionParser.js +0 -47
- package/lib/plugins/expressionPlugin.js +6 -78
- package/lib/plugins/jexlParser.js +0 -36
- package/lib/plugins/pluginUtils.js +0 -119
- package/lib/services/ngsi/entities-NGSI-LD.js +586 -199
- package/lib/services/ngsi/entities-NGSI-v2.js +607 -297
- package/lib/services/ngsi/ngsiUtils.js +15 -22
- package/package.json +2 -2
- package/test/unit/general/config-multi-core-test.js +2 -1
- package/test/unit/general/contextBrokerKeystoneSecurityAccess-test.js +2 -1
- package/test/unit/general/deviceService-test.js +2 -1
- package/test/unit/general/loglevel-api_test.js +2 -3
- package/test/unit/general/startup-test.js +2 -1
- package/test/unit/general/statistics-persistence_test.js +1 -0
- package/test/unit/general/statistics-service_test.js +1 -0
- package/test/unit/lazyAndCommands/commandRegistry_test.js +1 -0
- package/test/unit/memoryRegistry/deviceRegistryMemory_test.js +1 -0
- package/test/unit/mongodb/mongodb-connectionoptions-test.js +1 -0
- package/test/unit/mongodb/mongodb-group-registry-test.js +1 -0
- package/test/unit/mongodb/mongodb-registry-test.js +2 -1
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextAliasPlugin1.json +0 -5
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast10.json +1 -1
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast8.json +1 -1
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast9.json +1 -1
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin32.json +16 -15
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin4.json +8 -8
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin4a.json +34 -34
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin5.json +8 -8
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin6.json +0 -20
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin7.json +0 -5
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin8.json +10 -10
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityTimestampPlugin1.json +8 -7
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityTimestampPlugin2.json +5 -4
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityTimestampPlugin3.json +1 -1
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextProcessTimestamp.json +4 -4
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestampOverride.json +4 -3
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestampOverrideWithoutMilis.json +4 -3
- package/test/unit/ngsi-ld/expressions/expressionBasedTransformations-test.js +4 -6
- package/test/unit/ngsi-ld/expressions/jexlBasedTransformations-test.js +1 -7
- package/test/unit/ngsi-ld/general/config-jsonld-contexts-test.js +2 -1
- package/test/unit/ngsi-ld/general/contextBrokerOAuthSecurityAccess-test.js +2 -1
- package/test/unit/ngsi-ld/general/deviceService-test.js +2 -1
- package/test/unit/ngsi-ld/general/https-support-test.js +2 -1
- package/test/unit/ngsi-ld/general/iotam-autoregistration-test.js +2 -1
- package/test/unit/ngsi-ld/general/startup-test.js +3 -2
- package/test/unit/ngsi-ld/lazyAndCommands/active-devices-attribute-update-test.js +2 -1
- package/test/unit/ngsi-ld/lazyAndCommands/command-test.js +5 -7
- package/test/unit/ngsi-ld/lazyAndCommands/lazy-devices-test.js +8 -6
- package/test/unit/ngsi-ld/lazyAndCommands/merge-patch-test.js +18 -22
- package/test/unit/ngsi-ld/lazyAndCommands/polling-commands-test.js +2 -1
- package/test/unit/ngsi-ld/ngsiService/active-devices-test.js +2 -1
- package/test/unit/ngsi-ld/ngsiService/autocast-test.js +17 -4
- package/test/unit/ngsi-ld/ngsiService/geoproperties-test.js +2 -1
- package/test/unit/ngsi-ld/ngsiService/languageProperties-test.js +32 -34
- package/test/unit/ngsi-ld/ngsiService/staticAttributes-test.js +2 -1
- package/test/unit/ngsi-ld/ngsiService/subscriptions-test.js +2 -1
- package/test/unit/ngsi-ld/ngsiService/unsupported-endpoints-test.js +11 -15
- package/test/unit/ngsi-ld/plugins/alias-plugin_test.js +2 -3
- package/test/unit/ngsi-ld/plugins/bidirectional-plugin_test.js +2 -1
- package/test/unit/ngsi-ld/plugins/compress-timestamp-plugin_test.js +2 -32
- package/test/unit/ngsi-ld/plugins/multientity-plugin_test.js +20 -19
- package/test/unit/ngsi-ld/plugins/timestamp-processing-plugin_test.js +21 -7
- package/test/unit/ngsi-ld/provisioning/device-provisioning-api_test.js +1 -0
- package/test/unit/ngsi-ld/provisioning/device-registration_test.js +2 -1
- package/test/unit/ngsi-ld/provisioning/device-update-registration_test.js +15 -12
- package/test/unit/ngsi-ld/provisioning/listProvisionedDevices-test.js +1 -0
- package/test/unit/ngsi-ld/provisioning/provisionDeviceMultientity-test.js +1 -0
- package/test/unit/ngsi-ld/provisioning/removeProvisionedDevice-test.js +1 -0
- package/test/unit/ngsi-ld/provisioning/singleConfigurationMode-test.js +1 -0
- package/test/unit/ngsi-ld/provisioning/updateProvisionedDevices-test.js +1 -0
- package/test/unit/ngsi-mixed/provisioning/ngsi-versioning-test.js +13 -7
- package/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin1.json +2 -2
- package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin32.json +6 -0
- package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin11.json +5 -1
- package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin5.json +4 -4
- package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin6.json +0 -16
- package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin7.json +0 -4
- package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin8.json +8 -8
- package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin1.json +42 -42
- package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin2.json +7 -7
- package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin4.json +0 -5
- package/test/unit/ngsiv2/examples/contextRequests/updateContextProcessTimestamp.json +6 -6
- package/test/unit/ngsiv2/examples/contextRequests/updateContextTimestampOverride.json +7 -1
- package/test/unit/ngsiv2/examples/contextRequests/updateContextTimestampOverrideWithoutMilis.json +7 -1
- package/test/unit/ngsiv2/expressions/expressionBasedTransformations-test.js +2 -4
- package/test/unit/ngsiv2/expressions/expressionCombinedTransformations-test.js +4 -8
- package/test/unit/ngsiv2/expressions/jexlBasedTransformations-test.js +5 -9
- package/test/unit/ngsiv2/general/contextBrokerOAuthSecurityAccess-test.js +2 -1
- package/test/unit/ngsiv2/general/deviceService-test.js +2 -1
- package/test/unit/ngsiv2/general/https-support-test.js +2 -1
- package/test/unit/ngsiv2/general/iotam-autoregistration-test.js +2 -1
- package/test/unit/ngsiv2/general/startup-test.js +2 -1
- package/test/unit/ngsiv2/lazyAndCommands/active-devices-attribute-update-test.js +2 -1
- package/test/unit/ngsiv2/lazyAndCommands/command-test.js +2 -1
- package/test/unit/ngsiv2/lazyAndCommands/lazy-devices-test.js +8 -6
- package/test/unit/ngsiv2/lazyAndCommands/polling-commands-test.js +2 -1
- package/test/unit/ngsiv2/ngsiService/active-devices-test.js +3 -2
- package/test/unit/ngsiv2/ngsiService/autocast-test.js +2 -1
- package/test/unit/ngsiv2/ngsiService/queryDeviceInformationInCb-test.js +2 -1
- package/test/unit/ngsiv2/ngsiService/staticAttributes-test.js +2 -1
- package/test/unit/ngsiv2/ngsiService/subscriptions-test.js +2 -1
- package/test/unit/ngsiv2/plugins/alias-plugin_test.js +2 -3
- package/test/unit/ngsiv2/plugins/bidirectional-plugin_test.js +2 -5
- package/test/unit/ngsiv2/plugins/compress-timestamp-plugin_test.js +2 -33
- package/test/unit/ngsiv2/plugins/multientity-plugin_test.js +7 -16
- package/test/unit/ngsiv2/plugins/timestamp-processing-plugin_test.js +3 -2
- package/test/unit/ngsiv2/provisioning/device-group-api-test.js +1 -0
- package/test/unit/ngsiv2/provisioning/device-group-utils-test.js +1 -0
- package/test/unit/ngsiv2/provisioning/device-provisioning-api_test.js +1 -0
- package/test/unit/ngsiv2/provisioning/device-registration_test.js +2 -1
- package/test/unit/ngsiv2/provisioning/device-update-registration_test.js +15 -12
- package/test/unit/ngsiv2/provisioning/listProvisionedDevices-test.js +1 -0
- package/test/unit/ngsiv2/provisioning/provisionDeviceMultientity-test.js +1 -0
- package/test/unit/ngsiv2/provisioning/removeProvisionedDevice-test.js +1 -0
- package/test/unit/ngsiv2/provisioning/singleConfigurationMode-test.js +1 -0
- package/test/unit/ngsiv2/provisioning/updateProvisionedDevices-test.js +1 -0
- package/test/unit/plugins/capture-configuration-inPlugins_test.js +2 -1
- package/test/unit/plugins/capture-provision-inPlugins_test.js +2 -1
- package/lib/plugins/addEvent.js +0 -32
- package/lib/plugins/attributeAlias.js +0 -107
- package/lib/plugins/multiEntity.js +0 -255
- package/lib/plugins/timestampProcessPlugin.js +0 -95
- package/test/unit/ngsi-ld/plugins/event-plugin_test.js +0 -116
- package/test/unit/ngsiv2/plugins/event-plugin_test.js +0 -118
- package/test/unit/ngsiv2/plugins/translation-inPlugins_test.js +0 -262
|
@@ -28,9 +28,6 @@
|
|
|
28
28
|
/* eslint-disable consistent-return */
|
|
29
29
|
|
|
30
30
|
const request = require('../../request-shim');
|
|
31
|
-
const statsService = require('./../stats/statsRegistry');
|
|
32
|
-
const async = require('async');
|
|
33
|
-
const apply = async.apply;
|
|
34
31
|
const alarms = require('../common/alarmManagement');
|
|
35
32
|
const errors = require('../../errors');
|
|
36
33
|
const utils = require('../northBound/restUtils');
|
|
@@ -38,13 +35,14 @@ const pluginUtils = require('../../plugins/pluginUtils');
|
|
|
38
35
|
const config = require('../../commonConfig');
|
|
39
36
|
const constants = require('../../constants');
|
|
40
37
|
const jexlParser = require('../../plugins/jexlParser');
|
|
38
|
+
const expressionPlugin = require('../../plugins/expressionPlugin');
|
|
39
|
+
const compressTimestampPlugin = require('../../plugins/compressTimestamp');
|
|
41
40
|
const moment = require('moment-timezone');
|
|
42
41
|
const NGSIUtils = require('./ngsiUtils');
|
|
43
42
|
const logger = require('logops');
|
|
44
43
|
const context = {
|
|
45
44
|
op: 'IoTAgentNGSI.Entities-v2'
|
|
46
45
|
};
|
|
47
|
-
const _ = require('underscore');
|
|
48
46
|
|
|
49
47
|
/**
|
|
50
48
|
* Amends an NGSIv2 Geoattribute from String to GeoJSON format
|
|
@@ -96,16 +94,29 @@ function formatGeoAttrs(attr) {
|
|
|
96
94
|
return obj;
|
|
97
95
|
}
|
|
98
96
|
|
|
99
|
-
|
|
100
|
-
|
|
97
|
+
/**
|
|
98
|
+
* Adds timestamp to ngsiv2 payload entities accoding to timezone, and an optional timestampvalue.
|
|
99
|
+
*
|
|
100
|
+
* @param {Object} payload NGSIv2 payload with one or more entities
|
|
101
|
+
* @param String timezone TimeZone value (optional)
|
|
102
|
+
* @param String timestampValue Timestamp value (optional). If not provided current timestamp is used
|
|
103
|
+
* @param Boolean skipMetadataAtt An optional flag to indicate if timestamp should be added to each metadata attribute. Default is false
|
|
104
|
+
* @return {Object} NGSIv2 payload entities with timestamp
|
|
105
|
+
*/
|
|
106
|
+
function addTimestampNgsi2(payload, timezone, timestampValue) {
|
|
107
|
+
function addTimestampEntity(entity, timezone, timestampValue) {
|
|
101
108
|
const timestamp = {
|
|
102
109
|
type: constants.TIMESTAMP_TYPE_NGSI2
|
|
103
110
|
};
|
|
104
111
|
|
|
105
|
-
if (
|
|
106
|
-
timestamp.value =
|
|
112
|
+
if (timestampValue) {
|
|
113
|
+
timestamp.value = timestampValue;
|
|
107
114
|
} else {
|
|
108
|
-
|
|
115
|
+
if (!timezone) {
|
|
116
|
+
timestamp.value = new Date().toISOString();
|
|
117
|
+
} else {
|
|
118
|
+
timestamp.value = moment().tz(timezone).format('YYYY-MM-DD[T]HH:mm:ss.SSSZ');
|
|
119
|
+
}
|
|
109
120
|
}
|
|
110
121
|
|
|
111
122
|
function addMetadata(attribute) {
|
|
@@ -153,13 +164,13 @@ function addTimestampNgsi2(payload, timezone) {
|
|
|
153
164
|
if (payload instanceof Array) {
|
|
154
165
|
for (let i = 0; i < payload.length; i++) {
|
|
155
166
|
if (!utils.isTimestampedNgsi2(payload[i])) {
|
|
156
|
-
payload[i] = addTimestampEntity(payload[i], timezone);
|
|
167
|
+
payload[i] = addTimestampEntity(payload[i], timezone, timestampValue);
|
|
157
168
|
}
|
|
158
169
|
}
|
|
159
170
|
|
|
160
171
|
return payload;
|
|
161
172
|
}
|
|
162
|
-
return addTimestampEntity(payload, timezone);
|
|
173
|
+
return addTimestampEntity(payload, timezone, timestampValue);
|
|
163
174
|
}
|
|
164
175
|
|
|
165
176
|
/**
|
|
@@ -314,151 +325,14 @@ function sendQueryValueNgsi2(entityName, attributes, typeInformation, token, cal
|
|
|
314
325
|
);
|
|
315
326
|
}
|
|
316
327
|
|
|
317
|
-
function extractContextFromPayload(payload) {
|
|
318
|
-
//{b: {value: 3,type: "string"},c: {value: false,type: "string"},d:{value: {g: 45},type: "string"},id: "tamcaysanto",type: "WaterTankMulti"}
|
|
319
|
-
//to
|
|
320
|
-
//{b:3,c:false,d:{g:45}}
|
|
321
|
-
|
|
322
|
-
let ctx = {};
|
|
323
|
-
for (const key in payload) {
|
|
324
|
-
if (key !== 'type' && key !== 'id') {
|
|
325
|
-
ctx[key] = payload[key].value;
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
return ctx;
|
|
329
|
-
}
|
|
330
328
|
/**
|
|
331
|
-
*
|
|
329
|
+
* Determines if a value is of type float
|
|
332
330
|
*
|
|
333
|
-
* @param
|
|
334
|
-
* @
|
|
331
|
+
* @param {String} value Value to be analyzed
|
|
332
|
+
* @return {boolean} True if float, False otherwise.
|
|
335
333
|
*/
|
|
336
|
-
function
|
|
337
|
-
|
|
338
|
-
let effectiveList = [];
|
|
339
|
-
for (const attr of explicitAttrsList) {
|
|
340
|
-
if (typeof attr === 'string') {
|
|
341
|
-
effectiveList.push(attr);
|
|
342
|
-
} else if (typeof attr === 'object' && attr.object_id) {
|
|
343
|
-
//find objectId in active attributes
|
|
344
|
-
for (const active of typeInformation.active) {
|
|
345
|
-
if (active.object_id === attr.object_id) {
|
|
346
|
-
if (
|
|
347
|
-
active &&
|
|
348
|
-
active.name &&
|
|
349
|
-
active.entity_name &&
|
|
350
|
-
active.entity_name === entity.id
|
|
351
|
-
//if name is an expression it could fail... BUT IT WORKS!
|
|
352
|
-
) {
|
|
353
|
-
effectiveList.push(active.name);
|
|
354
|
-
}
|
|
355
|
-
continue; //object_id must be unique
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
return effectiveList;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
let explicitAttrsList;
|
|
364
|
-
if (typeInformation.explicitAttrs) {
|
|
365
|
-
explicitAttrsList = [];
|
|
366
|
-
explicitAttrsList.push('type');
|
|
367
|
-
explicitAttrsList.push('id');
|
|
368
|
-
if (typeof typeInformation.explicitAttrs === 'boolean') {
|
|
369
|
-
if (typeInformation.timestamp) {
|
|
370
|
-
explicitAttrsList.push(constants.TIMESTAMP_ATTRIBUTE);
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
if (typeInformation.active) {
|
|
374
|
-
typeInformation.active.forEach((attr) => {
|
|
375
|
-
explicitAttrsList.push(attr.name);
|
|
376
|
-
});
|
|
377
|
-
}
|
|
378
|
-
if (typeInformation.staticAttributes) {
|
|
379
|
-
typeInformation.staticAttributes.forEach((attr) => {
|
|
380
|
-
explicitAttrsList.push(attr.name);
|
|
381
|
-
});
|
|
382
|
-
}
|
|
383
|
-
} else if (typeof typeInformation.explicitAttrs === 'string') {
|
|
384
|
-
entities.forEach((entity) => {
|
|
385
|
-
const attsArray = pluginUtils.extractAttributesArrayFromNgsi2Entity(entity);
|
|
386
|
-
const ctx = {
|
|
387
|
-
...jexlParser.extractContext(attsArray),
|
|
388
|
-
...extractContextFromPayload(payload)
|
|
389
|
-
}; //maybe overlapping between this two objects.
|
|
390
|
-
let res = null;
|
|
391
|
-
try {
|
|
392
|
-
res = jexlParser.applyExpression(typeInformation.explicitAttrs, ctx, typeInformation);
|
|
393
|
-
} catch (e) {
|
|
394
|
-
// nothing to do: exception is already logged at info level
|
|
395
|
-
}
|
|
396
|
-
explicitAttrsList = res ? explicitAttrsList.concat(res) : explicitAttrsList;
|
|
397
|
-
});
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
if (explicitAttrsList && explicitAttrsList.length >= 0) {
|
|
401
|
-
entities.forEach((entity) => {
|
|
402
|
-
//some attrs are object_ids {object_id:oid} and need to be resolved for this entity
|
|
403
|
-
//"explicitAttrs" : "attrA?['attrA',{object_id:'foo'}]:['attrB',{object_id:'bar'}]",
|
|
404
|
-
let efectiveAttrsList = filterForEntity(entity, explicitAttrsList, typeInformation);
|
|
405
|
-
const hidden = _.difference(_.keys(entity), efectiveAttrsList);
|
|
406
|
-
logger.debug(context, 'removeHiddenAttrsFromMultiEntity %s from entity %s', hidden, entity);
|
|
407
|
-
hidden.forEach((attr) => {
|
|
408
|
-
delete entity[attr];
|
|
409
|
-
});
|
|
410
|
-
});
|
|
411
|
-
}
|
|
412
|
-
return entities;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
/**
|
|
416
|
-
* Remove id, type and any hidden attrs after processing
|
|
417
|
-
*
|
|
418
|
-
* @param {Object} result An Unprocessed entity
|
|
419
|
-
* @param {Object} typeInformation Configuration information for the device.
|
|
420
|
-
*/
|
|
421
|
-
function removeHiddenAttrs(result, typeInformation, payload) {
|
|
422
|
-
delete result.id;
|
|
423
|
-
delete result.type;
|
|
424
|
-
let explicitAttrsList;
|
|
425
|
-
if (typeInformation.explicitAttrs && typeof typeInformation.explicitAttrs === 'boolean') {
|
|
426
|
-
explicitAttrsList = [];
|
|
427
|
-
if (typeInformation.timestamp) {
|
|
428
|
-
explicitAttrsList.push(constants.TIMESTAMP_ATTRIBUTE);
|
|
429
|
-
}
|
|
430
|
-
if (typeInformation.active) {
|
|
431
|
-
typeInformation.active.forEach((attr) => {
|
|
432
|
-
explicitAttrsList.push(attr.name);
|
|
433
|
-
});
|
|
434
|
-
}
|
|
435
|
-
if (typeInformation.staticAttributes) {
|
|
436
|
-
typeInformation.staticAttributes.forEach((attr) => {
|
|
437
|
-
explicitAttrsList.push(attr.name);
|
|
438
|
-
});
|
|
439
|
-
}
|
|
440
|
-
} else if (typeInformation.explicitAttrs && typeof typeInformation.explicitAttrs === 'string') {
|
|
441
|
-
const attsArray = pluginUtils.extractAttributesArrayFromNgsi2Entity(result);
|
|
442
|
-
const ctx = {
|
|
443
|
-
...jexlParser.extractContext(attsArray),
|
|
444
|
-
...extractContextFromPayload(payload)
|
|
445
|
-
}; //maybe overlapping between this two objects. Measures not in active attrs.
|
|
446
|
-
let res = null;
|
|
447
|
-
try {
|
|
448
|
-
res = jexlParser.applyExpression(typeInformation.explicitAttrs, ctx, typeInformation);
|
|
449
|
-
} catch (e) {
|
|
450
|
-
// nothing to do: exception is already logged at info level
|
|
451
|
-
}
|
|
452
|
-
explicitAttrsList = res ? res : [];
|
|
453
|
-
}
|
|
454
|
-
if (explicitAttrsList && explicitAttrsList.length >= 0) {
|
|
455
|
-
const hidden = _.difference(_.keys(result), explicitAttrsList);
|
|
456
|
-
logger.debug(context, 'removeHiddenAttrs %s', hidden);
|
|
457
|
-
hidden.forEach((attr) => {
|
|
458
|
-
delete result[attr];
|
|
459
|
-
});
|
|
460
|
-
}
|
|
461
|
-
return result;
|
|
334
|
+
function isFloat(value) {
|
|
335
|
+
return !isNaN(value) && value.toString().indexOf('.') !== -1;
|
|
462
336
|
}
|
|
463
337
|
|
|
464
338
|
/**
|
|
@@ -471,12 +345,31 @@ function removeHiddenAttrs(result, typeInformation, payload) {
|
|
|
471
345
|
* @param {String} token User token to identify against the PEP Proxies (optional).
|
|
472
346
|
*/
|
|
473
347
|
function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, callback) {
|
|
474
|
-
|
|
348
|
+
logger.debug(
|
|
349
|
+
context,
|
|
350
|
+
'sendUpdateValueNgsi2 called with: \n entityName=%s \n attributes=%j \n typeInformation=%j',
|
|
351
|
+
entityName,
|
|
352
|
+
attributes,
|
|
353
|
+
typeInformation
|
|
354
|
+
);
|
|
355
|
+
let payload = {
|
|
356
|
+
entities: [
|
|
357
|
+
{
|
|
358
|
+
id: entityName
|
|
359
|
+
}
|
|
360
|
+
]
|
|
361
|
+
};
|
|
475
362
|
|
|
476
|
-
let url = '/v2/
|
|
363
|
+
let url = '/v2/op/update';
|
|
477
364
|
|
|
478
365
|
if (typeInformation && typeInformation.type) {
|
|
479
|
-
|
|
366
|
+
payload.entities[0].type = typeInformation.type;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (config.getConfig().appendMode === true) {
|
|
370
|
+
payload.actionType = 'append';
|
|
371
|
+
} else {
|
|
372
|
+
payload.actionType = 'update';
|
|
480
373
|
}
|
|
481
374
|
|
|
482
375
|
let options = NGSIUtils.createRequestObject(url, typeInformation, token);
|
|
@@ -489,173 +382,590 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
|
|
|
489
382
|
callback(new errors.TypeNotFound(null, entityName));
|
|
490
383
|
return;
|
|
491
384
|
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
385
|
+
let idTypeSSSList = pluginUtils.getIdTypeServSubServiceFromDevice(typeInformation);
|
|
386
|
+
logger.debug(context, 'sendUpdateValueNgsi2 \n idTypeSSS are %j ', idTypeSSSList);
|
|
387
|
+
let measureAttrsForCtxt = [];
|
|
388
|
+
|
|
389
|
+
// Check explicitAttrs: adds all final needed attributes to payload
|
|
390
|
+
if (
|
|
391
|
+
typeInformation.explicitAttrs === undefined ||
|
|
392
|
+
(typeof typeInformation.explicitAttrs === 'boolean' && !typeInformation.explicitAttrs)
|
|
393
|
+
// explicitAttrs is not defined => default case: all attrs should be included
|
|
394
|
+
) {
|
|
395
|
+
// This loop adds all measure values (attributes) into payload entities (entity[0])
|
|
396
|
+
for (let i = 0; i < attributes.length; i++) {
|
|
397
|
+
if (attributes[i].name && attributes[i].type) {
|
|
398
|
+
payload.entities[0][attributes[i].name] = {
|
|
399
|
+
value: attributes[i].value,
|
|
400
|
+
type: attributes[i].type
|
|
401
|
+
};
|
|
402
|
+
const metadata = NGSIUtils.getMetaData(typeInformation, attributes[i].name, attributes[i].metadata);
|
|
403
|
+
if (metadata) {
|
|
404
|
+
payload.entities[0][attributes[i].name].metadata = metadata;
|
|
405
|
+
}
|
|
406
|
+
} else {
|
|
407
|
+
callback(new errors.BadRequest(null, entityName));
|
|
408
|
+
return;
|
|
502
409
|
}
|
|
503
|
-
} else {
|
|
504
|
-
callback(new errors.BadRequest(null, entityName));
|
|
505
|
-
return;
|
|
506
410
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
callback(error);
|
|
520
|
-
} else {
|
|
521
|
-
if (result) {
|
|
522
|
-
// The payload has been transformed by multientity plugin. It is not a JSON object but an Array.
|
|
523
|
-
if (result instanceof Array) {
|
|
524
|
-
options = NGSIUtils.createRequestObject('/v2/op/update', typeInformation, token);
|
|
525
|
-
|
|
526
|
-
if (
|
|
527
|
-
'timestamp' in typeInformation && typeInformation.timestamp !== undefined
|
|
528
|
-
? typeInformation.timestamp
|
|
529
|
-
: config.getConfig().timestamp
|
|
530
|
-
) {
|
|
531
|
-
// jshint maxdepth:5
|
|
532
|
-
if (!utils.isTimestampedNgsi2(result)) {
|
|
533
|
-
result = addTimestampNgsi2(result, typeInformation.timezone);
|
|
534
|
-
// jshint maxdepth:5
|
|
535
|
-
} else if (!utils.IsValidTimestampedNgsi2(result)) {
|
|
536
|
-
logger.error(context, 'Invalid timestamp:%s', JSON.stringify(result));
|
|
537
|
-
callback(new errors.BadTimestamp(result));
|
|
538
|
-
return;
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
options.json = {
|
|
542
|
-
actionType: 'append',
|
|
543
|
-
entities: removeHiddenAttrsFromMultiEntity(result, typeInformation, payload)
|
|
411
|
+
logger.debug(context, 'sendUpdateValueNgsi2 \n pre-initial non-explicitAttrs payload=%j', payload);
|
|
412
|
+
// Loop for add attrs from type.information.active (and lazys?) into payload entities (entity[0])
|
|
413
|
+
if (typeInformation.active) {
|
|
414
|
+
typeInformation.active.forEach((attr) => {
|
|
415
|
+
if (attr.expression) {
|
|
416
|
+
if (attr.object_id) {
|
|
417
|
+
payload.entities[0][attr.object_id] = {
|
|
418
|
+
value: payload.entities[0][attr.object_id]
|
|
419
|
+
? payload.entities[0][attr.object_id].value
|
|
420
|
+
: undefined,
|
|
421
|
+
type: attr.type,
|
|
422
|
+
object_id: attr.object_id
|
|
544
423
|
};
|
|
545
|
-
if (config.getConfig().appendMode === true) {
|
|
546
|
-
options.json.actionType = 'append';
|
|
547
|
-
} else {
|
|
548
|
-
options.json.actionType = 'update';
|
|
549
|
-
}
|
|
550
424
|
} else {
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
425
|
+
payload.entities[0][attr.name] = {
|
|
426
|
+
value: payload.entities[0][attr.name] ? payload.entities[0][attr.name].value : undefined,
|
|
427
|
+
type: attr.type
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
} else {
|
|
434
|
+
let selectedAttrs = [];
|
|
435
|
+
if (typeof typeInformation.explicitAttrs === 'string') {
|
|
436
|
+
// explicitAttrs is a jexlExpression
|
|
437
|
+
// This ctxt should include all possible attrs
|
|
438
|
+
let attributesCtxt = [];
|
|
439
|
+
if (typeInformation.static) {
|
|
440
|
+
typeInformation.static.forEach(function (att) {
|
|
441
|
+
attributesCtxt.push(att);
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
// Measures
|
|
445
|
+
for (let i = 0; i < attributes.length; i++) {
|
|
446
|
+
if (attributes[i].name && attributes[i].type) {
|
|
447
|
+
let measureAttr = {
|
|
448
|
+
name: attributes[i].name,
|
|
449
|
+
value: attributes[i].value,
|
|
450
|
+
type: attributes[i].type
|
|
451
|
+
};
|
|
452
|
+
attributesCtxt.push(measureAttr);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
// This context is just to calculate explicitAttrs when is an expression
|
|
456
|
+
let ctxt = expressionPlugin.extractContext(attributesCtxt.concat(idTypeSSSList), typeInformation);
|
|
457
|
+
// typeInformation.active attrs with expressions expanded by current ctxt
|
|
458
|
+
if (typeInformation.active) {
|
|
459
|
+
typeInformation.active.forEach(function (att) {
|
|
460
|
+
if (att.expression) {
|
|
461
|
+
if (expressionPlugin.contextAvailable(att.expression, ctxt, typeInformation)) {
|
|
462
|
+
let expandedAttr = {
|
|
463
|
+
name: att.name,
|
|
464
|
+
value: att.expression, // it doesn't matter final value here
|
|
465
|
+
type: att.type
|
|
466
|
+
};
|
|
467
|
+
attributesCtxt.push(expandedAttr);
|
|
468
|
+
ctxt = expressionPlugin.extractContext(
|
|
469
|
+
attributesCtxt.concat(idTypeSSSList),
|
|
470
|
+
typeInformation
|
|
471
|
+
);
|
|
570
472
|
}
|
|
571
473
|
}
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
// calculate expression for explicitAttrs
|
|
477
|
+
try {
|
|
478
|
+
let res = jexlParser.applyExpression(typeInformation.explicitAttrs, ctxt, typeInformation);
|
|
479
|
+
if (res === true) {
|
|
480
|
+
// like explicitAttrs == true
|
|
481
|
+
// selectAttrs should be measures which are defined attributes
|
|
482
|
+
typeInformation.active.forEach((attr) => {
|
|
483
|
+
selectedAttrs.push(attr.name);
|
|
484
|
+
selectedAttrs.push(attr.object_id);
|
|
485
|
+
});
|
|
486
|
+
} else if (res === false) {
|
|
487
|
+
// like explicitAttrs == false
|
|
488
|
+
// selectAttrs should be measures and defined attributes
|
|
489
|
+
typeInformation.active.forEach((attr) => {
|
|
490
|
+
selectedAttrs.push(attr.name);
|
|
491
|
+
selectedAttrs.push(attr.object_id);
|
|
492
|
+
});
|
|
493
|
+
for (let i = 0; i < attributes.length; i++) {
|
|
494
|
+
selectedAttrs.push(attributes[i].name);
|
|
495
|
+
}
|
|
572
496
|
} else {
|
|
573
|
-
|
|
574
|
-
delete payload.type;
|
|
575
|
-
options.json = payload;
|
|
497
|
+
selectedAttrs = res; // TBD: Check ensure is an array of strings
|
|
576
498
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
499
|
+
if (selectedAttrs.length === 0) {
|
|
500
|
+
// implies do nothing
|
|
501
|
+
logger.info(
|
|
502
|
+
context,
|
|
503
|
+
'sendUpdateValueNgsi2 \n none selectedAttrs with %j and ctxt %j',
|
|
504
|
+
typeInformation.explicitAttrs,
|
|
505
|
+
ctxt
|
|
506
|
+
);
|
|
507
|
+
return callback(null);
|
|
585
508
|
}
|
|
509
|
+
} catch (e) {
|
|
510
|
+
// nothing to do: exception is already logged at info level
|
|
511
|
+
}
|
|
586
512
|
|
|
587
|
-
|
|
588
|
-
if (
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
513
|
+
typeInformation.active.forEach((attr) => {
|
|
514
|
+
if (selectedAttrs.includes(attr.name)) {
|
|
515
|
+
selectedAttrs.push(attr.object_id);
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
} else if (typeInformation.explicitAttrs && typeof typeInformation.explicitAttrs === 'boolean') {
|
|
519
|
+
// TBD: selectedAttrs could be a boolean as a result of applyExpression
|
|
520
|
+
// explicitAtts is true => Add measures which are defined attributes
|
|
521
|
+
typeInformation.active.forEach((attr) => {
|
|
522
|
+
selectedAttrs.push(attr.name);
|
|
523
|
+
selectedAttrs.push(attr.object_id);
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
// This loop adds selected measured values (attributes) into payload entities (entity[0])
|
|
527
|
+
for (let i = 0; i < attributes.length; i++) {
|
|
528
|
+
if (attributes[i].name && selectedAttrs.includes(attributes[i].name) && attributes[i].type) {
|
|
529
|
+
let attr = typeInformation.active.find((obj) => {
|
|
530
|
+
return obj.name === attributes[i].name;
|
|
531
|
+
});
|
|
532
|
+
payload.entities[0][attributes[i].name] = {
|
|
533
|
+
value: attributes[i].value,
|
|
534
|
+
type: attributes[i].type
|
|
535
|
+
};
|
|
536
|
+
// ensure payload has attr with proper object_id
|
|
537
|
+
if (attr && attr.object_id) {
|
|
538
|
+
payload.entities[0][attributes[i].name].object_id = attr.object_id;
|
|
539
|
+
}
|
|
540
|
+
const metadata = NGSIUtils.getMetaData(typeInformation, attributes[i].name, attributes[i].metadata);
|
|
541
|
+
if (metadata) {
|
|
542
|
+
payload.entities[0][attributes[i].name].metadata = metadata;
|
|
543
|
+
}
|
|
544
|
+
} else if (attributes[i].name && !selectedAttrs.includes(attributes[i].name) && attributes[i].type) {
|
|
545
|
+
let att = {
|
|
546
|
+
name: attributes[i].name,
|
|
547
|
+
type: attributes[i].type,
|
|
548
|
+
value: attributes[i].value
|
|
549
|
+
};
|
|
550
|
+
measureAttrsForCtxt.push(att);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
logger.debug(
|
|
554
|
+
context,
|
|
555
|
+
'sendUpdateValueNgsi2 \n pre-initial explicitAttrs payload=%j \n selectedAttrs',
|
|
556
|
+
payload,
|
|
557
|
+
selectedAttrs
|
|
558
|
+
);
|
|
559
|
+
|
|
560
|
+
// Loop for add seleted attrs from type.information.active into pyaload entities (entity[0])
|
|
561
|
+
if (typeInformation.active) {
|
|
562
|
+
typeInformation.active.forEach((attr) => {
|
|
563
|
+
if (selectedAttrs.includes(attr.name)) {
|
|
564
|
+
if (attr.object_id) {
|
|
565
|
+
payload.entities[0][attr.object_id] = {
|
|
566
|
+
value: payload.entities[0][attr.object_id]
|
|
567
|
+
? payload.entities[0][attr.object_id].value
|
|
568
|
+
: payload.entities[0][attr.name]
|
|
569
|
+
? payload.entities[0][attr.name].value
|
|
570
|
+
: undefined,
|
|
571
|
+
type: attr.type,
|
|
572
|
+
object_id: attr.object_id
|
|
573
|
+
};
|
|
574
|
+
} else {
|
|
575
|
+
payload.entities[0][attr.name] = {
|
|
576
|
+
value: payload.entities[0][attr.name] ? payload.entities[0][attr.name].value : undefined,
|
|
577
|
+
type: attr.type
|
|
578
|
+
};
|
|
610
579
|
}
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
} // END check explicitAttrs
|
|
584
|
+
logger.debug(context, 'sendUpdateValueNgsi2 \n initial payload=%j', payload);
|
|
585
|
+
|
|
586
|
+
let currentEntity = payload.entities[0];
|
|
587
|
+
|
|
588
|
+
// Prepare attributes for expresionPlugin
|
|
589
|
+
const attsArray = pluginUtils.extractAttributesArrayFromNgsi2Entity(currentEntity);
|
|
590
|
+
|
|
591
|
+
// Exclude processing all attr expressions when current attr is of type 'commandStatus' or 'commandResult'
|
|
592
|
+
let attsArrayFiltered = [];
|
|
593
|
+
if (attsArray) {
|
|
594
|
+
attsArrayFiltered = attsArray.filter((obj) => {
|
|
595
|
+
return ![constants.COMMAND_STATUS, constants.COMMAND_RESULT].includes(obj.type);
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
let attributesCtxt = [...attsArrayFiltered]; // just copy
|
|
599
|
+
if (typeInformation.static) {
|
|
600
|
+
typeInformation.static.forEach(function (att) {
|
|
601
|
+
attributesCtxt.push(att);
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
if (measureAttrsForCtxt) {
|
|
605
|
+
measureAttrsForCtxt.forEach(function (att) {
|
|
606
|
+
attributesCtxt.push(att);
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
attributesCtxt = attributesCtxt.concat(idTypeSSSList);
|
|
610
|
+
let ctxt = expressionPlugin.extractContext(attributesCtxt, typeInformation);
|
|
611
|
+
logger.debug(context, 'sendUpdateValueNgsi2 \n initial ctxt %j ', ctxt);
|
|
612
|
+
|
|
613
|
+
// Sort currentEntity to get first attrs without expressions (checking attrs in typeInformation.active)
|
|
614
|
+
// attributes without expressions should be processed before
|
|
615
|
+
logger.debug(context, 'sendUpdateValueNgsi2 \n currentEntity %j ', currentEntity);
|
|
616
|
+
if (typeInformation.active && typeInformation.active.length > 0) {
|
|
617
|
+
for (const k in currentEntity) {
|
|
618
|
+
typeInformation.active.forEach(function (att) {
|
|
619
|
+
if (
|
|
620
|
+
(att.object_id && att.object_id === k && att.expression) ||
|
|
621
|
+
(att.name && att.name === k && att.expression)
|
|
622
|
+
) {
|
|
623
|
+
let m = currentEntity[k];
|
|
624
|
+
delete currentEntity[k];
|
|
625
|
+
currentEntity[k] = m; // put into the end of currentEntity
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
logger.debug(context, 'sendUpdateValueNgsi2 \n currentEntity sorted %j ', currentEntity);
|
|
631
|
+
let timestampValue = undefined;
|
|
632
|
+
// Loop for each final attribute to apply alias, multientity and expressions
|
|
633
|
+
for (const j in currentEntity) {
|
|
634
|
+
// discard id and type
|
|
635
|
+
if (j !== 'id' || j !== 'type') {
|
|
636
|
+
// Apply Mapping Alias: object_id in attributes are in typeInformation.active
|
|
637
|
+
let attr;
|
|
638
|
+
let newAttr = payload.entities[0][j];
|
|
639
|
+
if (typeInformation.active) {
|
|
640
|
+
attr = typeInformation.active.find((obj) => {
|
|
641
|
+
return obj.object_id === j;
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
if (!attr) {
|
|
645
|
+
if (typeInformation.lazy) {
|
|
646
|
+
attr = typeInformation.lazy.find((obj) => {
|
|
647
|
+
return obj.object_id === j;
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
if (!attr) {
|
|
652
|
+
if (typeInformation.active) {
|
|
653
|
+
attr = typeInformation.active.find((obj) => {
|
|
654
|
+
return obj.name === j;
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
if (attr && attr.name) {
|
|
659
|
+
if (['id', 'type'].includes(attr.name)) {
|
|
660
|
+
// invalid mapping
|
|
661
|
+
logger.debug(
|
|
662
|
+
context,
|
|
663
|
+
'sendUpdateValueNgsi2 \n invalid mapping for attr %j \n newAttr %j',
|
|
664
|
+
attr,
|
|
665
|
+
newAttr
|
|
666
|
+
);
|
|
667
|
+
delete payload.entities[0][attr.object_id];
|
|
668
|
+
attr = undefined; // stop processing attr
|
|
669
|
+
newAttr = undefined;
|
|
611
670
|
} else {
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
671
|
+
ctxt[attr.name] = payload.entities[0][j]['value'];
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
logger.debug(
|
|
675
|
+
context,
|
|
676
|
+
'sendUpdateValueNgsi2 \n procesing j %j attr %j ctxt %j \n newAttr %j ',
|
|
677
|
+
j,
|
|
678
|
+
attr,
|
|
679
|
+
ctxt,
|
|
680
|
+
newAttr
|
|
681
|
+
);
|
|
682
|
+
if (attr && attr.type) {
|
|
683
|
+
newAttr.type = attr.type;
|
|
684
|
+
}
|
|
624
685
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
686
|
+
// Apply expression
|
|
687
|
+
if (attr && attr.expression) {
|
|
688
|
+
logger.debug(
|
|
689
|
+
context,
|
|
690
|
+
'sendUpdateValueNgsi2 \n apply expression %j \n over ctxt %j \n and device %j',
|
|
691
|
+
attr.expression,
|
|
692
|
+
ctxt,
|
|
693
|
+
typeInformation
|
|
694
|
+
);
|
|
695
|
+
let res = null;
|
|
696
|
+
try {
|
|
697
|
+
if (expressionPlugin.contextAvailable(attr.expression, ctxt, typeInformation)) {
|
|
698
|
+
res = expressionPlugin.applyExpression(attr.expression, ctxt, typeInformation);
|
|
699
|
+
} else {
|
|
700
|
+
logger.warn(
|
|
701
|
+
context,
|
|
702
|
+
'sendUpdateValueNgsi2 \n no context available for apply expression %j \n',
|
|
703
|
+
attr.expression
|
|
704
|
+
);
|
|
705
|
+
res = newAttr.value; // keep newAttr value
|
|
706
|
+
}
|
|
707
|
+
} catch (e) {
|
|
708
|
+
logger.error(context, 'sendUpdateValueNgsi2 \n apply expression exception %j \n', e);
|
|
709
|
+
if (!expressionPlugin.checkJexl(typeInformation)) {
|
|
710
|
+
return callback(e); // just throw error with legacy parser for backward compatiblity
|
|
711
|
+
} else {
|
|
712
|
+
res = ctxt[attr.name]; // TBD: add reference to test
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
if (!expressionPlugin.checkJexl(typeInformation)) {
|
|
716
|
+
// legacy expression plugin: Involves some legacy checks performed by applierExpression
|
|
717
|
+
if (
|
|
718
|
+
res &&
|
|
719
|
+
res !== 'undefined' &&
|
|
720
|
+
res !== 'null' &&
|
|
721
|
+
(typeof res === 'string' || res instanceof String) &&
|
|
722
|
+
!res.includes('NaN')
|
|
723
|
+
) {
|
|
724
|
+
newAttr.value = res;
|
|
725
|
+
if (newAttr.type === 'Number' && isFloat(newAttr.value)) {
|
|
726
|
+
newAttr.value = Number.parseFloat(newAttr.value);
|
|
727
|
+
} else if (newAttr.type === 'Number' && !Number.isNaN(Number.parseInt(newAttr.value))) {
|
|
728
|
+
newAttr.value = Number.parseInt(newAttr.value);
|
|
729
|
+
} else if (newAttr.type === 'Boolean') {
|
|
730
|
+
newAttr.value = newAttr.value === 'true' || newAttr.value === '1';
|
|
731
|
+
} else if (newAttr.type === 'None') {
|
|
732
|
+
newAttr.value = null;
|
|
733
|
+
}
|
|
734
|
+
} else if (isNaN(res) || res === 'undefined' || res === 'null') {
|
|
735
|
+
logger.debug(
|
|
736
|
+
context,
|
|
737
|
+
'sendUpdateValueNgsi2 \n apply expression result: isNaN || undefined || null'
|
|
738
|
+
);
|
|
739
|
+
if (res === 'null' && newAttr.type === 'None') {
|
|
740
|
+
newAttr.value = null;
|
|
741
|
+
} else {
|
|
742
|
+
delete payload.entities[0][j]; // remove measure attr
|
|
743
|
+
attr = undefined; // stop process attr
|
|
630
744
|
}
|
|
631
745
|
}
|
|
746
|
+
} else {
|
|
747
|
+
// jexl expression plugin
|
|
748
|
+
newAttr.value = res;
|
|
632
749
|
}
|
|
750
|
+
logger.debug(context, 'sendUpdateValueNgsi2 \n apply expression result %j \n newAttr %j', res, newAttr);
|
|
751
|
+
}
|
|
633
752
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
753
|
+
// Apply Multientity: entity_type and entity_name in attributes are in typeInformation.active
|
|
754
|
+
if (attr && (attr.entity_type || attr.entity_name)) {
|
|
755
|
+
// Create a newEntity for this attribute
|
|
756
|
+
let newEntityName = null;
|
|
757
|
+
if (attr.entity_name) {
|
|
758
|
+
try {
|
|
759
|
+
if (expressionPlugin.contextAvailable(attr.entity_name, ctxt, typeInformation)) {
|
|
760
|
+
newEntityName = expressionPlugin.applyExpression(attr.entity_name, ctxt, typeInformation);
|
|
761
|
+
} else {
|
|
762
|
+
logger.warn(
|
|
763
|
+
context,
|
|
764
|
+
'sendUpdateValueNgsi2 \n MULTI no context available for apply expression %j \n',
|
|
765
|
+
attr.entity_name
|
|
766
|
+
);
|
|
767
|
+
newEntityName = attr.entity_name;
|
|
768
|
+
}
|
|
769
|
+
newEntityName = newEntityName ? newEntityName : attr.entity_name;
|
|
770
|
+
} catch (e) {
|
|
771
|
+
logger.error(context, 'sendUpdateValueNgsi2 \n MULTI apply expression exception %j \n', e);
|
|
772
|
+
newEntityName = attr.entity_name;
|
|
773
|
+
}
|
|
637
774
|
logger.debug(
|
|
638
775
|
context,
|
|
639
|
-
'
|
|
640
|
-
|
|
776
|
+
'sendUpdateValueNgsi2 \n MULTI apply expression %j \n result %j \n payload %j',
|
|
777
|
+
attr.entity_name,
|
|
778
|
+
newEntityName,
|
|
779
|
+
payload
|
|
641
780
|
);
|
|
781
|
+
}
|
|
642
782
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
783
|
+
let newEntity = {
|
|
784
|
+
id: newEntityName ? newEntityName : payload.entities[0].id,
|
|
785
|
+
type: attr.entity_type ? attr.entity_type : payload.entities[0].type
|
|
786
|
+
};
|
|
787
|
+
// Check if there is already a newEntity created
|
|
788
|
+
let alreadyEntity = payload.entities.find((entity) => {
|
|
789
|
+
return entity.id === newEntity.id && entity.type === newEntity.type;
|
|
790
|
+
});
|
|
791
|
+
if (alreadyEntity) {
|
|
792
|
+
// Use alreadyEntity
|
|
793
|
+
alreadyEntity[attr.name] = newAttr;
|
|
647
794
|
} else {
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
795
|
+
// Add newEntity to payload.entities
|
|
796
|
+
newEntity[attr.name] = newAttr;
|
|
797
|
+
if (
|
|
798
|
+
'timestamp' in typeInformation && typeInformation.timestamp !== undefined
|
|
799
|
+
? typeInformation.timestamp
|
|
800
|
+
: config.getConfig().timestamp !== undefined
|
|
801
|
+
? config.getConfig().timestamp
|
|
802
|
+
: timestampValue !== undefined
|
|
803
|
+
) {
|
|
804
|
+
newEntity = addTimestampNgsi2(newEntity, typeInformation.timezone, timestampValue);
|
|
805
|
+
logger.debug(context, 'sendUpdateValueNgsi2 \n timestamped newEntity=%j', newEntity);
|
|
806
|
+
}
|
|
807
|
+
payload.entities.push(newEntity);
|
|
808
|
+
}
|
|
809
|
+
if (attr && attr.name) {
|
|
810
|
+
if (attr.name !== j) {
|
|
811
|
+
logger.debug(
|
|
812
|
+
context,
|
|
813
|
+
'sendUpdateValueNgsi2 \n MULTI remove measure attr %j keep alias j %j from %j \n',
|
|
814
|
+
j,
|
|
815
|
+
attr,
|
|
816
|
+
payload
|
|
817
|
+
);
|
|
818
|
+
delete payload.entities[0][j];
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
// if (attr && (attr.entity_type || attr.entity_name))
|
|
822
|
+
} else {
|
|
823
|
+
// Not a multientity attr
|
|
824
|
+
if (attr && attr.name) {
|
|
825
|
+
payload.entities[0][attr.name] = newAttr;
|
|
826
|
+
if (attr.name !== j) {
|
|
827
|
+
delete payload.entities[0][j]; // keep alias name, remove measure name
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
if (newAttr && newAttr.type === constants.TIMESTAMP_TYPE_NGSI2 && newAttr.value) {
|
|
831
|
+
let extendedTime = compressTimestampPlugin.fromBasicToExtended(newAttr.value);
|
|
832
|
+
if (extendedTime) {
|
|
833
|
+
// TBD: there is not flag about compressTimestamp in iotagent-node-lib,
|
|
834
|
+
// but there is one in agents
|
|
835
|
+
newAttr.value = extendedTime;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
if (j === constants.TIMESTAMP_ATTRIBUTE) {
|
|
839
|
+
if (newAttr && newAttr.type === constants.TIMESTAMP_TYPE_NGSI2 && newAttr.value) {
|
|
840
|
+
timestampValue = newAttr.value;
|
|
841
|
+
logger.debug(
|
|
842
|
+
context,
|
|
843
|
+
'sendUpdateValueNgsi2 \n newAttr is TimeInstant and new payload=%j',
|
|
844
|
+
payload
|
|
845
|
+
);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
if (
|
|
849
|
+
newAttr &&
|
|
850
|
+
newAttr.metadata &&
|
|
851
|
+
newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE] &&
|
|
852
|
+
newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].type === constants.TIMESTAMP_TYPE_NGSI2 &&
|
|
853
|
+
newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].value
|
|
854
|
+
) {
|
|
855
|
+
let extendedTime = compressTimestampPlugin.fromBasicToExtended(
|
|
856
|
+
newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].value
|
|
653
857
|
);
|
|
654
|
-
|
|
858
|
+
if (extendedTime) {
|
|
859
|
+
newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].value = extendedTime;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
} // if (j !== 'id' || j !== 'type')
|
|
864
|
+
|
|
865
|
+
// final attr loop
|
|
866
|
+
logger.debug(
|
|
867
|
+
context,
|
|
868
|
+
'sendUpdateValueNgsi2 \n after procesing attr %j \n current entity %j \n current payload=%j',
|
|
869
|
+
j,
|
|
870
|
+
currentEntity,
|
|
871
|
+
payload
|
|
872
|
+
);
|
|
873
|
+
}
|
|
874
|
+
// for attr loop
|
|
875
|
+
|
|
876
|
+
// Add timestamp to paylaod
|
|
877
|
+
if (
|
|
878
|
+
'timestamp' in typeInformation && typeInformation.timestamp !== undefined
|
|
879
|
+
? typeInformation.timestamp
|
|
880
|
+
: config.getConfig().timestamp !== undefined
|
|
881
|
+
? config.getConfig().timestamp
|
|
882
|
+
: timestampValue !== undefined
|
|
883
|
+
) {
|
|
884
|
+
if (timestampValue) {
|
|
885
|
+
// timeInstant is provided as measure
|
|
886
|
+
if (payload.entities.length > 0) {
|
|
887
|
+
for (let n = 0; n < payload.entities.length; n++) {
|
|
888
|
+
// include metadata with TimeInstant in attrs when TimeInstant is provided as measure in all entities
|
|
889
|
+
payload.entities[n] = addTimestampNgsi2(
|
|
890
|
+
payload.entities[n],
|
|
891
|
+
typeInformation.timezone,
|
|
892
|
+
timestampValue
|
|
893
|
+
);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
} else {
|
|
897
|
+
// jshint maxdepth:5
|
|
898
|
+
for (let n = 0; n < payload.entities.length; n++) {
|
|
899
|
+
if (!utils.isTimestampedNgsi2(payload.entities[n])) {
|
|
900
|
+
// legacy check needed?
|
|
901
|
+
payload.entities[n] = addTimestampNgsi2(payload.entities[n], typeInformation.timezone);
|
|
902
|
+
// jshint maxdepth:5
|
|
903
|
+
} else if (!utils.IsValidTimestampedNgsi2(payload.entities[n])) {
|
|
904
|
+
// legacy check needed?
|
|
905
|
+
logger.error(context, 'Invalid timestamp:%s', JSON.stringify(payload.entities[0]));
|
|
906
|
+
callback(new errors.BadTimestamp(payload.entities));
|
|
907
|
+
return;
|
|
655
908
|
}
|
|
656
909
|
}
|
|
657
910
|
}
|
|
658
|
-
|
|
911
|
+
}
|
|
912
|
+
logger.debug(context, 'sendUpdateValueNgsi2 \n ending payload=%j', payload);
|
|
913
|
+
|
|
914
|
+
for (let m = 0; m < payload.entities.length; m++) {
|
|
915
|
+
for (var key in payload.entities[m]) {
|
|
916
|
+
// purge object_id from payload
|
|
917
|
+
if (payload.entities[m][key] && payload.entities[m][key].object_id) {
|
|
918
|
+
delete payload.entities[m][key].object_id;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
payload.entities[m] = NGSIUtils.castJsonNativeAttributes(payload.entities[m]); // native types
|
|
922
|
+
}
|
|
923
|
+
logger.debug(context, 'sendUpdateValueNgsi2 \n payload with native types and without object_id %j', payload);
|
|
924
|
+
|
|
925
|
+
options.json = payload;
|
|
926
|
+
|
|
927
|
+
// Prevent to update an entity with an empty payload
|
|
928
|
+
if (
|
|
929
|
+
Object.keys(options.json).length > 0 &&
|
|
930
|
+
(options.json.entities.length > 1 ||
|
|
931
|
+
(options.json.entities.length === 1 && Object.keys(options.json.entities[0]).length > 2)) // more than id and type
|
|
932
|
+
) {
|
|
933
|
+
// Final check: (to keep tests unchanged) before do CB requests
|
|
934
|
+
// one entity -> request /v2/entities/ + entityName + /atts ?type=typeInformation.type
|
|
935
|
+
// multi entities -> request /v2/op/update
|
|
936
|
+
// Note that the options object is prepared for the second case (multi entity), so we "patch" it
|
|
937
|
+
// only in the first case
|
|
938
|
+
if (options.json.entities.length === 1) {
|
|
939
|
+
// recreate options object to use single entity update
|
|
940
|
+
url = '/v2/entities/' + entityName + '/attrs';
|
|
941
|
+
if (typeInformation && typeInformation.type) {
|
|
942
|
+
url += '?type=' + typeInformation.type;
|
|
943
|
+
}
|
|
944
|
+
options = NGSIUtils.createRequestObject(url, typeInformation, token);
|
|
945
|
+
options.json = payload.entities[0];
|
|
946
|
+
delete options.json.id;
|
|
947
|
+
delete options.json.type;
|
|
948
|
+
if (config.getConfig().appendMode === true) {
|
|
949
|
+
options.method = 'POST';
|
|
950
|
+
} else {
|
|
951
|
+
options.method = 'PATCH';
|
|
952
|
+
}
|
|
953
|
+
} // else: keep current options object created for a batch update
|
|
954
|
+
logger.debug(context, 'Updating device value in the Context Broker at [%s]', options.url);
|
|
955
|
+
logger.debug(context, 'Using the following NGSI v2 request:\n\n%s\n\n', JSON.stringify(options, null, 4));
|
|
956
|
+
request(
|
|
957
|
+
options,
|
|
958
|
+
generateNGSI2OperationHandler('update', entityName, typeInformation, token, options, callback)
|
|
959
|
+
);
|
|
960
|
+
} else {
|
|
961
|
+
logger.debug(
|
|
962
|
+
context,
|
|
963
|
+
'Not updating device value in the Context Broker at [%s] due to empty payload \n\n[%s]\n\n',
|
|
964
|
+
options.url,
|
|
965
|
+
JSON.stringify(options, null, 4)
|
|
966
|
+
);
|
|
967
|
+
callback(null);
|
|
968
|
+
}
|
|
659
969
|
}
|
|
660
970
|
|
|
661
971
|
exports.sendQueryValue = sendQueryValueNgsi2;
|