iotagent-node-lib 4.4.0 → 4.6.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.
Files changed (100) hide show
  1. package/README.md +67 -272
  2. package/config.js +2 -1
  3. package/doc/README.md +1 -1
  4. package/doc/admin.md +19 -22
  5. package/doc/api.md +621 -155
  6. package/doc/deprecated.md +4 -0
  7. package/doc/devel/architecture.md +5 -135
  8. package/doc/devel/development.md +224 -12
  9. package/doc/getting-started.md +114 -53
  10. package/doc/requirements.txt +1 -1
  11. package/doc/roadmap.md +5 -5
  12. package/docker/Mosquitto/Dockerfile +2 -2
  13. package/docker/Mosquitto/README.md +14 -11
  14. package/lib/commonConfig.js +7 -1
  15. package/lib/constants.js +3 -0
  16. package/lib/fiware-iotagent-lib.js +12 -15
  17. package/lib/jexlTranformsMap.js +3 -1
  18. package/lib/model/dbConn.js +1 -4
  19. package/lib/services/common/alarmManagement.js +3 -0
  20. package/lib/services/devices/deviceRegistryMemory.js +1 -1
  21. package/lib/services/devices/deviceRegistryMongoDB.js +2 -2
  22. package/lib/services/devices/deviceService.js +4 -3
  23. package/lib/services/devices/devices-NGSI-LD.js +5 -5
  24. package/lib/services/devices/devices-NGSI-mixed.js +3 -3
  25. package/lib/services/devices/devices-NGSI-v2.js +5 -5
  26. package/lib/services/groups/groupService.js +1 -1
  27. package/lib/services/ngsi/entities-NGSI-LD.js +321 -571
  28. package/lib/services/ngsi/entities-NGSI-v2.js +348 -281
  29. package/lib/services/ngsi/ngsiService.js +3 -1
  30. package/lib/services/ngsi/subscription-NGSI-LD.js +2 -2
  31. package/lib/services/ngsi/subscription-NGSI-v2.js +2 -2
  32. package/lib/services/northBound/deviceGroupAdministrationServer.js +42 -6
  33. package/lib/services/northBound/deviceProvisioningServer.js +5 -5
  34. package/lib/services/northBound/northboundServer.js +4 -2
  35. package/lib/services/stats/statsRegistry.js +128 -101
  36. package/lib/templates/createDevice.json +0 -24
  37. package/lib/templates/createDeviceLax.json +0 -23
  38. package/lib/templates/deviceGroup.json +1 -25
  39. package/lib/templates/updateDevice.json +0 -24
  40. package/lib/templates/updateDeviceLax.json +0 -23
  41. package/package.json +2 -2
  42. package/scripts/legacy_expression_tool/README.md +0 -1
  43. package/test/functional/README.md +75 -47
  44. package/test/functional/functional-tests-runner.js +9 -4
  45. package/test/functional/functional-tests.js +4 -4
  46. package/test/functional/testCases.js +1251 -257
  47. package/test/functional/testUtils.js +53 -20
  48. package/test/unit/examples/deviceProvisioningRequests/provisionFullDevice.json +1 -13
  49. package/test/unit/examples/deviceProvisioningRequests/updateProvisionDeviceWithApikey.json +4 -0
  50. package/test/unit/examples/groupProvisioningRequests/multipleConfigGroupsCreation.json +44 -0
  51. package/test/unit/examples/groupProvisioningRequests/provisionDuplicateConfigGroup.json +35 -0
  52. package/test/unit/examples/groupProvisioningRequests/provisionFullConfigGroup.json +36 -0
  53. package/test/unit/examples/groupProvisioningRequests/provisionFullConfigGroupAlternate.json +36 -0
  54. package/test/unit/general/deviceService-test.js +102 -0
  55. package/test/unit/general/statistics-service_test.js +1 -74
  56. package/test/unit/mongodb/mongodb-configGroup-registry-test.js +452 -0
  57. package/test/unit/mongodb/mongodb-connectionoptions-test.js +2 -3
  58. package/test/unit/mongodb/mongodb-group-registry-test.js +34 -33
  59. package/test/unit/mongodb/mongodb-service-registry-test.js +477 -0
  60. package/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin1a.json +4 -4
  61. package/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin2.json +22 -22
  62. package/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin29.json +4 -4
  63. package/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin32.json +14 -15
  64. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin1.json +23 -23
  65. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin15.json +0 -5
  66. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin4.json +11 -16
  67. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin5.json +23 -28
  68. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin6.json +8 -13
  69. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin7.json +0 -5
  70. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin8.json +24 -29
  71. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityTimestampPlugin2.json +12 -17
  72. package/test/unit/ngsi-ld/examples/contextRequests/updateContextStaticLinkedAttributes.json +12 -10
  73. package/test/unit/ngsi-ld/expressions/jexlBasedTransformations-test.js +0 -102
  74. package/test/unit/ngsi-ld/plugins/multientity-plugin_test.js +4 -5
  75. package/test/unit/ngsi-ld/provisioning/device-update-registration_test.js +5 -5
  76. package/test/unit/ngsi-ld/provisioning/listProvisionedDevices-test.js +0 -4
  77. package/test/unit/ngsiv2/general/deviceService-test.js +94 -1
  78. package/test/unit/ngsiv2/general/iotam-autoregistration-test.js +195 -0
  79. package/test/unit/ngsiv2/provisioning/device-group-api-test.js +259 -0
  80. package/test/unit/ngsiv2/provisioning/device-provisioning-configGroup-api_test.js +1189 -0
  81. package/test/unit/ngsiv2/provisioning/device-update-registration_test.js +6 -6
  82. package/test/unit/ngsiv2/provisioning/listProvisionedDevices-test.js +0 -4
  83. package/test/unit/ngsiv2/provisioning/updateProvisionedDevices-test.js +35 -0
  84. package/test/unit/statsRegistry/openmetrics-test.js +167 -0
  85. package/lib/templates/queryContext.json +0 -25
  86. package/test/unit/examples/deviceProvisioningRequests/provisionBidirectionalDevice.json +0 -35
  87. package/test/unit/examples/deviceProvisioningRequests/provisionDeviceBidirectionalGroup.json +0 -17
  88. package/test/unit/examples/groupProvisioningRequests/bidirectionalGroup.json +0 -31
  89. package/test/unit/general/statistics-persistence_test.js +0 -121
  90. package/test/unit/ngsi-ld/examples/contextRequests/createBidirectionalDevice.json +0 -17
  91. package/test/unit/ngsi-ld/examples/contextRequests/updateContextProcessTimestamp.json +0 -12
  92. package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotification.json +0 -13
  93. package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotificationWithDatasetId.json +0 -21
  94. package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotificationWithMetadata.json +0 -17
  95. package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalSubscriptionRequest.json +0 -23
  96. package/test/unit/ngsi-ld/plugins/timestamp-processing-plugin_test.js +0 -132
  97. package/test/unit/ngsiv2/examples/contextRequests/createBidirectionalDevice.json +0 -8
  98. package/test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalNotification.json +0 -13
  99. package/test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalNotificationWithMetadata.json +0 -19
  100. package/test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalSubscriptionRequest.json +0 -24
@@ -260,8 +260,8 @@ function sendQueryValueNgsi2(entityName, attributes, typeInformation, token, cal
260
260
  * @param {Object} typeInformation Configuration information for the device.
261
261
  * @param {String} token User token to identify against the PEP Proxies (optional).
262
262
  */
263
- function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, callback) {
264
- //aux function used to builf JEXL context.
263
+ function sendUpdateValueNgsi2(entityName, originMeasures, originTypeInformation, token, callback) {
264
+ //aux functions used to builf JEXL context.
265
265
  //it returns a flat object from an Attr array
266
266
  function reduceAttrToPlainObject(attrs, initObj = {}) {
267
267
  if (attrs !== undefined && Array.isArray(attrs)) {
@@ -273,339 +273,389 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call
273
273
  return initObj;
274
274
  }
275
275
  }
276
-
277
- let entities = {}; //{entityName:{entityType:[attrs]}} //SubGoal Populate entoties data striucture
278
- let jexlctxt = {}; //will store the whole context (not just for JEXL)
279
- let payload = {}; //will store the final payload
280
- let plainMeasures = null; //will contain measures POJO
281
- let idTypeSSSList = pluginUtils.getIdTypeServSubServiceFromDevice(typeInformation);
276
+ //it returns a metadata object using the same structure described
277
+ // at https://github.com/telefonicaid/fiware-orion/blob/master/doc/manuals/orion-api.md#metadata-support
278
+ function reduceMetadataAttrToPlainObject(attrs, initObj = {}) {
279
+ if (attrs !== undefined && Array.isArray(attrs)) {
280
+ return attrs.reduce((result, item) => {
281
+ if (result['metadata'] === undefined) {
282
+ result['metadata'] = {};
283
+ }
284
+ if (item.metadata !== undefined) {
285
+ result['metadata'][item.name] = {};
286
+ for (var meta in item.metadata) {
287
+ result['metadata'][item.name][meta] = item.metadata[meta].value;
288
+ }
289
+ }
290
+ return result;
291
+ }, initObj);
292
+ } else {
293
+ return initObj;
294
+ }
295
+ }
282
296
 
283
297
  //Make a clone and overwrite
284
- typeInformation = JSON.parse(JSON.stringify(typeInformation));
298
+ let idTypeSSSList = pluginUtils.getIdTypeServSubServiceFromDevice(originTypeInformation);
285
299
 
286
300
  //Check mandatory information: type
287
- if (!typeInformation || !typeInformation.type) {
288
- callback(new errors.TypeNotFound(null, entityName, typeInformation));
301
+ if (!originTypeInformation || !originTypeInformation.type) {
302
+ callback(new errors.TypeNotFound(null, entityName, originTypeInformation));
289
303
  return;
290
304
  }
291
- //Rename all measures with matches with id and type to measure_id and measure_type
292
- for (let measure of measures) {
293
- if (measure.name === 'id' || measure.name === 'type') {
294
- measure.name = constants.MEASURE + measure.name;
295
- }
296
- }
297
305
 
298
- //Make a copy of measures in an plain object: plainMeasures
299
- plainMeasures = reduceAttrToPlainObject(measures);
300
-
301
- //Build the initital JEXL Context
302
- //All the measures (avoid references make another copy instead)
303
- jexlctxt = reduceAttrToPlainObject(measures);
304
- //All the static
305
- jexlctxt = reduceAttrToPlainObject(typeInformation.staticAttributes, jexlctxt);
306
- //id type Service and Subservice
307
- jexlctxt = reduceAttrToPlainObject(idTypeSSSList, jexlctxt);
306
+ let payload = {}; //will store the final payload
307
+ let entities = {};
308
+ payload.actionType = 'append';
309
+ payload.entities = [];
308
310
 
311
+ const currentIsoDate = new Date().toISOString();
312
+ const currentMoment = moment(currentIsoDate);
309
313
  //Managing timestamp (mustInsertTimeInstant flag to decide if we should insert Timestamp later on)
310
- const mustInsertTimeInstant = typeInformation.timestamp !== undefined ? typeInformation.timestamp : false;
311
-
312
- logger.debug(
313
- context,
314
- 'sendUpdateValueNgsi2 called with: entityName=%s, measures=%j, typeInformation=%j, initial jexlContext=%j, timestamp=%j',
315
- entityName,
316
- plainMeasures,
317
- typeInformation,
318
- jexlctxt,
319
- mustInsertTimeInstant
320
- );
314
+ const mustInsertTimeInstant =
315
+ originTypeInformation.timestamp !== undefined ? originTypeInformation.timestamp : false;
321
316
 
322
- //Now we can calculate the EntityName of primary entity
323
- let entityNameCalc = null;
324
- if (typeInformation.entityNameExp !== undefined && typeInformation.entityNameExp !== '') {
325
- try {
326
- logger.debug(context, 'sendUpdateValueNgsi2 entityNameExp %j', typeInformation.entityNameExp);
327
- entityNameCalc = expressionPlugin.applyExpression(typeInformation.entityNameExp, jexlctxt, typeInformation);
328
- } catch (e) {
329
- logger.debug(
330
- context,
331
- 'Error evaluating expression for entityName: %j with context: %j',
332
- typeInformation.entityNameExp,
333
- jexlctxt
334
- );
335
- }
317
+ // Check if measures is a single measure or a array of measures (a multimeasure)
318
+ if (originMeasures[0] && !originMeasures[0][0]) {
319
+ originMeasures = [originMeasures];
336
320
  }
321
+ for (let measures of originMeasures) {
322
+ entities = {}; //{entityName:{entityType:[attrs]}} //SubGoal Populate entities data structure
323
+ let jexlctxt = {}; //will store the whole context (not just for JEXL)
324
+
325
+ let plainMeasures = null; //will contain measures POJO
326
+ //Make a clone and overwrite
327
+ let typeInformation = JSON.parse(JSON.stringify(originTypeInformation));
328
+
329
+ //Rename all measures with matches with id and type to measure_id and measure_type
330
+ for (let measure of measures) {
331
+ if (measure.name === 'id' || measure.name === 'type') {
332
+ measure.name = constants.MEASURE + measure.name;
333
+ }
334
+ }
337
335
 
338
- entityName = entityNameCalc ? entityNameCalc : entityName;
339
- //enrich JEXL context
340
- jexlctxt['entity_name'] = entityName;
336
+ //Make a copy of measures in an plain object: plainMeasures
337
+ plainMeasures = reduceAttrToPlainObject(measures);
338
+ //Build the initital JEXL Context
339
+ //All the measures (avoid references make another copy instead)
340
+ jexlctxt = reduceAttrToPlainObject(measures);
341
+ //All the static
342
+ jexlctxt = reduceAttrToPlainObject(typeInformation.staticAttributes, jexlctxt);
343
+ //id type Service and Subservice
344
+ jexlctxt = reduceAttrToPlainObject(idTypeSSSList, jexlctxt);
345
+ //metadata attributes
346
+ jexlctxt = reduceMetadataAttrToPlainObject(typeInformation.active, jexlctxt);
347
+ //metadata static attributes
348
+ jexlctxt = reduceMetadataAttrToPlainObject(typeInformation.staticAttributes, jexlctxt);
341
349
 
342
- let preprocessedAttr = [];
343
- //Add Raw Static, Lazy, Command and Actives attr attributes
344
- if (typeInformation && typeInformation.staticAttributes) {
345
- preprocessedAttr = preprocessedAttr.concat(typeInformation.staticAttributes);
346
- }
347
- if (typeInformation && typeInformation.lazy) {
348
- preprocessedAttr = preprocessedAttr.concat(typeInformation.lazy);
349
- }
350
- if (typeInformation && typeInformation.active) {
351
- preprocessedAttr = preprocessedAttr.concat(typeInformation.active);
352
- }
350
+ logger.debug(
351
+ context,
352
+ 'sendUpdateValueNgsi2 loop with: entityName=%s, measures=%j, typeInformation=%j, initial jexlContext=%j, timestamp=%j',
353
+ entityName,
354
+ plainMeasures,
355
+ typeInformation,
356
+ jexlctxt,
357
+ mustInsertTimeInstant
358
+ );
353
359
 
354
- //Proccess every proto Attribute to populate entities data steuture
355
- entities[entityName] = {};
356
- entities[entityName][typeInformation.type] = [];
357
-
358
- for (let currentAttr of preprocessedAttr) {
359
- let hitted = false; //any measure, expressiom or value hit the attr (avoid propagate "silent attr" with null values )
360
- let attrEntityName = entityName;
361
- let attrEntityType = typeInformation.type;
362
- let valueExpression = null;
363
- //manage active attr without object__id (name by default)
364
- currentAttr.object_id = currentAttr.object_id ? currentAttr.object_id : currentAttr.name;
365
- //Enrich the attr (skip, hit, value, meta-timeInstant)
366
- currentAttr.skipValue = currentAttr.skipValue ? currentAttr.skipValue : null;
367
-
368
- //determine AttrEntityName for multientity
369
- if (
370
- currentAttr.entity_name !== null &&
371
- currentAttr.entity_name !== undefined &&
372
- currentAttr.entity_name !== '' &&
373
- typeof currentAttr.entity_name == 'string'
374
- ) {
360
+ //Now we can calculate the EntityName of primary entity
361
+ let entityNameCalc = null;
362
+ if (typeInformation.entityNameExp !== undefined && typeInformation.entityNameExp !== '') {
375
363
  try {
376
- logger.debug(
377
- context,
378
- 'Evaluating attribute: %j, for entity_name(exp):%j, with ctxt: %j',
379
- currentAttr.name,
380
- currentAttr.entity_name,
381
- jexlctxt
364
+ logger.debug(context, 'sendUpdateValueNgsi2 entityNameExp %j', typeInformation.entityNameExp);
365
+ entityNameCalc = expressionPlugin.applyExpression(
366
+ typeInformation.entityNameExp,
367
+ jexlctxt,
368
+ typeInformation
382
369
  );
383
- attrEntityName = jexlParser.applyExpression(currentAttr.entity_name, jexlctxt, typeInformation);
384
- if (!attrEntityName) {
385
- attrEntityName = currentAttr.entity_name;
386
- }
387
370
  } catch (e) {
388
371
  logger.debug(
389
372
  context,
390
- 'Exception evaluating entityNameExp:%j, with jexlctxt: %j',
391
- currentAttr.entity_name,
373
+ 'Error evaluating expression for entityName: %j with context: %j',
374
+ typeInformation.entityNameExp,
392
375
  jexlctxt
393
376
  );
394
- attrEntityName = currentAttr.entity_name;
395
377
  }
396
378
  }
397
379
 
398
- //determine AttrEntityType for multientity
399
- if (
400
- currentAttr.entity_type !== null &&
401
- currentAttr.entity_type !== undefined &&
402
- currentAttr.entity_type !== '' &&
403
- typeof currentAttr.entity_type === 'string'
404
- ) {
405
- attrEntityType = currentAttr.entity_type;
406
- }
380
+ entityName = entityNameCalc ? entityNameCalc : entityName;
381
+ //enrich JEXL context
382
+ jexlctxt['entity_name'] = entityName;
407
383
 
408
- //PRE POPULATE CONTEXT
409
- jexlctxt[currentAttr.name] = plainMeasures[currentAttr.object_id];
410
-
411
- //determine Value
412
- if (currentAttr.value !== undefined) {
413
- //static attributes already have a value
414
- hitted = true;
415
- valueExpression = currentAttr.value;
416
- } else if (plainMeasures[currentAttr.object_id] !== undefined) {
417
- //we have got a meaure for that Attr
418
- //actives ¿lazis?
419
- hitted = true;
420
- valueExpression = plainMeasures[currentAttr.object_id];
384
+ let preprocessedAttr = [];
385
+ //Add Raw Static, Lazy, Command and Actives attr attributes
386
+ if (typeInformation && typeInformation.staticAttributes) {
387
+ preprocessedAttr = preprocessedAttr.concat(typeInformation.staticAttributes);
421
388
  }
422
- //remove measures that has been shadowed by an alias (some may be left and managed later)
423
- //Maybe we must filter object_id if there is name == object_id
424
- measures = measures.filter((item) => item.name !== currentAttr.object_id && item.name !== currentAttr.name);
425
-
426
- if (
427
- currentAttr.expression !== undefined &&
428
- currentAttr.expression !== '' &&
429
- typeof currentAttr.expression == 'string'
430
- ) {
431
- try {
389
+ if (typeInformation && typeInformation.lazy) {
390
+ preprocessedAttr = preprocessedAttr.concat(typeInformation.lazy);
391
+ }
392
+ if (typeInformation && typeInformation.active) {
393
+ preprocessedAttr = preprocessedAttr.concat(typeInformation.active);
394
+ }
395
+
396
+ //Proccess every proto Attribute to populate entities data steuture
397
+ entities[entityName] = {};
398
+ entities[entityName][typeInformation.type] = [];
399
+
400
+ for (let currentAttr of preprocessedAttr) {
401
+ let hitted = false; //any measure, expressiom or value hit the attr (avoid propagate "silent attr" with null values )
402
+ let attrEntityName = entityName;
403
+ let attrEntityType = typeInformation.type;
404
+ let valueExpression = null;
405
+ //manage active attr without object__id (name by default)
406
+ currentAttr.object_id = currentAttr.object_id ? currentAttr.object_id : currentAttr.name;
407
+ //Enrich the attr (skip, hit, value, meta-timeInstant)
408
+ currentAttr.skipValue = currentAttr.skipValue ? currentAttr.skipValue : null;
409
+
410
+ //determine AttrEntityName for multientity
411
+ if (
412
+ currentAttr.entity_name !== null &&
413
+ currentAttr.entity_name !== undefined &&
414
+ currentAttr.entity_name !== '' &&
415
+ typeof currentAttr.entity_name == 'string'
416
+ ) {
417
+ try {
418
+ logger.debug(
419
+ context,
420
+ 'Evaluating attribute: %j, for entity_name(exp):%j, with ctxt: %j',
421
+ currentAttr.name,
422
+ currentAttr.entity_name,
423
+ jexlctxt
424
+ );
425
+ attrEntityName = jexlParser.applyExpression(currentAttr.entity_name, jexlctxt, typeInformation);
426
+ if (!attrEntityName) {
427
+ attrEntityName = currentAttr.entity_name;
428
+ }
429
+ } catch (e) {
430
+ logger.debug(
431
+ context,
432
+ 'Exception evaluating entityNameExp:%j, with jexlctxt: %j',
433
+ currentAttr.entity_name,
434
+ jexlctxt
435
+ );
436
+ attrEntityName = currentAttr.entity_name;
437
+ }
438
+ }
439
+
440
+ //determine AttrEntityType for multientity
441
+ if (
442
+ currentAttr.entity_type !== null &&
443
+ currentAttr.entity_type !== undefined &&
444
+ currentAttr.entity_type !== '' &&
445
+ typeof currentAttr.entity_type === 'string'
446
+ ) {
447
+ attrEntityType = currentAttr.entity_type;
448
+ }
449
+
450
+ //PRE POPULATE CONTEXT
451
+ jexlctxt[currentAttr.name] = plainMeasures[currentAttr.object_id];
452
+
453
+ //determine Value
454
+ if (currentAttr.value !== undefined) {
455
+ //static attributes already have a value
456
+ hitted = true;
457
+ valueExpression = currentAttr.value;
458
+ } else if (plainMeasures[currentAttr.object_id] !== undefined) {
459
+ //we have got a meaure for that Attr
460
+ //actives ¿lazis?
432
461
  hitted = true;
433
- valueExpression = jexlParser.applyExpression(currentAttr.expression, jexlctxt, typeInformation);
434
- //we fallback to null if anything unexpecte happend
435
- if (valueExpression === null || valueExpression === undefined || Number.isNaN(valueExpression)) {
462
+ valueExpression = plainMeasures[currentAttr.object_id];
463
+ }
464
+ //remove measures that has been shadowed by an alias (some may be left and managed later)
465
+ //Maybe we must filter object_id if there is name == object_id
466
+ measures = measures.filter((item) => item.name !== currentAttr.object_id && item.name !== currentAttr.name);
467
+
468
+ if (
469
+ currentAttr.expression !== undefined &&
470
+ currentAttr.expression !== '' &&
471
+ typeof currentAttr.expression == 'string'
472
+ ) {
473
+ try {
474
+ hitted = true;
475
+ valueExpression = jexlParser.applyExpression(currentAttr.expression, jexlctxt, typeInformation);
476
+ //we fallback to null if anything unexpecte happend
477
+ if (valueExpression === null || valueExpression === undefined || Number.isNaN(valueExpression)) {
478
+ valueExpression = null;
479
+ }
480
+ } catch (e) {
436
481
  valueExpression = null;
437
482
  }
438
- } catch (e) {
439
- valueExpression = null;
483
+ logger.debug(
484
+ context,
485
+ 'Evaluated attr: %j, with expression: %j, and ctxt: %j resulting: %j',
486
+ currentAttr.name,
487
+ currentAttr.expression,
488
+ jexlctxt,
489
+ valueExpression
490
+ );
440
491
  }
441
- logger.debug(
442
- context,
443
- 'Evaluated attr: %j, with expression: %j, and ctxt: %j resulting: %j',
444
- currentAttr.name,
445
- currentAttr.expression,
446
- jexlctxt,
447
- valueExpression
448
- );
449
- }
450
492
 
451
- currentAttr.hitted = hitted;
452
- currentAttr.value = valueExpression;
493
+ currentAttr.hitted = hitted;
494
+ currentAttr.value = valueExpression;
453
495
 
454
- //store de New Attributte in entity data structure
455
- if (hitted === true) {
456
- if (entities[attrEntityName] === undefined) {
457
- entities[attrEntityName] = {};
458
- }
459
- if (entities[attrEntityName][attrEntityType] === undefined) {
460
- entities[attrEntityName][attrEntityType] = [];
496
+ //store de New Attributte in entity data structure
497
+ if (hitted === true) {
498
+ if (entities[attrEntityName] === undefined) {
499
+ entities[attrEntityName] = {};
500
+ }
501
+ if (entities[attrEntityName][attrEntityType] === undefined) {
502
+ entities[attrEntityName][attrEntityType] = [];
503
+ }
504
+ //store de New Attributte
505
+ entities[attrEntityName][attrEntityType].push(currentAttr);
461
506
  }
462
- //store de New Attributte
463
- entities[attrEntityName][attrEntityType].push(currentAttr);
464
- }
465
507
 
466
- //RE-Populate de JEXLcontext (except for null or NaN we preffer undefined)
467
- jexlctxt[currentAttr.name] = valueExpression;
508
+ //RE-Populate de JEXLcontext (except for null or NaN we preffer undefined)
509
+ jexlctxt[currentAttr.name] = valueExpression;
468
510
 
469
- // Expand metadata value expression
470
- if (currentAttr.metadata) {
471
- for (var metaKey in currentAttr.metadata) {
472
- if (currentAttr.metadata[metaKey].expression && metaKey !== constants.TIMESTAMP_ATTRIBUTE) {
473
- let newAttrMeta = {};
474
- if (currentAttr.metadata[metaKey].type) {
475
- newAttrMeta['type'] = currentAttr.metadata[metaKey].type;
476
- }
477
- let metaValueExpression;
478
- try {
479
- metaValueExpression = jexlParser.applyExpression(
480
- currentAttr.metadata[metaKey].expression,
481
- jexlctxt,
482
- typeInformation
483
- );
484
- //we fallback to null if anything unexpecte happend
485
- if (
486
- metaValueExpression === null ||
487
- metaValueExpression === undefined ||
488
- Number.isNaN(metaValueExpression)
489
- ) {
511
+ // Expand metadata value expression
512
+ if (currentAttr.metadata) {
513
+ for (var metaKey in currentAttr.metadata) {
514
+ if (currentAttr.metadata[metaKey].expression && metaKey !== constants.TIMESTAMP_ATTRIBUTE) {
515
+ let newAttrMeta = {};
516
+ if (currentAttr.metadata[metaKey].type) {
517
+ newAttrMeta['type'] = currentAttr.metadata[metaKey].type;
518
+ }
519
+ let metaValueExpression;
520
+ try {
521
+ metaValueExpression = jexlParser.applyExpression(
522
+ currentAttr.metadata[metaKey].expression,
523
+ jexlctxt,
524
+ typeInformation
525
+ );
526
+ //we fallback to null if anything unexpecte happend
527
+ if (
528
+ metaValueExpression === null ||
529
+ metaValueExpression === undefined ||
530
+ Number.isNaN(metaValueExpression)
531
+ ) {
532
+ metaValueExpression = null;
533
+ }
534
+ } catch (e) {
490
535
  metaValueExpression = null;
491
536
  }
492
- } catch (e) {
493
- metaValueExpression = null;
537
+ newAttrMeta['value'] = metaValueExpression;
538
+ currentAttr.metadata[metaKey] = newAttrMeta;
539
+
540
+ //RE-Populate de JEXLcontext
541
+ // It is possible metadata is still not in ctxt
542
+ if (!jexlctxt.metadata) {
543
+ jexlctxt.metadata = {};
544
+ }
545
+ if (!jexlctxt.metadata[currentAttr.name]) {
546
+ jexlctxt.metadata[currentAttr.name] = {};
547
+ }
548
+ jexlctxt.metadata[currentAttr.name][currentAttr.metadata[metaKey]] = metaValueExpression;
494
549
  }
495
- newAttrMeta['value'] = metaValueExpression;
496
- currentAttr.metadata[metaKey] = newAttrMeta;
497
550
  }
498
551
  }
499
552
  }
500
- }
501
553
 
502
- //now we can compute explicit (Bool or Array) with the complete JexlContext
503
- let explicit = false;
504
- if (typeof typeInformation.explicitAttrs === 'string') {
505
- try {
506
- explicit = jexlParser.applyExpression(typeInformation.explicitAttrs, jexlctxt, typeInformation);
507
- if (explicit instanceof Array && mustInsertTimeInstant) {
508
- explicit.push(constants.TIMESTAMP_ATTRIBUTE);
554
+ //now we can compute explicit (Bool or Array) with the complete JexlContext
555
+ let explicit = false;
556
+ if (typeof typeInformation.explicitAttrs === 'string') {
557
+ try {
558
+ explicit = jexlParser.applyExpression(typeInformation.explicitAttrs, jexlctxt, typeInformation);
559
+ if (explicit instanceof Array && explicit.length > 0 && mustInsertTimeInstant) {
560
+ explicit.push(constants.TIMESTAMP_ATTRIBUTE);
561
+ }
562
+ logger.debug(
563
+ context,
564
+ 'Calculated explicitAttrs with expression: %j and ctxt: %j resulting: %j',
565
+ typeInformation.explicitAttrs,
566
+ jexlctxt,
567
+ explicit
568
+ );
569
+ } catch (e) {
570
+ // nothing to do: exception is already logged at info level
509
571
  }
510
- logger.debug(
511
- context,
512
- 'Calculated explicitAttrs with expression: %j and ctxt: %j resulting: %j',
513
- typeInformation.explicitAttrs,
514
- jexlctxt,
515
- explicit
516
- );
517
- } catch (e) {
518
- // nothing to do: exception is already logged at info level
572
+ } else if (typeof typeInformation.explicitAttrs == 'boolean') {
573
+ explicit = typeInformation.explicitAttrs;
519
574
  }
520
- } else if (typeof typeInformation.explicitAttrs == 'boolean') {
521
- explicit = typeInformation.explicitAttrs;
522
- }
523
-
524
- //more mesures may be added to the attribute list (unnhandled/left mesaures) l
525
- if (explicit === false && Object.keys(measures).length > 0) {
526
- entities[entityName][typeInformation.type] = entities[entityName][typeInformation.type].concat(measures);
527
- }
528
575
 
529
- //PRE-PROCESSING FINISHED
530
- //Explicit ATTRS and SKIPVALUES will be managed while we build NGSI payload
531
-
532
- //Get ready to build and send NGSI payload (entities-->payload)
533
- payload.actionType = 'append';
576
+ //more mesures may be added to the attribute list (unnhandled/left mesaures) l
577
+ if (explicit === false && Object.keys(measures).length > 0) {
578
+ entities[entityName][typeInformation.type] = entities[entityName][typeInformation.type].concat(measures);
579
+ }
534
580
 
535
- payload.entities = [];
536
- const currentIsoDate = new Date().toISOString();
537
- const currentMoment = moment(currentIsoDate);
538
- for (let ename in entities) {
539
- for (let etype in entities[ename]) {
540
- let e = {};
541
- e.id = String(ename);
542
- e.type = String(etype);
543
- let timestamp = { type: constants.TIMESTAMP_TYPE_NGSI2 }; //timestamp scafold-attr for insertions.
544
- let timestampAttrs = null;
545
- if (mustInsertTimeInstant) {
546
- // get timestamp for current entity
547
-
548
- timestampAttrs = entities[ename][etype].filter((item) => item.name === constants.TIMESTAMP_ATTRIBUTE);
549
- if (timestampAttrs && timestampAttrs.length > 0) {
550
- timestamp.value = timestampAttrs[0]['value'];
551
- }
581
+ //PRE-PROCESSING FINISHED
582
+ //Explicit ATTRS and SKIPVALUES will be managed while we build NGSI payload
583
+ //Get ready to build and send NGSI payload (entities-->payload)
584
+
585
+ for (let ename in entities) {
586
+ for (let etype in entities[ename]) {
587
+ let e = {};
588
+ e.id = String(ename);
589
+ e.type = String(etype);
590
+ let timestamp = { type: constants.TIMESTAMP_TYPE_NGSI2 }; //timestamp scafold-attr for insertions.
591
+ let timestampAttrs = null;
592
+ if (mustInsertTimeInstant) {
593
+ // get timestamp for current entity
552
594
 
553
- if (timestamp.value) {
554
- if (!moment(timestamp.value, moment.ISO_8601, true).isValid()) {
555
- callback(new errors.BadTimestamp(timestamp.value, entityName, typeInformation));
556
- return;
595
+ timestampAttrs = entities[ename][etype].filter(
596
+ (item) => item.name === constants.TIMESTAMP_ATTRIBUTE
597
+ );
598
+ if (timestampAttrs && timestampAttrs.length > 0) {
599
+ timestamp.value = timestampAttrs[0]['value'];
557
600
  }
558
- } else {
559
- if (!typeInformation.timezone) {
560
- timestamp.value = currentIsoDate;
561
- jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value;
601
+
602
+ if (timestamp.value) {
603
+ if (!moment(timestamp.value, moment.ISO_8601, true).isValid()) {
604
+ callback(new errors.BadTimestamp(timestamp.value, entityName, typeInformation));
605
+ return;
606
+ }
562
607
  } else {
563
- timestamp.value = currentMoment
564
- .tz(typeInformation.timezone)
565
- .format('YYYY-MM-DD[T]HH:mm:ss.SSSZ');
566
- jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value;
608
+ if (!typeInformation.timezone) {
609
+ timestamp.value = currentIsoDate;
610
+ jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value;
611
+ } else {
612
+ timestamp.value = currentMoment
613
+ .tz(typeInformation.timezone)
614
+ .format('YYYY-MM-DD[T]HH:mm:ss.SSSZ');
615
+ jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value;
616
+ }
567
617
  }
568
618
  }
569
- }
570
- //extract attributes
571
- let isEmpty = true;
572
- for (let attr of entities[ename][etype]) {
573
- if (
574
- attr.name !== 'id' &&
575
- attr.name !== 'type' &&
576
- (attr.value !== attr.skipValue || attr.skipValue === undefined) &&
577
- (attr.hitted || attr.hitted === undefined) && //undefined is for pure measures
578
- (typeof explicit === 'boolean' || //true and false already handled
579
- (explicit instanceof Array && //check the array version
580
- (explicit.includes(attr.name) ||
581
- explicit.some(
582
- (item) => attr.object_id !== undefined && item.object_id === attr.object_id
583
- ))))
584
- ) {
585
- isEmpty = false;
586
- if (mustInsertTimeInstant) {
587
- // Add TimeInstant to all attribute metadata of all entities
588
- if (attr.name !== constants.TIMESTAMP_ATTRIBUTE) {
589
- if (!attr.metadata) {
590
- attr.metadata = {};
619
+ //extract attributes
620
+ let isEmpty = true;
621
+ for (let attr of entities[ename][etype]) {
622
+ if (
623
+ attr.name !== 'id' &&
624
+ attr.name !== 'type' &&
625
+ (attr.value !== attr.skipValue || attr.skipValue === undefined) &&
626
+ (attr.hitted || attr.hitted === undefined) && //undefined is for pure measures
627
+ (typeof explicit === 'boolean' || //true and false already handled
628
+ (explicit instanceof Array && //check the array version
629
+ (explicit.includes(attr.name) ||
630
+ explicit.some(
631
+ (item) => attr.object_id !== undefined && item.object_id === attr.object_id
632
+ ))))
633
+ ) {
634
+ isEmpty = false;
635
+ if (mustInsertTimeInstant) {
636
+ // Add TimeInstant to all attribute metadata of all entities
637
+ if (attr.name !== constants.TIMESTAMP_ATTRIBUTE) {
638
+ if (!attr.metadata) {
639
+ attr.metadata = {};
640
+ }
641
+ attr.metadata[constants.TIMESTAMP_ATTRIBUTE] = timestamp;
591
642
  }
592
- attr.metadata[constants.TIMESTAMP_ATTRIBUTE] = timestamp;
593
643
  }
644
+ e[attr.name] = { type: attr.type, value: attr.value, metadata: attr.metadata };
594
645
  }
595
- e[attr.name] = { type: attr.type, value: attr.value, metadata: attr.metadata };
596
646
  }
597
- }
598
- if (!isEmpty) {
599
- if (mustInsertTimeInstant) {
600
- e[constants.TIMESTAMP_ATTRIBUTE] = timestamp;
647
+ if (!isEmpty) {
648
+ if (mustInsertTimeInstant) {
649
+ e[constants.TIMESTAMP_ATTRIBUTE] = timestamp;
650
+ }
651
+ payload.entities.push(e);
601
652
  }
602
- payload.entities.push(e);
603
653
  }
604
654
  }
605
- }
655
+ } // end for (let measures of originMeasures)
606
656
 
607
657
  let url = '/v2/op/update';
608
- let options = NGSIUtils.createRequestObject(url, typeInformation, token);
658
+ let options = NGSIUtils.createRequestObject(url, originTypeInformation, token);
609
659
  options.json = payload;
610
660
 
611
661
  // Prevent to update an entity with an empty payload: more than id and type
@@ -620,13 +670,17 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call
620
670
  // Note that the options object is prepared for the second case (multi entity), so we "patch" it
621
671
  // only in the first case
622
672
 
623
- //Multientity more than one name o more than one type at primary entity
624
- let multientity = Object.keys(entities).length > 1 || Object.keys(entities[entityName]).length > 1;
673
+ //Multi: multientity (more than one name o more than one type at primary entity)
674
+ // of multimeasure (originMeasures is an array of more than one element)
675
+ let multi =
676
+ Object.keys(entities).length > 1 ||
677
+ Object.keys(entities[entityName]).length > 1 ||
678
+ originMeasures.length > 1;
625
679
 
626
- if (!multientity) {
680
+ if (!multi) {
627
681
  // recreate options object to use single entity update
628
682
  url = '/v2/entities?options=upsert';
629
- options = NGSIUtils.createRequestObject(url, typeInformation, token);
683
+ options = NGSIUtils.createRequestObject(url, originTypeInformation, token);
630
684
  delete payload.actionType;
631
685
 
632
686
  let entityAttrs = payload.entities[0];
@@ -643,7 +697,20 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call
643
697
  transformedObject.type = entityAttrs.type;
644
698
  options.json = transformedObject;
645
699
  options.method = 'POST';
646
- } // else: keep current options object created for a batch update
700
+ } else if (payload.entities.every((entity) => 'TimeInstant' in entity)) {
701
+ // Try sort entities by TimeInstant
702
+ payload.entities.sort(
703
+ (a, b) => new Date(a.TimeInstant.value).getTime() - new Date(b.TimeInstant.value).getTime()
704
+ );
705
+ options.json = payload;
706
+ } else {
707
+ // keep current options object created for a batch update
708
+ logger.debug(
709
+ context,
710
+ "some entities lack the 'TimeInstant' key. Sorting is not feasible: %j ",
711
+ payload.entities
712
+ );
713
+ }
647
714
 
648
715
  //Send the NGSI request
649
716
  logger.debug(context, 'Updating device value in the Context Broker at: %j', options.url);
@@ -651,7 +718,7 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call
651
718
 
652
719
  request(
653
720
  options,
654
- generateNGSI2OperationHandler('update', entityName, typeInformation, token, options, callback)
721
+ generateNGSI2OperationHandler('update', entityName, originTypeInformation, token, options, callback)
655
722
  );
656
723
  } else {
657
724
  logger.debug(