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.
Files changed (130) hide show
  1. package/.github/workflows/ci.yml +1 -0
  2. package/CHANGES_NEXT_RELEASE +2 -0
  3. package/docker-compose-dev.yml +1 -1
  4. package/lib/fiware-iotagent-lib.js +0 -5
  5. package/lib/plugins/compressTimestamp.js +2 -7
  6. package/lib/plugins/expressionParser.js +0 -47
  7. package/lib/plugins/expressionPlugin.js +6 -78
  8. package/lib/plugins/jexlParser.js +0 -36
  9. package/lib/plugins/pluginUtils.js +0 -119
  10. package/lib/services/ngsi/entities-NGSI-LD.js +586 -199
  11. package/lib/services/ngsi/entities-NGSI-v2.js +607 -297
  12. package/lib/services/ngsi/ngsiUtils.js +15 -22
  13. package/package.json +2 -2
  14. package/test/unit/general/config-multi-core-test.js +2 -1
  15. package/test/unit/general/contextBrokerKeystoneSecurityAccess-test.js +2 -1
  16. package/test/unit/general/deviceService-test.js +2 -1
  17. package/test/unit/general/loglevel-api_test.js +2 -3
  18. package/test/unit/general/startup-test.js +2 -1
  19. package/test/unit/general/statistics-persistence_test.js +1 -0
  20. package/test/unit/general/statistics-service_test.js +1 -0
  21. package/test/unit/lazyAndCommands/commandRegistry_test.js +1 -0
  22. package/test/unit/memoryRegistry/deviceRegistryMemory_test.js +1 -0
  23. package/test/unit/mongodb/mongodb-connectionoptions-test.js +1 -0
  24. package/test/unit/mongodb/mongodb-group-registry-test.js +1 -0
  25. package/test/unit/mongodb/mongodb-registry-test.js +2 -1
  26. package/test/unit/ngsi-ld/examples/contextRequests/updateContextAliasPlugin1.json +0 -5
  27. package/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast10.json +1 -1
  28. package/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast8.json +1 -1
  29. package/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast9.json +1 -1
  30. package/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin32.json +16 -15
  31. package/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin4.json +8 -8
  32. package/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin4a.json +34 -34
  33. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin5.json +8 -8
  34. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin6.json +0 -20
  35. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin7.json +0 -5
  36. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin8.json +10 -10
  37. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityTimestampPlugin1.json +8 -7
  38. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityTimestampPlugin2.json +5 -4
  39. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityTimestampPlugin3.json +1 -1
  40. package/test/unit/ngsi-ld/examples/contextRequests/updateContextProcessTimestamp.json +4 -4
  41. package/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestampOverride.json +4 -3
  42. package/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestampOverrideWithoutMilis.json +4 -3
  43. package/test/unit/ngsi-ld/expressions/expressionBasedTransformations-test.js +4 -6
  44. package/test/unit/ngsi-ld/expressions/jexlBasedTransformations-test.js +1 -7
  45. package/test/unit/ngsi-ld/general/config-jsonld-contexts-test.js +2 -1
  46. package/test/unit/ngsi-ld/general/contextBrokerOAuthSecurityAccess-test.js +2 -1
  47. package/test/unit/ngsi-ld/general/deviceService-test.js +2 -1
  48. package/test/unit/ngsi-ld/general/https-support-test.js +2 -1
  49. package/test/unit/ngsi-ld/general/iotam-autoregistration-test.js +2 -1
  50. package/test/unit/ngsi-ld/general/startup-test.js +3 -2
  51. package/test/unit/ngsi-ld/lazyAndCommands/active-devices-attribute-update-test.js +2 -1
  52. package/test/unit/ngsi-ld/lazyAndCommands/command-test.js +5 -7
  53. package/test/unit/ngsi-ld/lazyAndCommands/lazy-devices-test.js +8 -6
  54. package/test/unit/ngsi-ld/lazyAndCommands/merge-patch-test.js +18 -22
  55. package/test/unit/ngsi-ld/lazyAndCommands/polling-commands-test.js +2 -1
  56. package/test/unit/ngsi-ld/ngsiService/active-devices-test.js +2 -1
  57. package/test/unit/ngsi-ld/ngsiService/autocast-test.js +17 -4
  58. package/test/unit/ngsi-ld/ngsiService/geoproperties-test.js +2 -1
  59. package/test/unit/ngsi-ld/ngsiService/languageProperties-test.js +32 -34
  60. package/test/unit/ngsi-ld/ngsiService/staticAttributes-test.js +2 -1
  61. package/test/unit/ngsi-ld/ngsiService/subscriptions-test.js +2 -1
  62. package/test/unit/ngsi-ld/ngsiService/unsupported-endpoints-test.js +11 -15
  63. package/test/unit/ngsi-ld/plugins/alias-plugin_test.js +2 -3
  64. package/test/unit/ngsi-ld/plugins/bidirectional-plugin_test.js +2 -1
  65. package/test/unit/ngsi-ld/plugins/compress-timestamp-plugin_test.js +2 -32
  66. package/test/unit/ngsi-ld/plugins/multientity-plugin_test.js +20 -19
  67. package/test/unit/ngsi-ld/plugins/timestamp-processing-plugin_test.js +21 -7
  68. package/test/unit/ngsi-ld/provisioning/device-provisioning-api_test.js +1 -0
  69. package/test/unit/ngsi-ld/provisioning/device-registration_test.js +2 -1
  70. package/test/unit/ngsi-ld/provisioning/device-update-registration_test.js +15 -12
  71. package/test/unit/ngsi-ld/provisioning/listProvisionedDevices-test.js +1 -0
  72. package/test/unit/ngsi-ld/provisioning/provisionDeviceMultientity-test.js +1 -0
  73. package/test/unit/ngsi-ld/provisioning/removeProvisionedDevice-test.js +1 -0
  74. package/test/unit/ngsi-ld/provisioning/singleConfigurationMode-test.js +1 -0
  75. package/test/unit/ngsi-ld/provisioning/updateProvisionedDevices-test.js +1 -0
  76. package/test/unit/ngsi-mixed/provisioning/ngsi-versioning-test.js +13 -7
  77. package/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin1.json +2 -2
  78. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin32.json +6 -0
  79. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin11.json +5 -1
  80. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin5.json +4 -4
  81. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin6.json +0 -16
  82. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin7.json +0 -4
  83. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin8.json +8 -8
  84. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin1.json +42 -42
  85. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin2.json +7 -7
  86. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin4.json +0 -5
  87. package/test/unit/ngsiv2/examples/contextRequests/updateContextProcessTimestamp.json +6 -6
  88. package/test/unit/ngsiv2/examples/contextRequests/updateContextTimestampOverride.json +7 -1
  89. package/test/unit/ngsiv2/examples/contextRequests/updateContextTimestampOverrideWithoutMilis.json +7 -1
  90. package/test/unit/ngsiv2/expressions/expressionBasedTransformations-test.js +2 -4
  91. package/test/unit/ngsiv2/expressions/expressionCombinedTransformations-test.js +4 -8
  92. package/test/unit/ngsiv2/expressions/jexlBasedTransformations-test.js +5 -9
  93. package/test/unit/ngsiv2/general/contextBrokerOAuthSecurityAccess-test.js +2 -1
  94. package/test/unit/ngsiv2/general/deviceService-test.js +2 -1
  95. package/test/unit/ngsiv2/general/https-support-test.js +2 -1
  96. package/test/unit/ngsiv2/general/iotam-autoregistration-test.js +2 -1
  97. package/test/unit/ngsiv2/general/startup-test.js +2 -1
  98. package/test/unit/ngsiv2/lazyAndCommands/active-devices-attribute-update-test.js +2 -1
  99. package/test/unit/ngsiv2/lazyAndCommands/command-test.js +2 -1
  100. package/test/unit/ngsiv2/lazyAndCommands/lazy-devices-test.js +8 -6
  101. package/test/unit/ngsiv2/lazyAndCommands/polling-commands-test.js +2 -1
  102. package/test/unit/ngsiv2/ngsiService/active-devices-test.js +3 -2
  103. package/test/unit/ngsiv2/ngsiService/autocast-test.js +2 -1
  104. package/test/unit/ngsiv2/ngsiService/queryDeviceInformationInCb-test.js +2 -1
  105. package/test/unit/ngsiv2/ngsiService/staticAttributes-test.js +2 -1
  106. package/test/unit/ngsiv2/ngsiService/subscriptions-test.js +2 -1
  107. package/test/unit/ngsiv2/plugins/alias-plugin_test.js +2 -3
  108. package/test/unit/ngsiv2/plugins/bidirectional-plugin_test.js +2 -5
  109. package/test/unit/ngsiv2/plugins/compress-timestamp-plugin_test.js +2 -33
  110. package/test/unit/ngsiv2/plugins/multientity-plugin_test.js +7 -16
  111. package/test/unit/ngsiv2/plugins/timestamp-processing-plugin_test.js +3 -2
  112. package/test/unit/ngsiv2/provisioning/device-group-api-test.js +1 -0
  113. package/test/unit/ngsiv2/provisioning/device-group-utils-test.js +1 -0
  114. package/test/unit/ngsiv2/provisioning/device-provisioning-api_test.js +1 -0
  115. package/test/unit/ngsiv2/provisioning/device-registration_test.js +2 -1
  116. package/test/unit/ngsiv2/provisioning/device-update-registration_test.js +15 -12
  117. package/test/unit/ngsiv2/provisioning/listProvisionedDevices-test.js +1 -0
  118. package/test/unit/ngsiv2/provisioning/provisionDeviceMultientity-test.js +1 -0
  119. package/test/unit/ngsiv2/provisioning/removeProvisionedDevice-test.js +1 -0
  120. package/test/unit/ngsiv2/provisioning/singleConfigurationMode-test.js +1 -0
  121. package/test/unit/ngsiv2/provisioning/updateProvisionedDevices-test.js +1 -0
  122. package/test/unit/plugins/capture-configuration-inPlugins_test.js +2 -1
  123. package/test/unit/plugins/capture-provision-inPlugins_test.js +2 -1
  124. package/lib/plugins/addEvent.js +0 -32
  125. package/lib/plugins/attributeAlias.js +0 -107
  126. package/lib/plugins/multiEntity.js +0 -255
  127. package/lib/plugins/timestampProcessPlugin.js +0 -95
  128. package/test/unit/ngsi-ld/plugins/event-plugin_test.js +0 -116
  129. package/test/unit/ngsiv2/plugins/event-plugin_test.js +0 -118
  130. package/test/unit/ngsiv2/plugins/translation-inPlugins_test.js +0 -262
@@ -26,20 +26,20 @@
26
26
  /* eslint-disable consistent-return */
27
27
 
28
28
  const request = require('../../request-shim');
29
- const statsService = require('./../stats/statsRegistry');
30
- const async = require('async');
31
- const apply = async.apply;
32
29
  const alarms = require('../common/alarmManagement');
33
30
  const errors = require('../../errors');
34
31
  const utils = require('../northBound/restUtils');
32
+ const pluginUtils = require('../../plugins/pluginUtils');
35
33
  const config = require('../../commonConfig');
36
34
  const constants = require('../../constants');
37
35
  const jexlParser = require('../../plugins/jexlParser');
36
+ const expressionPlugin = require('../../plugins/expressionPlugin');
37
+ const compressTimestampPlugin = require('../../plugins/compressTimestamp');
38
38
  const moment = require('moment-timezone');
39
39
  const logger = require('logops');
40
40
  const _ = require('underscore');
41
41
  const context = {
42
- op: 'IoTAgentNGSI.Entities-LD'
42
+ op: 'IoTAgentNGSI-LD'
43
43
  };
44
44
  const NGSIv2 = require('./entities-NGSI-v2');
45
45
  const NGSIUtils = require('./ngsiUtils');
@@ -171,7 +171,7 @@ function convertAttrNGSILD(attr) {
171
171
  obj.value = { '@type': attr.type, '@value': attr.value };
172
172
  }
173
173
 
174
- if (attr.metadata) {
174
+ if (!!obj && attr.metadata) {
175
175
  let timestamp;
176
176
  Object.keys(attr.metadata).forEach(function (key) {
177
177
  switch (key) {
@@ -411,89 +411,13 @@ function addLinkedEntities(typeInformation, json) {
411
411
  }
412
412
 
413
413
  /**
414
- * Remove id, type and any hidden attrs after processing
414
+ * Determines if a value is of type float
415
415
  *
416
- * @param {Object} enities Unprocessed entities
417
- * @param {Object} typeInformation Configuration information for the device.
416
+ * @param {String} value Value to be analyzed
417
+ * @return {boolean} True if float, False otherwise.
418
418
  */
419
- function removeHiddenAttrsFromMultiEntity(entities, typeInformation) {
420
- let explicitAttrsList;
421
- if (typeInformation.explicitAttrs) {
422
- explicitAttrsList = [];
423
- explicitAttrsList.push('type');
424
- explicitAttrsList.push('id');
425
- if (typeof typeInformation.explicitAttrs === 'boolean') {
426
- if (typeInformation.timestamp) {
427
- explicitAttrsList.push(constants.TIMESTAMP_ATTRIBUTE);
428
- }
429
- if (typeInformation.active) {
430
- typeInformation.active.forEach((attr) => {
431
- explicitAttrsList.push(attr.name);
432
- });
433
- }
434
- if (typeInformation.staticAttributes) {
435
- typeInformation.staticAttributes.forEach((attr) => {
436
- explicitAttrsList.push(attr.name);
437
- });
438
- }
439
- } else if (typeof typeInformation.explicitAttrs === 'string') {
440
- entities.forEach((entity) => {
441
- const attsArray = utils.extractAttributesArrayFromNgsi2Entity(entity);
442
- const ctx = jexlParser.extractContext(attsArray);
443
- const res = jexlParser.parse(typeInformation.explicitAttrs, ctx);
444
- explicitAttrsList = explicitAttrsList.concat(res);
445
- });
446
- }
447
- }
448
- if (explicitAttrsList && explicitAttrsList.length >= 0) {
449
- entities.forEach((entity) => {
450
- const hidden = _.difference(_.keys(entity), explicitAttrsList);
451
- hidden.forEach((attr) => {
452
- delete entity[attr];
453
- });
454
- });
455
- }
456
- return entities;
457
- }
458
-
459
- /**
460
- * Remove id, type and any hidden attrs after processing
461
- *
462
- * @param {Object} result Unprocessed entity
463
- * @param {Object} typeInformation Configuration information for the device.
464
- */
465
- function removeHiddenAttrs(result, typeInformation) {
466
- delete result.id;
467
- delete result.type;
468
- let explicitAttrsList;
469
- if (typeInformation.explicitAttrs && typeof typeInformation.explicitAttrs === 'boolean') {
470
- explicitAttrsList = [];
471
- if (typeInformation.timestamp) {
472
- explicitAttrsList.push(constants.TIMESTAMP_ATTRIBUTE);
473
- }
474
- if (typeInformation.active) {
475
- typeInformation.active.forEach((attr) => {
476
- explicitAttrsList.push(attr.name);
477
- });
478
- }
479
- if (typeInformation.staticAttributes) {
480
- typeInformation.staticAttributes.forEach((attr) => {
481
- explicitAttrsList.push(attr.name);
482
- });
483
- }
484
- } else if (typeInformation.explicitAttrs && typeof typeInformation.explicitAttrs === 'string') {
485
- const attsArray = utils.extractAttributesArrayFromNgsi2Entity(result);
486
- const ctx = jexlParser.extractContext(attsArray);
487
- const res = jexlParser.parse(typeInformation.explicitAttrs, ctx);
488
- explicitAttrsList = res;
489
- }
490
- if (explicitAttrsList && explicitAttrsList.length >= 0) {
491
- const hidden = _.difference(_.keys(result), explicitAttrsList);
492
- hidden.forEach((attr) => {
493
- delete result[attr];
494
- });
495
- }
496
- return result;
419
+ function isFloat(value) {
420
+ return !isNaN(value) && value.toString().indexOf('.') !== -1;
497
421
  }
498
422
 
499
423
  /**
@@ -506,9 +430,31 @@ function removeHiddenAttrs(result, typeInformation) {
506
430
  * @param {String} token User token to identify against the PEP Proxies (optional).
507
431
  */
508
432
  function sendUpdateValueNgsiLD(entityName, attributes, typeInformation, token, callback) {
509
- let payload = {};
433
+ logger.debug(
434
+ context,
435
+ 'sendUpdateValueNgsiLD called with: \n entityName=%s \n attributes=%j \n typeInformation=%j',
436
+ entityName,
437
+ attributes,
438
+ typeInformation
439
+ );
440
+ const payload = [
441
+ {
442
+ id: entityName
443
+ }
444
+ ];
445
+
510
446
  const url = '/ngsi-ld/v1/entityOperations/upsert/?options=update';
511
447
 
448
+ if (typeInformation && typeInformation.type) {
449
+ payload[0].type = typeInformation.type;
450
+ }
451
+
452
+ if (config.getConfig().appendMode === true) {
453
+ payload.actionType = 'append';
454
+ } else {
455
+ payload.actionType = 'update';
456
+ }
457
+
512
458
  const options = NGSIUtils.createRequestObject(url, typeInformation, token);
513
459
  options.method = 'POST';
514
460
 
@@ -520,143 +466,584 @@ function sendUpdateValueNgsiLD(entityName, attributes, typeInformation, token, c
520
466
  callback(new errors.TypeNotFound(null, entityName));
521
467
  return;
522
468
  }
469
+ const idTypeSSSList = pluginUtils.getIdTypeServSubServiceFromDevice(typeInformation);
470
+ logger.debug(context, 'sendUpdateValueNgsiLD \n idTypeSSS are %j ', idTypeSSSList);
471
+ const measureAttrsForCtxt = [];
523
472
 
524
- payload.id = entityName;
525
- payload.type = typeInformation.type;
473
+ // Check explicitAttrs: adds all final needed attributes to payload
474
+ if (
475
+ typeInformation.explicitAttrs === undefined ||
476
+ (typeof typeInformation.explicitAttrs === 'boolean' && !typeInformation.explicitAttrs)
477
+ // explicitAttrs is not defined => default case: all attrs should be included
478
+ ) {
479
+ // This loop adds all measure values (attributes) into payload entities (entity[0])
480
+ for (let i = 0; i < attributes.length; i++) {
481
+ if (attributes[i].name && attributes[i].type) {
482
+ payload[0][attributes[i].name] = {
483
+ value: attributes[i].value,
484
+ type: attributes[i].type
485
+ };
486
+ const metadata = NGSIUtils.getMetaData(typeInformation, attributes[i].name, attributes[i].metadata);
487
+ if (metadata) {
488
+ payload[0][attributes[i].name].metadata = metadata;
489
+ }
490
+ } else {
491
+ callback(new errors.BadRequest(null, entityName));
492
+ return;
493
+ }
494
+ }
495
+ logger.debug(context, 'sendUpdateValueNgsiLD \n pre-initial non-explicitAttrs payload=%j', payload);
496
+ // Loop for add attrs from type.information.active (and lazys?) into payload entities (entity[0])
497
+ if (typeInformation.active) {
498
+ typeInformation.active.forEach((attr) => {
499
+ if (attr.expression) {
500
+ if (attr.object_id) {
501
+ payload[0][attr.object_id] = {
502
+ value: payload[0][attr.object_id] ? payload[0][attr.object_id].value : undefined,
503
+ type: attr.type,
504
+ object_id: attr.object_id
505
+ };
506
+ } else {
507
+ payload[0][attr.name] = {
508
+ value: payload[0][attr.name] ? payload[0][attr.name].value : undefined,
509
+ type: attr.type
510
+ };
511
+ }
512
+ }
513
+ });
514
+ }
515
+ } else {
516
+ let selectedAttrs = [];
517
+ if (typeof typeInformation.explicitAttrs === 'string') {
518
+ // explicitAttrs is a jexlExpression
519
+ // This ctxt should include all possible attrs
520
+ const attributesCtxt = [];
521
+ if (typeInformation.static) {
522
+ typeInformation.static.forEach(function (att) {
523
+ attributesCtxt.push(att);
524
+ });
525
+ }
526
+ // Measures
527
+ for (let i = 0; i < attributes.length; i++) {
528
+ if (attributes[i].name && attributes[i].type) {
529
+ const measureAttr = {
530
+ name: attributes[i].name,
531
+ value: attributes[i].value,
532
+ type: attributes[i].type
533
+ };
534
+ attributesCtxt.push(measureAttr);
535
+ }
536
+ }
537
+ // This context is just to calculate explicitAttrs when is an expression
538
+ let ctxt = expressionPlugin.extractContext(attributesCtxt.concat(idTypeSSSList), typeInformation);
539
+ // typeInformation.active attrs with expressions expanded by current ctxt
540
+ if (typeInformation.active) {
541
+ typeInformation.active.forEach(function (att) {
542
+ if (att.expression) {
543
+ if (expressionPlugin.contextAvailable(att.expression, ctxt, typeInformation)) {
544
+ const expandedAttr = {
545
+ name: att.name,
546
+ value: att.expression, // it doesn't matter final value here
547
+ type: att.type
548
+ };
549
+ attributesCtxt.push(expandedAttr);
550
+ ctxt = expressionPlugin.extractContext(
551
+ attributesCtxt.concat(idTypeSSSList),
552
+ typeInformation
553
+ );
554
+ }
555
+ }
556
+ });
557
+ }
558
+ // calculate expression for explicitAttrs
559
+ try {
560
+ const res = jexlParser.applyExpression(typeInformation.explicitAttrs, ctxt, typeInformation);
561
+ if (res === true) {
562
+ // like explicitAttrs == true
563
+ // selectAttrs should be measures which are defined attributes
564
+ typeInformation.active.forEach((attr) => {
565
+ selectedAttrs.push(attr.name);
566
+ selectedAttrs.push(attr.object_id);
567
+ });
568
+ } else if (res === false) {
569
+ // like explicitAttrs == false
570
+ // selectAttrs should be measures and defined attributes
571
+ typeInformation.active.forEach((attr) => {
572
+ selectedAttrs.push(attr.name);
573
+ selectedAttrs.push(attr.object_id);
574
+ });
575
+ for (let i = 0; i < attributes.length; i++) {
576
+ selectedAttrs.push(attributes[i].name);
577
+ }
578
+ } else {
579
+ selectedAttrs = res; // TBD: Check ensure is an array of strings
580
+ }
581
+ if (selectedAttrs.length === 0) {
582
+ // implies do nothing
583
+ logger.info(
584
+ context,
585
+ 'sendUpdateValueNgsiLD \n none selectedAttrs with %j and ctxt %j',
586
+ typeInformation.explicitAttrs,
587
+ ctxt
588
+ );
589
+ return callback(null);
590
+ }
591
+ } catch (e) {
592
+ // nothing to do: exception is already logged at info level
593
+ }
526
594
 
527
- for (let i = 0; i < attributes.length; i++) {
528
- if (attributes[i].name && attributes[i].type) {
529
- payload[attributes[i].name] = {
530
- value: attributes[i].value,
531
- type: attributes[i].type
532
- };
533
- const metadata = NGSIUtils.getMetaData(typeInformation, attributes[i].name, attributes[i].metadata);
534
- if (metadata) {
535
- payload[attributes[i].name].metadata = metadata;
595
+ typeInformation.active.forEach((attr) => {
596
+ if (selectedAttrs.includes(attr.name)) {
597
+ selectedAttrs.push(attr.object_id);
598
+ }
599
+ });
600
+ } else if (typeInformation.explicitAttrs && typeof typeInformation.explicitAttrs === 'boolean') {
601
+ // TBD: selectedAttrs could be a boolean as a result of applyExpression
602
+ // explicitAtts is true => Add measures which are defined attributes
603
+ typeInformation.active.forEach((attr) => {
604
+ selectedAttrs.push(attr.name);
605
+ selectedAttrs.push(attr.object_id);
606
+ });
607
+ }
608
+ // This loop adds selected measured values (attributes) into payload entities (entity[0])
609
+ for (let i = 0; i < attributes.length; i++) {
610
+ if (attributes[i].name && selectedAttrs.includes(attributes[i].name) && attributes[i].type) {
611
+ const attr = typeInformation.active.find((obj) => {
612
+ return obj.name === attributes[i].name;
613
+ });
614
+ payload[0][attributes[i].name] = {
615
+ value: attributes[i].value,
616
+ type: attributes[i].type
617
+ };
618
+ // ensure payload has attr with proper object_id
619
+ if (attr && attr.object_id) {
620
+ payload[0][attributes[i].name].object_id = attr.object_id;
621
+ }
622
+ const metadata = NGSIUtils.getMetaData(typeInformation, attributes[i].name, attributes[i].metadata);
623
+ if (metadata) {
624
+ payload[0][attributes[i].name].metadata = metadata;
625
+ }
626
+ } else if (attributes[i].name && !selectedAttrs.includes(attributes[i].name) && attributes[i].type) {
627
+ const att = {
628
+ name: attributes[i].name,
629
+ type: attributes[i].type,
630
+ value: attributes[i].value
631
+ };
632
+ measureAttrsForCtxt.push(att);
536
633
  }
537
- } else {
538
- callback(new errors.BadRequest(null, entityName));
539
- return;
634
+ }
635
+ logger.debug(
636
+ context,
637
+ 'sendUpdateValueNgsiLD \n pre-initial explicitAttrs payload=%j \n selectedAttrs',
638
+ payload,
639
+ selectedAttrs
640
+ );
641
+
642
+ // Loop for add seleted attrs from type.information.active into pyaload entities (entity[0])
643
+ if (typeInformation.active) {
644
+ typeInformation.active.forEach((attr) => {
645
+ if (selectedAttrs.includes(attr.name)) {
646
+ if (attr.object_id) {
647
+ payload[0][attr.object_id] = {
648
+ value: payload[0][attr.object_id]
649
+ ? payload[0][attr.object_id].value
650
+ : payload[0][attr.name]
651
+ ? payload[0][attr.name].value
652
+ : undefined,
653
+ type: attr.type,
654
+ object_id: attr.object_id
655
+ };
656
+ } else {
657
+ payload[0][attr.name] = {
658
+ value: payload[0][attr.name] ? payload[0][attr.name].value : undefined,
659
+ type: attr.type
660
+ };
661
+ }
662
+ }
663
+ });
664
+ }
665
+ } // END check explicitAttrs
666
+ logger.debug(context, 'sendUpdateValueNgsiLD \n initial payload=%j', payload);
667
+
668
+ const currentEntity = payload[0];
669
+
670
+ // Prepare attributes for expresionPlugin
671
+ const attsArray = pluginUtils.extractAttributesArrayFromNgsi2Entity(currentEntity);
672
+
673
+ // Exclude processing all attr expressions when current attr is of type 'commandStatus' or 'commandResult'
674
+ let attsArrayFiltered = [];
675
+ if (attsArray) {
676
+ attsArrayFiltered = attsArray.filter((obj) => {
677
+ return ![constants.COMMAND_STATUS, constants.COMMAND_RESULT].includes(obj.type);
678
+ });
679
+ }
680
+ let attributesCtxt = [...attsArrayFiltered]; // just copy
681
+ if (typeInformation.static) {
682
+ typeInformation.static.forEach(function (att) {
683
+ attributesCtxt.push(att);
684
+ });
685
+ }
686
+ if (measureAttrsForCtxt) {
687
+ measureAttrsForCtxt.forEach(function (att) {
688
+ attributesCtxt.push(att);
689
+ });
690
+ }
691
+ attributesCtxt = attributesCtxt.concat(idTypeSSSList);
692
+ const ctxt = expressionPlugin.extractContext(attributesCtxt, typeInformation);
693
+ logger.debug(context, 'sendUpdateValueNgsiLD \n initial ctxt %j ', ctxt);
694
+
695
+ // Sort currentEntity to get first attrs without expressions (checking attrs in typeInformation.active)
696
+ // attributes without expressions should be processed before
697
+ logger.debug(context, 'sendUpdateValueNgsiLD \n currentEntity %j ', currentEntity);
698
+ if (typeInformation.active && typeInformation.active.length > 0) {
699
+ for (const k in currentEntity) {
700
+ typeInformation.active.forEach(function (att) {
701
+ if (
702
+ (att.object_id && att.object_id === k && att.expression) ||
703
+ (att.name && att.name === k && att.expression)
704
+ ) {
705
+ const m = currentEntity[k];
706
+ delete currentEntity[k];
707
+ currentEntity[k] = m; // put into the end of currentEntity
708
+ }
709
+ });
540
710
  }
541
711
  }
712
+ logger.debug(context, 'sendUpdateValueNgsiLD \n currentEntity sorted %j ', currentEntity);
713
+ let timestampValue;
714
+ // Loop for each final attribute to apply alias, multientity and expressions
715
+ for (const j in currentEntity) {
716
+ // discard id and type
717
+ if (j !== 'id' || j !== 'type') {
718
+ // Apply Mapping Alias: object_id in attributes are in typeInformation.active
719
+ let attr;
720
+ let newAttr = payload[0][j];
721
+ if (typeInformation.active) {
722
+ attr = typeInformation.active.find((obj) => {
723
+ return obj.object_id === j;
724
+ });
725
+ }
726
+ if (!attr) {
727
+ if (typeInformation.lazy) {
728
+ attr = typeInformation.lazy.find((obj) => {
729
+ return obj.object_id === j;
730
+ });
731
+ }
732
+ }
733
+ if (!attr) {
734
+ if (typeInformation.active) {
735
+ attr = typeInformation.active.find((obj) => {
736
+ return obj.name === j;
737
+ });
738
+ }
739
+ }
740
+ if (attr && attr.name) {
741
+ if (['id', 'type'].includes(attr.name)) {
742
+ // invalid mapping
743
+ logger.debug(
744
+ context,
745
+ 'sendUpdateValueNgsiLD \n invalid mapping for attr %j \n newAttr %j',
746
+ attr,
747
+ newAttr
748
+ );
749
+ delete payload[0][attr.object_id];
750
+ attr = undefined; // stop processing attr
751
+ newAttr = undefined;
752
+ } else {
753
+ ctxt[attr.name] = payload[0][j].value;
754
+ }
755
+ }
756
+ logger.debug(
757
+ context,
758
+ 'sendUpdateValueNgsiLD \n procesing j %j attr %j ctxt %j \n newAttr %j ',
759
+ j,
760
+ attr,
761
+ ctxt,
762
+ newAttr
763
+ );
542
764
 
543
- payload = NGSIUtils.castJsonNativeAttributes(payload);
544
- async.waterfall(
545
- [
546
- apply(statsService.add, 'measureRequests', 1),
547
- apply(NGSIUtils.applyMiddlewares, NGSIUtils.updateMiddleware, payload, typeInformation)
548
- ],
549
- function (error, result) {
550
- if (error) {
551
- callback(error);
552
- } else {
553
- if (result) {
554
- // The payload has been transformed by multientity plugin. It is not a JSON object but an Array.
555
- if (result instanceof Array) {
556
- if (
557
- 'timestamp' in typeInformation && typeInformation.timestamp !== undefined
558
- ? typeInformation.timestamp
559
- : config.getConfig().timestamp
560
- ) {
561
- if (!utils.isTimestampedNgsi2(result)) {
562
- options.json = NGSIv2.addTimestamp(result, typeInformation.timezone);
563
- } else if (!utils.IsValidTimestampedNgsi2(result)) {
564
- logger.error(context, 'Invalid timestamp:%s', JSON.stringify(result));
565
- callback(new errors.BadTimestamp(result));
566
- return;
567
- }
568
- }
765
+ if (attr && attr.type) {
766
+ if (attr.type !== 'GeoProperty') {
767
+ newAttr.type = attr.type;
768
+ }
769
+ }
569
770
 
570
- options.json = removeHiddenAttrsFromMultiEntity(result, typeInformation);
771
+ // Apply expression
772
+ if (attr && attr.expression) {
773
+ logger.debug(
774
+ context,
775
+ 'sendUpdateValueNgsiLD \n apply expression %j \n over ctxt %j \n and device %j',
776
+ attr.expression,
777
+ ctxt,
778
+ typeInformation
779
+ );
780
+ let res = null;
781
+ try {
782
+ if (expressionPlugin.contextAvailable(attr.expression, ctxt, typeInformation)) {
783
+ res = expressionPlugin.applyExpression(attr.expression, ctxt, typeInformation);
571
784
  } else {
572
- // Remove id, type and any hidden attrs after processing
573
- options.json = removeHiddenAttrs(result, typeInformation);
574
- logger.debug(context, 'typeInformation: %j', typeInformation);
575
- if (
576
- 'timestamp' in typeInformation && typeInformation.timestamp !== undefined
577
- ? typeInformation.timestamp
578
- : config.getConfig().timestamp
579
- ) {
580
- if (!utils.isTimestampedNgsi2(options.json)) {
581
- options.json = NGSIv2.addTimestamp(options.json, typeInformation.timezone);
582
- } else if (!utils.IsValidTimestampedNgsi2(options.json)) {
583
- logger.error(context, 'Invalid timestamp:%s', JSON.stringify(options.json));
584
- callback(new errors.BadTimestamp(options.json));
585
- return;
586
- }
587
- }
785
+ logger.warn(
786
+ context,
787
+ 'sendUpdateValueNgsiLD \n no context available for apply expression %j \n',
788
+ attr.expression
789
+ );
790
+ res = newAttr.value; // keep newAttr value
791
+ }
792
+ } catch (e) {
793
+ logger.error(context, 'sendUpdateValueNgsiLD \n apply expression exception %j \n', e);
794
+ if (!expressionPlugin.checkJexl(typeInformation)) {
795
+ return callback(e); // just throw error with legacy parser for backward compatiblity
796
+ } else {
797
+ res = ctxt[attr.name]; // TBD: add reference to test
588
798
  }
589
- } else {
590
- delete payload.id;
591
- delete payload.type;
592
- options.json = payload;
593
799
  }
594
- // Purge object_id from entities before sent to CB
595
- // object_id was added by createNgsi2Entity to allow multientity
596
- // with duplicate attribute names.
597
- let att;
598
- if (options.json) {
599
- for (let entity = 0; entity < options.json.length; entity++) {
600
- for (att in options.json[entity]) {
601
- /*jshint camelcase: false */
602
- if (options.json[entity][att].object_id) {
603
- /*jshint camelcase: false */
604
- delete options.json[entity][att].object_id;
605
- }
606
- if (options.json[entity][att].multi) {
607
- delete options.json[entity][att].multi;
608
- }
609
- if (options.json[entity][att].name) {
610
- delete options.json[entity][att].name;
611
- }
800
+ if (!expressionPlugin.checkJexl(typeInformation)) {
801
+ // legacy expression plugin: Involves some legacy checks performed by applierExpression
802
+ if (
803
+ res &&
804
+ res !== 'undefined' &&
805
+ res !== 'null' &&
806
+ (typeof res === 'string' || res instanceof String) &&
807
+ !res.includes('NaN')
808
+ ) {
809
+ newAttr.value = res;
810
+ if (newAttr.type === 'Number' && isFloat(newAttr.value)) {
811
+ newAttr.value = Number.parseFloat(newAttr.value);
812
+ } else if (newAttr.type === 'Number' && !Number.isNaN(Number.parseInt(newAttr.value))) {
813
+ newAttr.value = Number.parseInt(newAttr.value);
814
+ } else if (newAttr.type === 'Boolean') {
815
+ newAttr.value = newAttr.value === 'true' || newAttr.value === '1';
816
+ } else if (newAttr.type === 'None') {
817
+ newAttr.value = null;
818
+ }
819
+ } else if (isNaN(res) || res === 'undefined' || res === 'null') {
820
+ logger.debug(
821
+ context,
822
+ 'sendUpdateValueNgsiLD \n apply expression result: isNaN || undefined || null'
823
+ );
824
+ if (res === 'null' && newAttr.type === 'None') {
825
+ newAttr.value = null;
826
+ } else {
827
+ delete payload[0][j]; // remove measure attr
828
+ attr = undefined; // stop process attr
612
829
  }
613
830
  }
614
831
  } else {
615
- for (att in options.json) {
616
- /*jshint camelcase: false */
617
- if (options.json[att].object_id) {
618
- /*jshint camelcase: false */
619
- delete options.json[att].object_id;
620
- }
621
- if (options.json[att].multi) {
622
- delete options.json[att].multi;
623
- }
624
- if (options.json[att].name) {
625
- delete options.json[att].name;
832
+ // jexl expression plugin
833
+ newAttr.value = res;
834
+ }
835
+ logger.debug(
836
+ context,
837
+ 'sendUpdateValueNgsiLD \n apply expression result %j \n newAttr %j',
838
+ res,
839
+ newAttr
840
+ );
841
+ }
842
+
843
+ // Apply Multientity: entity_type and entity_name in attributes are in typeInformation.active
844
+ if (attr && (attr.entity_type || attr.entity_name)) {
845
+ // Create a newEntity for this attribute
846
+ let newEntityName = null;
847
+ if (attr.entity_name) {
848
+ try {
849
+ if (expressionPlugin.contextAvailable(attr.entity_name, ctxt, typeInformation)) {
850
+ newEntityName = expressionPlugin.applyExpression(attr.entity_name, ctxt, typeInformation);
851
+ } else {
852
+ logger.warn(
853
+ context,
854
+ 'sendUpdateValueNgsiLD \n MULTI no context available for apply expression %j \n',
855
+ attr.entity_name
856
+ );
857
+ newEntityName = attr.entity_name;
626
858
  }
859
+ newEntityName = newEntityName ? newEntityName : attr.entity_name;
860
+ } catch (e) {
861
+ logger.error(context, 'sendUpdateValueNgsiLD \n MULTI apply expression exception %j \n', e);
862
+ newEntityName = attr.entity_name;
627
863
  }
864
+ logger.debug(
865
+ context,
866
+ 'sendUpdateValueNgsiLD \n MULTI apply expression %j \n result %j \n payload %j',
867
+ attr.entity_name,
868
+ newEntityName,
869
+ payload
870
+ );
628
871
  }
629
872
 
630
- try {
631
- if (result instanceof Array) {
632
- options.json = _.map(options.json, formatAsNGSILD);
633
- } else {
634
- options.json.id = entityName;
635
- options.json.type = typeInformation.type;
636
- options.json = [formatAsNGSILD(options.json)];
873
+ let newEntity = {
874
+ id: newEntityName ? newEntityName : payload[0].id,
875
+ type: attr.entity_type ? attr.entity_type : payload[0].type
876
+ };
877
+ // Check if there is already a newEntity created
878
+ const alreadyEntity = payload.find((entity) => {
879
+ return entity.id === newEntity.id && entity.type === newEntity.type;
880
+ });
881
+ if (alreadyEntity) {
882
+ // Use alreadyEntity
883
+ alreadyEntity[attr.name] = newAttr;
884
+ } else {
885
+ // Add newEntity to payload
886
+ newEntity[attr.name] = newAttr;
887
+ if (
888
+ 'timestamp' in typeInformation && typeInformation.timestamp !== undefined
889
+ ? typeInformation.timestamp
890
+ : config.getConfig().timestamp !== undefined
891
+ ? config.getConfig().timestamp
892
+ : timestampValue !== undefined
893
+ ) {
894
+ newEntity = NGSIv2.addTimestamp(newEntity, typeInformation.timezone, timestampValue);
895
+ logger.debug(context, 'sendUpdateValueNgsiLD \n timestamped newEntity=%j', newEntity);
637
896
  }
638
- } catch (error) {
639
- return callback(new errors.BadGeocoordinates(JSON.stringify(payload)));
897
+ payload.push(newEntity);
640
898
  }
899
+ if (attr && attr.name) {
900
+ if (attr.name !== j) {
901
+ logger.debug(
902
+ context,
903
+ 'sendUpdateValueNgsiLD \n MULTI remove measure attr %j keep alias j %j from %j \n',
904
+ j,
905
+ attr,
906
+ payload
907
+ );
908
+ delete payload[0][j];
909
+ }
910
+ }
911
+ // if (attr && (attr.entity_type || attr.entity_name))
912
+ } else {
913
+ // Not a multientity attr
914
+ if (attr && attr.name) {
915
+ payload[0][attr.name] = newAttr;
916
+ if (attr.name !== j) {
917
+ delete payload[0][j]; // keep alias name, remove measure name
918
+ }
919
+ }
920
+ if (newAttr && newAttr.type === constants.TIMESTAMP_TYPE_NGSI2 && newAttr.value) {
921
+ const extendedTime = compressTimestampPlugin.fromBasicToExtended(newAttr.value);
922
+ if (extendedTime) {
923
+ // TBD: there is not flag about compressTimestamp in iotagent-node-lib,
924
+ // but there is one in agents
925
+ newAttr.value = extendedTime;
926
+ }
927
+ }
928
+ if (j === constants.TIMESTAMP_ATTRIBUTE) {
929
+ if (newAttr && newAttr.type === constants.TIMESTAMP_TYPE_NGSI2 && newAttr.value) {
930
+ timestampValue = newAttr.value;
931
+ logger.debug(
932
+ context,
933
+ 'sendUpdateValueNgsiLD \n newAttr is TimeInstant and new payload=%j',
934
+ payload
935
+ );
936
+ }
937
+ }
938
+ if (
939
+ newAttr &&
940
+ newAttr.metadata &&
941
+ newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE] &&
942
+ newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].type === constants.TIMESTAMP_TYPE_NGSI2 &&
943
+ newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].value
944
+ ) {
945
+ const extendedTime = compressTimestampPlugin.fromBasicToExtended(
946
+ newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].value
947
+ );
948
+ if (extendedTime) {
949
+ newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].value = extendedTime;
950
+ }
951
+ }
952
+ }
953
+ } // if (j !== 'id' || j !== 'type')
641
954
 
642
- if (typeInformation.active) {
643
- addLinkedEntities(typeInformation, options.json);
955
+ // final attr loop
956
+ logger.debug(
957
+ context,
958
+ 'sendUpdateValueNgsiLD \n after procesing attr %j \n current entity %j \n current payload=%j',
959
+ j,
960
+ currentEntity,
961
+ payload
962
+ );
963
+ }
964
+ // for attr loop
965
+
966
+ // Add timestamp to paylaod
967
+ if (
968
+ 'timestamp' in typeInformation && typeInformation.timestamp !== undefined
969
+ ? typeInformation.timestamp
970
+ : config.getConfig().timestamp !== undefined
971
+ ? config.getConfig().timestamp
972
+ : timestampValue !== undefined
973
+ ) {
974
+ if (timestampValue) {
975
+ // timeInstant is provided as measure
976
+ if (Object.keys(payload[0]).length > 1) {
977
+ // include metadata with TimeInstant in attrs when TimeInstant is provided as measure in all entities
978
+ payload[0] = NGSIv2.addTimestamp(payload[0], typeInformation.timezone, timestampValue);
979
+ }
980
+ } else {
981
+ // jshint maxdepth:5
982
+ for (let n = 0; n < payload.length; n++) {
983
+ if (!utils.isTimestampedNgsi2(payload[n])) {
984
+ // legacy check needed?
985
+ payload[n] = NGSIv2.addTimestamp(payload[n], typeInformation.timezone);
986
+ // jshint maxdepth:5
987
+ } else if (!utils.IsValidTimestampedNgsi2(payload[n])) {
988
+ // legacy check needed?
989
+ logger.error(context, 'Invalid timestamp:%s', JSON.stringify(payload[0]));
990
+ callback(new errors.BadTimestamp(payload));
991
+ return;
644
992
  }
993
+ }
994
+ }
995
+ }
645
996
 
646
- logger.debug(context, 'Updating device value in the Context Broker at [%s]', options.url);
647
- logger.debug(
648
- context,
649
- 'Using the following NGSI-LD request:\n\n%s\n\n',
650
- JSON.stringify(options, null, 4)
651
- );
997
+ logger.debug(context, 'sendUpdateValueNgsiLD \n ending payload=%j', payload);
652
998
 
653
- request(
654
- options,
655
- generateNGSILDOperationHandler('update', entityName, typeInformation, token, options, callback)
656
- );
999
+ for (let m = 0; m < payload.length; m++) {
1000
+ for (const key in payload[m]) {
1001
+ // purge object_id from payload
1002
+ if (payload[m][key] && payload[m][key].object_id) {
1003
+ delete payload[m][key].object_id;
657
1004
  }
658
1005
  }
659
- );
1006
+ payload[m] = NGSIUtils.castJsonNativeAttributes(payload[m]); // native types
1007
+ }
1008
+ logger.debug(context, 'sendUpdateValueNgsiLD \n payload with native types and without object_id %j', payload);
1009
+
1010
+ options.json = payload;
1011
+ try {
1012
+ if (payload instanceof Array) {
1013
+ options.json = _.map(options.json, formatAsNGSILD);
1014
+ } else {
1015
+ options.json.id = entityName;
1016
+ options.json.type = typeInformation.type;
1017
+ options.json = [formatAsNGSILD(options.json)];
1018
+ }
1019
+ } catch (error) {
1020
+ return callback(new errors.BadGeocoordinates(JSON.stringify(payload)));
1021
+ }
1022
+
1023
+ if (typeInformation.active) {
1024
+ addLinkedEntities(typeInformation, options.json);
1025
+ }
1026
+
1027
+ // Prevent to update an entity with an empty payload
1028
+ if (
1029
+ Object.keys(options.json).length > 0 &&
1030
+ (options.json.length > 1 || (options.json.length === 1 && Object.keys(options.json[0]).length > 2)) // more than id and type
1031
+ ) {
1032
+ logger.debug(context, 'Updating device value in the Context Broker at [%s]', options.url);
1033
+ logger.debug(context, 'Using the following NGSI LD request:\n\n%s\n\n', JSON.stringify(options, null, 4));
1034
+ request(
1035
+ options,
1036
+ generateNGSILDOperationHandler('update', entityName, typeInformation, token, options, callback)
1037
+ );
1038
+ } else {
1039
+ logger.debug(
1040
+ context,
1041
+ 'Not updating device value in the Context Broker at [%s] due to empty payload \n\n[%s]\n\n',
1042
+ options.url,
1043
+ JSON.stringify(options, null, 4)
1044
+ );
1045
+ callback(null);
1046
+ }
660
1047
  }
661
1048
 
662
1049
  exports.convertAttrNGSILD = convertAttrNGSILD;