iotagent-node-lib 4.5.0 → 4.7.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 (124) hide show
  1. package/.github/workflows/ci.yml +0 -1
  2. package/Changelog +12 -0
  3. package/README.md +67 -272
  4. package/config.js +3 -1
  5. package/doc/README.md +1 -1
  6. package/doc/admin.md +40 -18
  7. package/doc/api.md +532 -136
  8. package/doc/deprecated.md +4 -0
  9. package/doc/devel/architecture.md +5 -135
  10. package/doc/devel/development.md +224 -12
  11. package/doc/getting-started.md +114 -53
  12. package/doc/requirements.txt +1 -1
  13. package/doc/roadmap.md +5 -5
  14. package/docker/Mosquitto/Dockerfile +2 -2
  15. package/docker/Mosquitto/README.md +14 -11
  16. package/lib/commonConfig.js +21 -2
  17. package/lib/constants.js +3 -0
  18. package/lib/fiware-iotagent-lib.js +12 -15
  19. package/lib/jexlTranformsMap.js +3 -1
  20. package/lib/model/Command.js +2 -2
  21. package/lib/model/Device.js +7 -3
  22. package/lib/model/Group.js +5 -3
  23. package/lib/model/dbConn.js +53 -115
  24. package/lib/services/commands/commandRegistryMongoDB.js +115 -75
  25. package/lib/services/common/alarmManagement.js +3 -0
  26. package/lib/services/common/iotManagerService.js +3 -1
  27. package/lib/services/devices/deviceRegistryMemory.js +36 -0
  28. package/lib/services/devices/deviceRegistryMongoDB.js +160 -87
  29. package/lib/services/devices/deviceService.js +33 -3
  30. package/lib/services/devices/devices-NGSI-v2.js +6 -1
  31. package/lib/services/groups/groupRegistryMongoDB.js +120 -83
  32. package/lib/services/groups/groupService.js +1 -1
  33. package/lib/services/ngsi/entities-NGSI-LD.js +320 -570
  34. package/lib/services/ngsi/entities-NGSI-v2.js +51 -3
  35. package/lib/services/ngsi/ngsiService.js +34 -1
  36. package/lib/services/northBound/deviceGroupAdministrationServer.js +42 -6
  37. package/lib/services/northBound/deviceProvisioningServer.js +12 -4
  38. package/lib/services/northBound/northboundServer.js +2 -0
  39. package/lib/services/stats/statsRegistry.js +128 -101
  40. package/lib/templates/createDevice.json +0 -24
  41. package/lib/templates/createDeviceLax.json +0 -23
  42. package/lib/templates/deviceGroup.json +1 -25
  43. package/lib/templates/updateDevice.json +12 -24
  44. package/lib/templates/updateDeviceLax.json +12 -23
  45. package/package.json +5 -5
  46. package/scripts/legacy_expression_tool/README.md +0 -1
  47. package/test/functional/README.md +22 -17
  48. package/test/functional/config-test.js +3 -2
  49. package/test/functional/functional-tests-runner.js +9 -4
  50. package/test/functional/functional-tests.js +4 -4
  51. package/test/functional/testCases.js +245 -4
  52. package/test/functional/testUtils.js +2 -2
  53. package/test/unit/examples/deviceProvisioningRequests/provisionFullDevice.json +1 -13
  54. package/test/unit/examples/groupProvisioningRequests/multipleConfigGroupsCreation.json +44 -0
  55. package/test/unit/examples/groupProvisioningRequests/provisionDuplicateConfigGroup.json +35 -0
  56. package/test/unit/examples/groupProvisioningRequests/provisionFullConfigGroup.json +36 -0
  57. package/test/unit/examples/groupProvisioningRequests/provisionFullConfigGroupAlternate.json +36 -0
  58. package/test/unit/examples/groupProvisioningRequests/provisionFullGroup.json +1 -0
  59. package/test/unit/general/config-multi-core-test.js +1 -2
  60. package/test/unit/general/contextBrokerKeystoneSecurityAccess-test.js +5 -4
  61. package/test/unit/general/deviceService-test.js +106 -3
  62. package/test/unit/general/statistics-service_test.js +1 -74
  63. package/test/unit/memoryRegistry/deviceRegistryMemory_test.js +6 -5
  64. package/test/unit/mongodb/mongodb-configGroup-registry-test.js +452 -0
  65. package/test/unit/mongodb/mongodb-connectionoptions-test.js +9 -42
  66. package/test/unit/mongodb/mongodb-group-registry-test.js +34 -33
  67. package/test/unit/mongodb/mongodb-service-registry-test.js +477 -0
  68. package/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin1a.json +4 -4
  69. package/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin2.json +22 -22
  70. package/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin29.json +4 -4
  71. package/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin32.json +14 -15
  72. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin1.json +23 -23
  73. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin15.json +0 -5
  74. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin4.json +11 -16
  75. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin5.json +23 -28
  76. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin6.json +8 -13
  77. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin7.json +0 -5
  78. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin8.json +24 -29
  79. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityTimestampPlugin2.json +12 -17
  80. package/test/unit/ngsi-ld/examples/contextRequests/updateContextStaticLinkedAttributes.json +12 -10
  81. package/test/unit/ngsi-ld/expressions/jexlBasedTransformations-test.js +1 -104
  82. package/test/unit/ngsi-ld/general/config-jsonld-contexts-test.js +1 -2
  83. package/test/unit/ngsi-ld/plugins/multientity-plugin_test.js +4 -5
  84. package/test/unit/ngsi-ld/provisioning/listProvisionedDevices-test.js +0 -4
  85. package/test/unit/ngsi-mixed/provisioning/ngsi-versioning-test.js +8 -5
  86. package/test/unit/ngsiv2/expressions/jexlBasedTransformations-test.js +42 -41
  87. package/test/unit/ngsiv2/general/contextBrokerOAuthSecurityAccess-test.js +11 -10
  88. package/test/unit/ngsiv2/general/deviceService-test.js +98 -4
  89. package/test/unit/ngsiv2/general/https-support-test.js +1 -1
  90. package/test/unit/ngsiv2/general/iotam-autoregistration-test.js +195 -0
  91. package/test/unit/ngsiv2/lazyAndCommands/active-devices-attribute-update-test.js +4 -3
  92. package/test/unit/ngsiv2/lazyAndCommands/command-test.js +6 -5
  93. package/test/unit/ngsiv2/lazyAndCommands/lazy-devices-test.js +17 -16
  94. package/test/unit/ngsiv2/lazyAndCommands/polling-commands-test.js +10 -18
  95. package/test/unit/ngsiv2/ngsiService/active-devices-test.js +21 -20
  96. package/test/unit/ngsiv2/ngsiService/staticAttributes-test.js +8 -7
  97. package/test/unit/ngsiv2/plugins/alias-plugin_test.js +12 -11
  98. package/test/unit/ngsiv2/plugins/custom-plugin_test.js +3 -2
  99. package/test/unit/ngsiv2/plugins/multientity-plugin_test.js +28 -27
  100. package/test/unit/ngsiv2/provisioning/device-group-api-test.js +265 -4
  101. package/test/unit/ngsiv2/provisioning/device-provisioning-api_test.js +12 -11
  102. package/test/unit/ngsiv2/provisioning/device-provisioning-configGroup-api_test.js +1190 -0
  103. package/test/unit/ngsiv2/provisioning/device-registration_test.js +5 -4
  104. package/test/unit/ngsiv2/provisioning/listProvisionedDevices-test.js +6 -9
  105. package/test/unit/ngsiv2/provisioning/provisionDeviceMultientity-test.js +1 -1
  106. package/test/unit/ngsiv2/provisioning/removeProvisionedDevice-test.js +5 -4
  107. package/test/unit/ngsiv2/provisioning/updateProvisionedDevices-test.js +8 -7
  108. package/test/unit/statsRegistry/openmetrics-test.js +167 -0
  109. package/lib/templates/queryContext.json +0 -25
  110. package/test/unit/examples/deviceProvisioningRequests/provisionBidirectionalDevice.json +0 -35
  111. package/test/unit/examples/deviceProvisioningRequests/provisionDeviceBidirectionalGroup.json +0 -17
  112. package/test/unit/examples/groupProvisioningRequests/bidirectionalGroup.json +0 -31
  113. package/test/unit/general/statistics-persistence_test.js +0 -121
  114. package/test/unit/ngsi-ld/examples/contextRequests/createBidirectionalDevice.json +0 -17
  115. package/test/unit/ngsi-ld/examples/contextRequests/updateContextProcessTimestamp.json +0 -12
  116. package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotification.json +0 -13
  117. package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotificationWithDatasetId.json +0 -21
  118. package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotificationWithMetadata.json +0 -17
  119. package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalSubscriptionRequest.json +0 -23
  120. package/test/unit/ngsi-ld/plugins/timestamp-processing-plugin_test.js +0 -132
  121. package/test/unit/ngsiv2/examples/contextRequests/createBidirectionalDevice.json +0 -8
  122. package/test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalNotification.json +0 -13
  123. package/test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalNotificationWithMetadata.json +0 -19
  124. package/test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalSubscriptionRequest.json +0 -24
@@ -28,13 +28,11 @@
28
28
  const request = require('../../request-shim');
29
29
  const alarms = require('../common/alarmManagement');
30
30
  const errors = require('../../errors');
31
- const utils = require('../northBound/restUtils');
32
31
  const pluginUtils = require('../../plugins/pluginUtils');
33
32
  const config = require('../../commonConfig');
34
33
  const constants = require('../../constants');
35
34
  const jexlParser = require('../../plugins/jexlParser');
36
35
  const expressionPlugin = require('../../plugins/expressionPlugin');
37
- const compressTimestampPlugin = require('../../plugins/compressTimestamp');
38
36
  const moment = require('moment-timezone');
39
37
  const logger = require('logops');
40
38
  const _ = require('underscore');
@@ -45,83 +43,6 @@ const NGSIUtils = require('./ngsiUtils');
45
43
 
46
44
  const NGSI_LD_URN = 'urn:ngsi-ld:';
47
45
 
48
- /**
49
- * Adds timestamp to ngsi payload entities accoding to timezone, and an optional timestampvalue.
50
- *
51
- * @param {Object} payload NGSIv2 payload with one or more entities
52
- * @param String timezone TimeZone value (optional)
53
- * @param String timestampValue Timestamp value (optional). If not provided current timestamp is used
54
- * @param Boolean skipMetadataAtt An optional flag to indicate if timestamp should be added to each metadata attribute. Default is false
55
- * @return {Object} NGSIv2 payload entities with timestamp
56
- */
57
- function addTimestamp(payload, timezone, timestampValue) {
58
- function addTimestampEntity(entity, timezone, timestampValue) {
59
- const timestamp = {
60
- type: constants.TIMESTAMP_TYPE_NGSI2
61
- };
62
-
63
- if (timestampValue) {
64
- timestamp.value = timestampValue;
65
- } else if (!timezone) {
66
- timestamp.value = new Date().toISOString();
67
- } else {
68
- timestamp.value = moment().tz(timezone).format('YYYY-MM-DD[T]HH:mm:ss.SSSZ');
69
- }
70
-
71
- function addMetadata(attribute) {
72
- let timestampFound = false;
73
-
74
- if (!attribute.metadata) {
75
- attribute.metadata = {};
76
- }
77
-
78
- for (let i = 0; i < attribute.metadata.length; i++) {
79
- if (attribute.metadata[i] === constants.TIMESTAMP_ATTRIBUTE) {
80
- if (
81
- attribute.metadata[constants.TIMESTAMP_ATTRIBUTE].type === constants.TIMESTAMP_TYPE_NGSI2 &&
82
- attribute.metadata[constants.TIMESTAMP_ATTRIBUTE].value === timestamp.value
83
- ) {
84
- timestampFound = true;
85
- break;
86
- }
87
- }
88
- }
89
-
90
- if (!timestampFound) {
91
- attribute.metadata[constants.TIMESTAMP_ATTRIBUTE] = timestamp;
92
- }
93
-
94
- return attribute;
95
- }
96
- let keyCount = 0;
97
- for (const key in entity) {
98
- /* eslint-disable-next-line no-prototype-builtins */
99
- if (entity.hasOwnProperty(key) && key !== 'id' && key !== 'type') {
100
- addMetadata(entity[key]);
101
- keyCount += 1;
102
- }
103
- }
104
- // Add timestamp just to entity with attrs: multientity plugin could
105
- // create empty entities just with id and type.
106
- if (keyCount > 0) {
107
- entity[constants.TIMESTAMP_ATTRIBUTE] = timestamp;
108
- }
109
-
110
- return entity;
111
- }
112
-
113
- if (payload instanceof Array) {
114
- for (let i = 0; i < payload.length; i++) {
115
- if (!utils.isTimestampedNgsi2(payload[i])) {
116
- payload[i] = addTimestampEntity(payload[i], timezone, timestampValue);
117
- }
118
- }
119
-
120
- return payload;
121
- }
122
- return addTimestampEntity(payload, timezone, timestampValue);
123
- }
124
-
125
46
  /**
126
47
  * Amends an NGSIv2 attribute to NGSI-LD format
127
48
  * All native JSON types are respected and cast as Property values
@@ -503,560 +424,389 @@ function addLinkedEntities(typeInformation, json) {
503
424
  * @param {Object} typeInformation Configuration information for the device.
504
425
  * @param {String} token User token to identify against the PEP Proxies (optional).
505
426
  */
506
- function sendUpdateValueNgsiLD(entityName, attributes, typeInformation, token, callback) {
507
- logger.debug(
508
- context,
509
- 'sendUpdateValueNgsiLD called with: \n entityName=%s \n attributes=%j \n typeInformation=%j',
510
- entityName,
511
- attributes,
512
- typeInformation
513
- );
514
- const payload = [
515
- {
516
- id: entityName
427
+ /**
428
+ * Makes an update in the Device's entity in the context broker, with the values given in the 'attributes' array. This
429
+ * array should comply to the NGSIv2's attribute format.
430
+ *
431
+ * @param {String} entityName Name of the entity to register.
432
+ * @param {Array} measures measure array containing the values to update.
433
+ * @param {Object} typeInformation Configuration information for the device.
434
+ * @param {String} token User token to identify against the PEP Proxies (optional).
435
+ */
436
+ function sendUpdateValueNgsiLD(entityName, originMeasures, originTypeInformation, token, callback) {
437
+ //aux function used to builf JEXL context.
438
+ //it returns a flat object from an Attr array
439
+ function reduceAttrToPlainObject(attrs, initObj = {}) {
440
+ if (attrs !== undefined && Array.isArray(attrs)) {
441
+ return attrs.reduce((result, item) => {
442
+ result[item.name] = item.value;
443
+ return result;
444
+ }, initObj);
445
+ } else {
446
+ return initObj;
517
447
  }
518
- ];
519
-
520
- const url = '/ngsi-ld/v1/entityOperations/upsert/?options=update';
521
-
522
- if (typeInformation && typeInformation.type) {
523
- payload[0].type = typeInformation.type;
524
448
  }
449
+ //Make a clone and overwrite
450
+ const idTypeSSSList = pluginUtils.getIdTypeServSubServiceFromDevice(originTypeInformation);
525
451
 
526
- if (config.getConfig().appendMode === false) {
527
- payload.actionType = 'update';
528
- } else {
529
- payload.actionType = 'append';
452
+ //Check mandatory information: type
453
+ if (!originTypeInformation || !originTypeInformation.type) {
454
+ callback(new errors.TypeNotFound(null, entityName, originTypeInformation));
455
+ return;
530
456
  }
531
457
 
532
- const options = NGSIUtils.createRequestObject(url, typeInformation, token);
533
- options.method = 'POST';
458
+ const payload = []; //will store the final payload
459
+ let entities = {};
534
460
 
535
- if (typeInformation && typeInformation.staticAttributes) {
536
- attributes = attributes.concat(typeInformation.staticAttributes);
537
- }
461
+ const currentIsoDate = new Date().toISOString();
462
+ const currentMoment = moment(currentIsoDate);
463
+ //Managing timestamp (mustInsertTimeInstant flag to decide if we should insert Timestamp later on)
464
+ const mustInsertTimeInstant =
465
+ originTypeInformation.timestamp !== undefined ? originTypeInformation.timestamp : false;
538
466
 
539
- if (!typeInformation || !typeInformation.type) {
540
- callback(new errors.TypeNotFound(null, entityName, typeInformation));
541
- return;
467
+ // Check if measures is a single measure or a array of measures (a multimeasure)
468
+ if (originMeasures[0] && !originMeasures[0][0]) {
469
+ originMeasures = [originMeasures];
542
470
  }
543
- const idTypeSSSList = pluginUtils.getIdTypeServSubServiceFromDevice(typeInformation);
544
- logger.debug(context, 'sendUpdateValueNgsiLD \n idTypeSSS are %j ', idTypeSSSList);
545
- const measureAttrsForCtxt = [];
546
471
 
547
- // Check explicitAttrs: adds all final needed attributes to payload
548
- if (
549
- typeInformation.explicitAttrs === undefined ||
550
- (typeof typeInformation.explicitAttrs === 'boolean' && !typeInformation.explicitAttrs)
551
- // explicitAttrs is not defined => default case: all attrs should be included
552
- ) {
553
- // This loop adds all measure values (attributes) into payload entities (entity[0])
554
- for (let i = 0; i < attributes.length; i++) {
555
- if (attributes[i].name && attributes[i].type) {
556
- payload[0][attributes[i].name] = {
557
- value: attributes[i].value,
558
- type: attributes[i].type
559
- };
560
- const metadata = NGSIUtils.getMetaData(typeInformation, attributes[i].name, attributes[i].metadata);
561
- if (metadata) {
562
- payload[0][attributes[i].name].metadata = metadata;
563
- }
564
- } else {
565
- callback(new errors.BadRequest(null, entityName));
566
- return;
567
- }
568
- }
569
- logger.debug(context, 'sendUpdateValueNgsiLD \n pre-initial non-explicitAttrs payload=%j', payload);
570
- // Loop for add attrs from type.information.active (and lazys?) into payload entities (entity[0])
571
- if (typeInformation.active) {
572
- typeInformation.active.forEach((attr) => {
573
- if (attr.expression) {
574
- if (attr.object_id) {
575
- payload[0][attr.object_id] = {
576
- value: payload[0][attr.object_id] ? payload[0][attr.object_id].value : undefined,
577
- type: attr.type,
578
- object_id: attr.object_id
579
- };
580
- } else {
581
- payload[0][attr.name] = {
582
- value: payload[0][attr.name] ? payload[0][attr.name].value : undefined,
583
- type: attr.type
584
- };
585
- }
586
- }
587
- });
588
- }
589
- } else {
590
- let selectedAttrs = [];
591
- if (typeof typeInformation.explicitAttrs === 'string') {
592
- // explicitAttrs is a jexlExpression
593
- // This ctxt should include all possible attrs
594
- const attributesCtxt = [];
595
- if (typeInformation.static) {
596
- typeInformation.static.forEach(function (att) {
597
- attributesCtxt.push(att);
598
- });
599
- }
600
- // Measures
601
- for (let i = 0; i < attributes.length; i++) {
602
- if (attributes[i].name && attributes[i].type) {
603
- const measureAttr = {
604
- name: attributes[i].name,
605
- value: attributes[i].value,
606
- type: attributes[i].type
607
- };
608
- attributesCtxt.push(measureAttr);
609
- }
610
- }
611
- // This context is just to calculate explicitAttrs when is an expression
612
- let ctxt = expressionPlugin.extractContext(attributesCtxt.concat(idTypeSSSList));
613
- // typeInformation.active attrs with expressions expanded by current ctxt
614
- if (typeInformation.active) {
615
- typeInformation.active.forEach(function (att) {
616
- if (att.expression) {
617
- if (expressionPlugin.contextAvailable(att.expression, ctxt, typeInformation)) {
618
- const expandedAttr = {
619
- name: att.name,
620
- value: att.expression, // it doesn't matter final value here
621
- type: att.type
622
- };
623
- attributesCtxt.push(expandedAttr);
624
- ctxt = expressionPlugin.extractContext(attributesCtxt.concat(idTypeSSSList));
625
- }
626
- }
627
- });
628
- }
629
- // calculate expression for explicitAttrs
630
- try {
631
- const res = jexlParser.applyExpression(typeInformation.explicitAttrs, ctxt, typeInformation);
632
- if (res === true) {
633
- // like explicitAttrs == true
634
- // selectAttrs should be measures which are defined attributes
635
- typeInformation.active.forEach((attr) => {
636
- selectedAttrs.push(attr.name);
637
- selectedAttrs.push(attr.object_id);
638
- });
639
- } else if (res === false) {
640
- // like explicitAttrs == false
641
- // selectAttrs should be measures and defined attributes
642
- typeInformation.active.forEach((attr) => {
643
- selectedAttrs.push(attr.name);
644
- selectedAttrs.push(attr.object_id);
645
- });
646
- for (let i = 0; i < attributes.length; i++) {
647
- selectedAttrs.push(attributes[i].name);
648
- }
649
- } else {
650
- selectedAttrs = res; // TBD: Check ensure is an array of strings
651
- }
652
- if (selectedAttrs.length === 0) {
653
- // implies do nothing
654
- logger.info(
655
- context,
656
- 'sendUpdateValueNgsiLD \n none selectedAttrs with %j and ctxt %j',
657
- typeInformation.explicitAttrs,
658
- ctxt
659
- );
660
- return callback(null);
661
- }
662
- } catch (e) {
663
- // nothing to do: exception is already logged at info level
664
- }
472
+ for (let measures of originMeasures) {
473
+ entities = {}; //{entityName:{entityType:[attrs]}} //SubGoal Populate entities data structure
474
+ let jexlctxt = {}; //will store the whole context (not just for JEXL)
665
475
 
666
- typeInformation.active.forEach((attr) => {
667
- if (selectedAttrs.includes(attr.name)) {
668
- selectedAttrs.push(attr.object_id);
669
- }
670
- });
671
- } else if (typeInformation.explicitAttrs && typeof typeInformation.explicitAttrs === 'boolean') {
672
- // TBD: selectedAttrs could be a boolean as a result of applyExpression
673
- // explicitAtts is true => Add measures which are defined attributes
674
- typeInformation.active.forEach((attr) => {
675
- selectedAttrs.push(attr.name);
676
- selectedAttrs.push(attr.object_id);
677
- });
678
- }
679
- // This loop adds selected measured values (attributes) into payload entities (entity[0])
680
- for (let i = 0; i < attributes.length; i++) {
681
- if (attributes[i].name && selectedAttrs.includes(attributes[i].name) && attributes[i].type) {
682
- const attr = typeInformation.active.find((obj) => {
683
- return obj.name === attributes[i].name;
684
- });
685
- payload[0][attributes[i].name] = {
686
- value: attributes[i].value,
687
- type: attributes[i].type
688
- };
689
- // ensure payload has attr with proper object_id
690
- if (attr && attr.object_id) {
691
- payload[0][attributes[i].name].object_id = attr.object_id;
692
- }
693
- const metadata = NGSIUtils.getMetaData(typeInformation, attributes[i].name, attributes[i].metadata);
694
- if (metadata) {
695
- payload[0][attributes[i].name].metadata = metadata;
696
- }
697
- } else if (attributes[i].name && !selectedAttrs.includes(attributes[i].name) && attributes[i].type) {
698
- const att = {
699
- name: attributes[i].name,
700
- type: attributes[i].type,
701
- value: attributes[i].value
702
- };
703
- measureAttrsForCtxt.push(att);
476
+ let plainMeasures = null; //will contain measures POJO
477
+ //Make a clone and overwrite
478
+ const typeInformation = JSON.parse(JSON.stringify(originTypeInformation));
479
+
480
+ //Rename all measures with matches with id and type to measure_id and measure_type
481
+ for (const measure of measures) {
482
+ if (measure.name === 'id' || measure.name === 'type') {
483
+ measure.name = constants.MEASURE + measure.name;
704
484
  }
705
485
  }
486
+
487
+ //Make a copy of measures in an plain object: plainMeasures
488
+ plainMeasures = reduceAttrToPlainObject(measures);
489
+ //Build the initital JEXL Context
490
+ //All the measures (avoid references make another copy instead)
491
+ jexlctxt = reduceAttrToPlainObject(measures);
492
+ //All the static
493
+ jexlctxt = reduceAttrToPlainObject(typeInformation.staticAttributes, jexlctxt);
494
+ //id type Service and Subservice
495
+ jexlctxt = reduceAttrToPlainObject(idTypeSSSList, jexlctxt);
496
+
706
497
  logger.debug(
707
498
  context,
708
- 'sendUpdateValueNgsiLD \n pre-initial explicitAttrs payload=%j \n selectedAttrs',
709
- payload,
710
- selectedAttrs
499
+ 'sendUpdateValueNgsiLD loop with: entityName=%s, measures=%j, typeInformation=%j, initial jexlContext=%j, timestamp=%j',
500
+ entityName,
501
+ plainMeasures,
502
+ typeInformation,
503
+ jexlctxt,
504
+ mustInsertTimeInstant
711
505
  );
712
506
 
713
- // Loop for add seleted attrs from type.information.active into pyaload entities (entity[0])
714
- if (typeInformation.active) {
715
- typeInformation.active.forEach((attr) => {
716
- if (selectedAttrs.includes(attr.name)) {
717
- if (attr.object_id) {
718
- payload[0][attr.object_id] = {
719
- value: payload[0][attr.object_id]
720
- ? payload[0][attr.object_id].value
721
- : payload[0][attr.name]
722
- ? payload[0][attr.name].value
723
- : undefined,
724
- type: attr.type,
725
- object_id: attr.object_id
726
- };
727
- } else {
728
- payload[0][attr.name] = {
729
- value: payload[0][attr.name] ? payload[0][attr.name].value : undefined,
730
- type: attr.type
731
- };
732
- }
733
- }
734
- });
507
+ //Now we can calculate the EntityName of primary entity
508
+ let entityNameCalc = null;
509
+ if (typeInformation.entityNameExp !== undefined && typeInformation.entityNameExp !== '') {
510
+ try {
511
+ logger.debug(context, 'sendUpdateValueNgsiLD entityNameExp %j', typeInformation.entityNameExp);
512
+ entityNameCalc = expressionPlugin.applyExpression(
513
+ typeInformation.entityNameExp,
514
+ jexlctxt,
515
+ typeInformation
516
+ );
517
+ } catch (e) {
518
+ logger.debug(
519
+ context,
520
+ 'Error evaluating expression for entityName: %j with context: %j',
521
+ typeInformation.entityNameExp,
522
+ jexlctxt
523
+ );
524
+ }
735
525
  }
736
- } // END check explicitAttrs
737
- logger.debug(context, 'sendUpdateValueNgsiLD \n initial payload=%j', payload);
738
-
739
- const currentEntity = payload[0];
740
526
 
741
- // Prepare attributes for expresionPlugin
742
- const attsArray = pluginUtils.extractAttributesArrayFromNgsi2Entity(currentEntity);
527
+ entityName = entityNameCalc ? entityNameCalc : entityName;
528
+ //enrich JEXL context
529
+ jexlctxt.entity_name = entityName;
743
530
 
744
- // Exclude processing all attr expressions when current attr is of type 'commandStatus' or 'commandResult'
745
- let attsArrayFiltered = [];
746
- if (attsArray) {
747
- attsArrayFiltered = attsArray.filter((obj) => {
748
- return ![constants.COMMAND_STATUS, constants.COMMAND_RESULT].includes(obj.type);
749
- });
750
- }
751
- let attributesCtxt = [...attsArrayFiltered]; // just copy
752
- if (typeInformation.static) {
753
- typeInformation.static.forEach(function (att) {
754
- attributesCtxt.push(att);
755
- });
756
- }
757
- if (measureAttrsForCtxt) {
758
- measureAttrsForCtxt.forEach(function (att) {
759
- attributesCtxt.push(att);
760
- });
761
- }
762
- attributesCtxt = attributesCtxt.concat(idTypeSSSList);
763
- const ctxt = expressionPlugin.extractContext(attributesCtxt);
764
- logger.debug(context, 'sendUpdateValueNgsiLD \n initial ctxt %j ', ctxt);
765
-
766
- // Sort currentEntity to get first attrs without expressions (checking attrs in typeInformation.active)
767
- // attributes without expressions should be processed before
768
- logger.debug(context, 'sendUpdateValueNgsiLD \n currentEntity %j ', currentEntity);
769
- if (typeInformation.active && typeInformation.active.length > 0) {
770
- for (const k in currentEntity) {
771
- typeInformation.active.forEach(function (att) {
772
- if (
773
- (att.object_id && att.object_id === k && att.expression) ||
774
- (att.name && att.name === k && att.expression)
775
- ) {
776
- const m = currentEntity[k];
777
- delete currentEntity[k];
778
- currentEntity[k] = m; // put into the end of currentEntity
779
- }
780
- });
531
+ let preprocessedAttr = [];
532
+ //Add Raw Static, Lazy, Command and Actives attr attributes
533
+ if (typeInformation && typeInformation.staticAttributes) {
534
+ preprocessedAttr = preprocessedAttr.concat(typeInformation.staticAttributes);
781
535
  }
782
- }
783
- logger.debug(context, 'sendUpdateValueNgsiLD \n currentEntity sorted %j ', currentEntity);
784
- let timestampValue;
785
- // Loop for each final attribute to apply alias, multientity and expressions
786
- for (const j in currentEntity) {
787
- // discard id and type
788
- if (j !== 'id' || j !== 'type') {
789
- // Apply Mapping Alias: object_id in attributes are in typeInformation.active
790
- let attr;
791
- let newAttr = payload[0][j];
792
- if (typeInformation.active) {
793
- attr = typeInformation.active.find((obj) => {
794
- return obj.object_id === j;
795
- });
796
- }
797
- if (!attr) {
798
- if (typeInformation.lazy) {
799
- attr = typeInformation.lazy.find((obj) => {
800
- return obj.object_id === j;
801
- });
802
- }
803
- }
804
- if (!attr) {
805
- if (typeInformation.active) {
806
- attr = typeInformation.active.find((obj) => {
807
- return obj.name === j;
808
- });
809
- }
810
- }
811
- if (attr && attr.name) {
812
- if (['id', 'type'].includes(attr.name)) {
813
- // invalid mapping
536
+ if (typeInformation && typeInformation.lazy) {
537
+ preprocessedAttr = preprocessedAttr.concat(typeInformation.lazy);
538
+ }
539
+ if (typeInformation && typeInformation.active) {
540
+ preprocessedAttr = preprocessedAttr.concat(typeInformation.active);
541
+ }
542
+
543
+ //Proccess every proto Attribute to populate entities data steuture
544
+ entities[entityName] = {};
545
+ entities[entityName][typeInformation.type] = [];
546
+
547
+ for (const currentAttr of preprocessedAttr) {
548
+ let hitted = false; //any measure, expressiom or value hit the attr (avoid propagate "silent attr" with null values )
549
+ let attrEntityName = entityName;
550
+ let attrEntityType = typeInformation.type;
551
+ let valueExpression = null;
552
+ //manage active attr without object__id (name by default)
553
+ currentAttr.object_id = currentAttr.object_id ? currentAttr.object_id : currentAttr.name;
554
+ //Enrich the attr (skip, hit, value, meta-timeInstant)
555
+ currentAttr.skipValue = currentAttr.skipValue ? currentAttr.skipValue : null;
556
+
557
+ //determine AttrEntityName for multientity
558
+ if (
559
+ currentAttr.entity_name !== null &&
560
+ currentAttr.entity_name !== undefined &&
561
+ currentAttr.entity_name !== '' &&
562
+ typeof currentAttr.entity_name === 'string'
563
+ ) {
564
+ try {
565
+ logger.debug(
566
+ context,
567
+ 'Evaluating attribute: %j, for entity_name(exp):%j, with ctxt: %j',
568
+ currentAttr.name,
569
+ currentAttr.entity_name,
570
+ jexlctxt
571
+ );
572
+ attrEntityName = jexlParser.applyExpression(currentAttr.entity_name, jexlctxt, typeInformation);
573
+ if (!attrEntityName) {
574
+ attrEntityName = currentAttr.entity_name;
575
+ }
576
+ } catch (e) {
814
577
  logger.debug(
815
578
  context,
816
- 'sendUpdateValueNgsiLD \n invalid mapping for attr %j \n newAttr %j',
817
- attr,
818
- newAttr
579
+ 'Exception evaluating entityNameExp:%j, with jexlctxt: %j',
580
+ currentAttr.entity_name,
581
+ jexlctxt
819
582
  );
820
- delete payload[0][attr.object_id];
821
- attr = undefined; // stop processing attr
822
- newAttr = undefined;
823
- } else {
824
- ctxt[attr.name] = payload[0][j].value;
583
+ attrEntityName = currentAttr.entity_name;
825
584
  }
826
585
  }
827
- logger.debug(
828
- context,
829
- 'sendUpdateValueNgsiLD \n procesing j %j attr %j ctxt %j \n newAttr %j ',
830
- j,
831
- attr,
832
- ctxt,
833
- newAttr
834
- );
835
586
 
836
- if (attr && attr.type) {
837
- if (attr.type !== 'GeoProperty') {
838
- newAttr.type = attr.type;
839
- }
587
+ //determine AttrEntityType for multientity
588
+ if (
589
+ currentAttr.entity_type !== null &&
590
+ currentAttr.entity_type !== undefined &&
591
+ currentAttr.entity_type !== '' &&
592
+ typeof currentAttr.entity_type === 'string'
593
+ ) {
594
+ attrEntityType = currentAttr.entity_type;
840
595
  }
841
596
 
842
- // Apply expression
843
- if (attr && attr.expression) {
844
- logger.debug(
845
- context,
846
- 'sendUpdateValueNgsiLD \n apply expression %j \n over ctxt %j \n and device %j',
847
- attr.expression,
848
- ctxt,
849
- typeInformation
850
- );
851
- let res = null;
597
+ //PRE POPULATE CONTEXT
598
+ jexlctxt[currentAttr.name] = plainMeasures[currentAttr.object_id];
599
+
600
+ //determine Value
601
+ if (currentAttr.value !== undefined) {
602
+ //static attributes already have a value
603
+ hitted = true;
604
+ valueExpression = currentAttr.value;
605
+ } else if (plainMeasures[currentAttr.object_id] !== undefined) {
606
+ //we have got a meaure for that Attr
607
+ //actives ¿lazis?
608
+ hitted = true;
609
+ valueExpression = plainMeasures[currentAttr.object_id];
610
+ }
611
+ //remove measures that has been shadowed by an alias (some may be left and managed later)
612
+ //Maybe we must filter object_id if there is name == object_id
613
+ measures = measures.filter((item) => item.name !== currentAttr.object_id && item.name !== currentAttr.name);
614
+
615
+ if (
616
+ currentAttr.expression !== undefined &&
617
+ currentAttr.expression !== '' &&
618
+ typeof currentAttr.expression === 'string'
619
+ ) {
852
620
  try {
853
- if (expressionPlugin.contextAvailable(attr.expression, ctxt, typeInformation)) {
854
- res = expressionPlugin.applyExpression(attr.expression, ctxt, typeInformation);
855
- } else {
856
- logger.warn(
857
- context,
858
- 'sendUpdateValueNgsiLD \n no context available for apply expression %j \n',
859
- attr.expression
860
- );
861
- res = newAttr.value; // keep newAttr value
621
+ hitted = true;
622
+ valueExpression = jexlParser.applyExpression(currentAttr.expression, jexlctxt, typeInformation);
623
+ //we fallback to null if anything unexpecte happend
624
+ if (valueExpression === null || valueExpression === undefined || Number.isNaN(valueExpression)) {
625
+ valueExpression = null;
862
626
  }
863
627
  } catch (e) {
864
- logger.error(context, 'sendUpdateValueNgsiLD \n apply expression exception %j \n', e);
865
- res = ctxt[attr.name]; // TBD: add reference to test
628
+ valueExpression = null;
866
629
  }
867
-
868
- // jexl expression plugin
869
- newAttr.value = res;
870
-
871
630
  logger.debug(
872
631
  context,
873
- 'sendUpdateValueNgsiLD \n apply expression result %j \n newAttr %j',
874
- res,
875
- newAttr
632
+ 'Evaluated attr: %j, with expression: %j, and ctxt: %j resulting: %j',
633
+ currentAttr.name,
634
+ currentAttr.expression,
635
+ jexlctxt,
636
+ valueExpression
876
637
  );
877
638
  }
878
639
 
879
- // Apply Multientity: entity_type and entity_name in attributes are in typeInformation.active
880
- if (attr && (attr.entity_type || attr.entity_name)) {
881
- // Create a newEntity for this attribute
882
- let newEntityName = null;
883
- if (attr.entity_name) {
884
- try {
885
- if (expressionPlugin.contextAvailable(attr.entity_name, ctxt, typeInformation)) {
886
- newEntityName = expressionPlugin.applyExpression(attr.entity_name, ctxt, typeInformation);
887
- } else {
888
- logger.warn(
889
- context,
890
- 'sendUpdateValueNgsiLD \n MULTI no context available for apply expression %j \n',
891
- attr.entity_name
640
+ currentAttr.hitted = hitted;
641
+ currentAttr.value = valueExpression;
642
+
643
+ //store de New Attributte in entity data structure
644
+ if (hitted === true) {
645
+ if (entities[attrEntityName] === undefined) {
646
+ entities[attrEntityName] = {};
647
+ }
648
+ if (entities[attrEntityName][attrEntityType] === undefined) {
649
+ entities[attrEntityName][attrEntityType] = [];
650
+ }
651
+ //store de New Attributte
652
+ entities[attrEntityName][attrEntityType].push(currentAttr);
653
+ }
654
+
655
+ //RE-Populate de JEXLcontext (except for null or NaN we preffer undefined)
656
+ jexlctxt[currentAttr.name] = valueExpression;
657
+
658
+ // Expand metadata value expression
659
+ if (currentAttr.metadata) {
660
+ for (const metaKey in currentAttr.metadata) {
661
+ if (currentAttr.metadata[metaKey].expression && metaKey !== constants.TIMESTAMP_ATTRIBUTE) {
662
+ const newAttrMeta = {};
663
+ if (currentAttr.metadata[metaKey].type) {
664
+ newAttrMeta.type = currentAttr.metadata[metaKey].type;
665
+ }
666
+ let metaValueExpression;
667
+ try {
668
+ metaValueExpression = jexlParser.applyExpression(
669
+ currentAttr.metadata[metaKey].expression,
670
+ jexlctxt,
671
+ typeInformation
892
672
  );
893
- newEntityName = attr.entity_name;
673
+ //we fallback to null if anything unexpecte happend
674
+ if (
675
+ metaValueExpression === null ||
676
+ metaValueExpression === undefined ||
677
+ Number.isNaN(metaValueExpression)
678
+ ) {
679
+ metaValueExpression = null;
680
+ }
681
+ } catch (e) {
682
+ metaValueExpression = null;
894
683
  }
895
- newEntityName = newEntityName ? newEntityName : attr.entity_name;
896
- } catch (e) {
897
- logger.error(context, 'sendUpdateValueNgsiLD \n MULTI apply expression exception %j \n', e);
898
- newEntityName = attr.entity_name;
684
+ newAttrMeta.value = metaValueExpression;
685
+ currentAttr.metadata[metaKey] = newAttrMeta;
899
686
  }
900
- logger.debug(
901
- context,
902
- 'sendUpdateValueNgsiLD \n MULTI apply expression %j \n result %j \n payload %j',
903
- attr.entity_name,
904
- newEntityName,
905
- payload
906
- );
907
687
  }
688
+ }
689
+ }
908
690
 
909
- let newEntity = {
910
- id: newEntityName ? newEntityName : payload[0].id,
911
- type: attr.entity_type ? attr.entity_type : payload[0].type
912
- };
913
- // Check if there is already a newEntity created
914
- const alreadyEntity = payload.find((entity) => {
915
- return entity.id === newEntity.id && entity.type === newEntity.type;
916
- });
917
- if (alreadyEntity) {
918
- // Use alreadyEntity
919
- alreadyEntity[attr.name] = newAttr;
920
- } else {
921
- // Add newEntity to payload
922
- newEntity[attr.name] = newAttr;
923
- if (
924
- 'timestamp' in typeInformation && typeInformation.timestamp !== undefined
925
- ? typeInformation.timestamp
926
- : config.getConfig().timestamp !== undefined
927
- ? config.getConfig().timestamp
928
- : timestampValue !== undefined
929
- ) {
930
- newEntity = addTimestamp(newEntity, typeInformation.timezone, timestampValue);
931
- logger.debug(context, 'sendUpdateValueNgsiLD \n timestamped newEntity=%j', newEntity);
932
- }
933
- payload.push(newEntity);
934
- }
935
- if (attr && attr.name) {
936
- if (attr.name !== j) {
937
- logger.debug(
938
- context,
939
- 'sendUpdateValueNgsiLD \n MULTI remove measure attr %j keep alias j %j from %j \n',
940
- j,
941
- attr,
942
- payload
943
- );
944
- delete payload[0][j];
945
- }
691
+ //now we can compute explicit (Bool or Array) with the complete JexlContext
692
+ let explicit = false;
693
+ if (typeof typeInformation.explicitAttrs === 'string') {
694
+ try {
695
+ explicit = jexlParser.applyExpression(typeInformation.explicitAttrs, jexlctxt, typeInformation);
696
+ if (explicit instanceof Array && explicit.length > 0 && mustInsertTimeInstant) {
697
+ explicit.push(constants.TIMESTAMP_ATTRIBUTE);
946
698
  }
947
- // if (attr && (attr.entity_type || attr.entity_name))
948
- } else {
949
- // Not a multientity attr
950
- if (attr && attr.name) {
951
- payload[0][attr.name] = newAttr;
952
- if (attr.name !== j) {
953
- delete payload[0][j]; // keep alias name, remove measure name
699
+ logger.debug(
700
+ context,
701
+ 'Calculated explicitAttrs with expression: %j and ctxt: %j resulting: %j',
702
+ typeInformation.explicitAttrs,
703
+ jexlctxt,
704
+ explicit
705
+ );
706
+ } catch (e) {
707
+ // nothing to do: exception is already logged at info level
708
+ }
709
+ } else if (typeof typeInformation.explicitAttrs === 'boolean') {
710
+ explicit = typeInformation.explicitAttrs;
711
+ }
712
+
713
+ //more mesures may be added to the attribute list (unnhandled/left mesaures) l
714
+ if (explicit === false && Object.keys(measures).length > 0) {
715
+ entities[entityName][typeInformation.type] = entities[entityName][typeInformation.type].concat(measures);
716
+ }
717
+
718
+ //PRE-PROCESSING FINISHED
719
+ //Explicit ATTRS and SKIPVALUES will be managed while we build NGSI payload
720
+ //Get ready to build and send NGSI payload (entities-->payload)
721
+
722
+ for (const ename in entities) {
723
+ for (const etype in entities[ename]) {
724
+ const e = {};
725
+ e.id = String(ename);
726
+ e.type = String(etype);
727
+ const timestamp = { type: constants.TIMESTAMP_TYPE_NGSI2 }; //timestamp scafold-attr for insertions.
728
+ let timestampAttrs = null;
729
+ if (mustInsertTimeInstant) {
730
+ // get timestamp for current entity
731
+
732
+ timestampAttrs = entities[ename][etype].filter(
733
+ (item) => item.name === constants.TIMESTAMP_ATTRIBUTE
734
+ );
735
+ if (timestampAttrs && timestampAttrs.length > 0) {
736
+ timestamp.value = timestampAttrs[0].value;
954
737
  }
955
- }
956
- if (newAttr && newAttr.type === constants.TIMESTAMP_TYPE_NGSI2 && newAttr.value) {
957
- const extendedTime = compressTimestampPlugin.fromBasicToExtended(newAttr.value);
958
- if (extendedTime) {
959
- // TBD: there is not flag about compressTimestamp in iotagent-node-lib,
960
- // but there is one in agents
961
- newAttr.value = extendedTime;
738
+
739
+ if (timestamp.value) {
740
+ if (!moment(timestamp.value, moment.ISO_8601, true).isValid()) {
741
+ callback(new errors.BadTimestamp(timestamp.value, entityName, typeInformation));
742
+ return;
743
+ }
744
+ } else if (!typeInformation.timezone) {
745
+ timestamp.value = currentIsoDate;
746
+ jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value;
747
+ } else {
748
+ timestamp.value = currentMoment
749
+ .tz(typeInformation.timezone)
750
+ .format('YYYY-MM-DD[T]HH:mm:ss.SSSZ');
751
+ jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value;
962
752
  }
963
753
  }
964
- if (j === constants.TIMESTAMP_ATTRIBUTE) {
965
- if (newAttr && newAttr.type === constants.TIMESTAMP_TYPE_NGSI2 && newAttr.value) {
966
- timestampValue = newAttr.value;
967
- logger.debug(
968
- context,
969
- 'sendUpdateValueNgsiLD \n newAttr is TimeInstant and new payload=%j',
970
- payload
971
- );
754
+ //extract attributes
755
+ let isEmpty = true;
756
+ for (const attr of entities[ename][etype]) {
757
+ if (
758
+ attr.name !== 'id' &&
759
+ attr.name !== 'type' &&
760
+ (attr.value !== attr.skipValue || attr.skipValue === undefined) &&
761
+ (attr.hitted || attr.hitted === undefined) && //undefined is for pure measures
762
+ (typeof explicit === 'boolean' || //true and false already handled
763
+ (explicit instanceof Array && //check the array version
764
+ (explicit.includes(attr.name) ||
765
+ explicit.some(
766
+ (item) => attr.object_id !== undefined && item.object_id === attr.object_id
767
+ ))))
768
+ ) {
769
+ isEmpty = false;
770
+ if (mustInsertTimeInstant) {
771
+ // Add TimeInstant to all attribute metadata of all entities
772
+ if (attr.name !== constants.TIMESTAMP_ATTRIBUTE) {
773
+ if (!attr.metadata) {
774
+ attr.metadata = {};
775
+ }
776
+ attr.metadata[constants.TIMESTAMP_ATTRIBUTE] = timestamp;
777
+ }
778
+ }
779
+ e[attr.name] = { type: attr.type, value: attr.value, metadata: attr.metadata };
972
780
  }
973
781
  }
974
- if (
975
- newAttr &&
976
- newAttr.metadata &&
977
- newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE] &&
978
- newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].type === constants.TIMESTAMP_TYPE_NGSI2 &&
979
- newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].value
980
- ) {
981
- const extendedTime = compressTimestampPlugin.fromBasicToExtended(
982
- newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].value
983
- );
984
- if (extendedTime) {
985
- newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].value = extendedTime;
782
+ if (!isEmpty) {
783
+ if (mustInsertTimeInstant) {
784
+ e[constants.TIMESTAMP_ATTRIBUTE] = timestamp;
986
785
  }
786
+ payload.push(e);
987
787
  }
988
788
  }
989
- } // if (j !== 'id' || j !== 'type')
990
-
991
- // final attr loop
992
- logger.debug(
993
- context,
994
- 'sendUpdateValueNgsiLD \n after procesing attr %j \n current entity %j \n current payload=%j',
995
- j,
996
- currentEntity,
997
- payload
998
- );
999
- }
1000
- // for attr loop
1001
-
1002
- // Add timestamp to paylaod
1003
- if (
1004
- 'timestamp' in typeInformation && typeInformation.timestamp !== undefined
1005
- ? typeInformation.timestamp
1006
- : config.getConfig().timestamp !== undefined
1007
- ? config.getConfig().timestamp
1008
- : timestampValue !== undefined
1009
- ) {
1010
- if (timestampValue) {
1011
- // timeInstant is provided as measure
1012
- if (Object.keys(payload[0]).length > 1) {
1013
- // include metadata with TimeInstant in attrs when TimeInstant is provided as measure in all entities
1014
- payload[0] = addTimestamp(payload[0], typeInformation.timezone, timestampValue);
1015
- }
1016
- } else {
1017
- // jshint maxdepth:5
1018
- for (let n = 0; n < payload.length; n++) {
1019
- if (!utils.isTimestampedNgsi2(payload[n])) {
1020
- // legacy check needed?
1021
- payload[n] = addTimestamp(payload[n], typeInformation.timezone);
1022
- // jshint maxdepth:5
1023
- } else if (!utils.IsValidTimestampedNgsi2(payload[n])) {
1024
- // legacy check needed?
1025
- logger.error(context, 'Invalid timestamp:%s', JSON.stringify(payload[0]));
1026
- callback(new errors.BadTimestamp(payload, entityName, typeInformation));
1027
- return;
1028
- }
1029
- }
1030
- }
1031
- }
1032
-
1033
- logger.debug(context, 'sendUpdateValueNgsiLD \n ending payload=%j', payload);
1034
-
1035
- for (let m = 0; m < payload.length; m++) {
1036
- for (const key in payload[m]) {
1037
- // purge object_id from payload
1038
- if (payload[m][key] && payload[m][key].object_id) {
1039
- delete payload[m][key].object_id;
1040
- }
1041
789
  }
1042
- }
1043
- logger.debug(context, 'sendUpdateValueNgsiLD \n payload and without object_id %j', payload);
790
+ } // end for (let measures of originMeasures)
1044
791
 
792
+ const url = '/ngsi-ld/v1/entityOperations/upsert/?options=update';
793
+ const options = NGSIUtils.createRequestObject(url, originTypeInformation, token);
1045
794
  options.json = payload;
795
+
1046
796
  try {
1047
797
  if (payload instanceof Array) {
1048
798
  options.json = _.map(options.json, formatAsNGSILD);
1049
799
  } else {
1050
800
  options.json.id = entityName;
1051
- options.json.type = typeInformation.type;
801
+ options.json.type = originTypeInformation.type;
1052
802
  options.json = [formatAsNGSILD(options.json)];
1053
803
  }
1054
804
  } catch (error) {
1055
- return callback(new errors.BadGeocoordinates(JSON.stringify(payload), typeInformation));
805
+ return callback(new errors.BadGeocoordinates(JSON.stringify(payload), originTypeInformation));
1056
806
  }
1057
807
 
1058
- if (typeInformation.active) {
1059
- addLinkedEntities(typeInformation, options.json);
808
+ if (originTypeInformation.active) {
809
+ addLinkedEntities(originTypeInformation, options.json);
1060
810
  }
1061
811
 
1062
812
  // Prevent to update an entity with an empty payload
@@ -1068,7 +818,7 @@ function sendUpdateValueNgsiLD(entityName, attributes, typeInformation, token, c
1068
818
  logger.debug(context, 'Using the following NGSI LD request:\n\n%s\n\n', JSON.stringify(options, null, 4));
1069
819
  request(
1070
820
  options,
1071
- generateNGSILDOperationHandler('update', entityName, typeInformation, token, options, callback)
821
+ generateNGSILDOperationHandler('update', entityName, originTypeInformation, token, options, callback)
1072
822
  );
1073
823
  } else {
1074
824
  logger.debug(