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