iotagent-node-lib 3.4.4 → 4.0.1

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 (95) hide show
  1. package/README.md +4 -0
  2. package/doc/admin.md +1 -13
  3. package/doc/api.md +116 -18
  4. package/doc/devel/architecture.md +0 -12
  5. package/doc/index.md +1 -1
  6. package/doc/roadmap.md +22 -10
  7. package/lib/commonConfig.js +0 -11
  8. package/lib/errors.js +2 -2
  9. package/lib/model/Device.js +2 -1
  10. package/lib/model/Group.js +2 -1
  11. package/lib/model/dbConn.js +22 -11
  12. package/lib/plugins/expressionPlugin.js +0 -5
  13. package/lib/plugins/jexlParser.js +15 -31
  14. package/lib/services/common/genericMiddleware.js +14 -2
  15. package/lib/services/common/iotManagerService.js +2 -1
  16. package/lib/services/devices/deviceRegistryMongoDB.js +3 -1
  17. package/lib/services/devices/deviceService.js +16 -21
  18. package/lib/services/devices/devices-NGSI-LD.js +5 -98
  19. package/lib/services/devices/devices-NGSI-mixed.js +0 -14
  20. package/lib/services/devices/devices-NGSI-v2.js +3 -0
  21. package/lib/services/groups/groupRegistryMemory.js +0 -25
  22. package/lib/services/groups/groupRegistryMongoDB.js +20 -19
  23. package/lib/services/groups/groupService.js +3 -14
  24. package/lib/services/ngsi/entities-NGSI-LD.js +82 -7
  25. package/lib/services/ngsi/entities-NGSI-v2.js +297 -696
  26. package/lib/services/ngsi/ngsiUtils.js +0 -30
  27. package/lib/services/northBound/deviceProvisioningServer.js +6 -3
  28. package/lib/templates/createDevice.json +4 -0
  29. package/lib/templates/createDeviceLax.json +4 -0
  30. package/lib/templates/deviceGroup.json +4 -0
  31. package/lib/templates/updateDevice.json +4 -0
  32. package/lib/templates/updateDeviceLax.json +4 -0
  33. package/package.json +6 -2
  34. package/test/functional/README.md +378 -0
  35. package/test/functional/config-test.js +70 -0
  36. package/test/functional/functional-tests-runner.js +126 -0
  37. package/test/functional/functional-tests.js +241 -0
  38. package/test/functional/testCases.js +2944 -0
  39. package/test/functional/testUtils.js +251 -0
  40. package/test/tools/utils.js +25 -0
  41. package/test/unit/mongodb/mongodb-connectionoptions-test.js +35 -22
  42. package/test/unit/ngsi-ld/examples/contextRequests/createProvisionedDeviceWithGroupAndStatic2.json +3 -34
  43. package/test/unit/ngsi-ld/examples/contextRequests/updateContextAliasPlugin6.json +8 -1
  44. package/test/unit/ngsi-ld/examples/contextRequests/updateContextAliasPlugin7.json +1 -4
  45. package/test/unit/ngsi-ld/examples/contextRequests/updateContextAliasPlugin8.json +1 -6
  46. package/test/unit/ngsi-ld/general/contextBrokerOAuthSecurityAccess-test.js +67 -87
  47. package/test/unit/ngsi-ld/lazyAndCommands/command-test.js +7 -13
  48. package/test/unit/ngsi-ld/lazyAndCommands/merge-patch-test.js +43 -43
  49. package/test/unit/ngsi-ld/lazyAndCommands/polling-commands-test.js +19 -29
  50. package/test/unit/ngsi-ld/ngsiService/languageProperties-test.js +0 -1
  51. package/test/unit/ngsi-ld/ngsiService/subscriptions-test.js +35 -46
  52. package/test/unit/ngsi-ld/plugins/alias-plugin_test.js +8 -9
  53. package/test/unit/ngsi-ld/provisioning/device-provisioning-api_test.js +96 -221
  54. package/test/unit/ngsi-ld/provisioning/device-registration_test.js +18 -27
  55. package/test/unit/ngsi-ld/provisioning/device-update-registration_test.js +8 -16
  56. package/test/unit/ngsi-ld/provisioning/updateProvisionedDevices-test.js +0 -13
  57. package/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin8.json +4 -4
  58. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin29b.json +8 -0
  59. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin30.json +1 -1
  60. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin32.json +0 -6
  61. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin34.json +8 -0
  62. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin34b.json +14 -0
  63. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin35.json +1 -11
  64. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin36b.json +13 -0
  65. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin37.json +8 -0
  66. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin41.json +1 -11
  67. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin10b.json +37 -0
  68. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin11.json +0 -4
  69. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin15.json +0 -4
  70. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin16.json +0 -4
  71. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin25.json +4 -0
  72. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin4.json +0 -3
  73. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin5.json +10 -12
  74. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin6.json +0 -4
  75. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin7.json +1 -5
  76. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin8.json +8 -12
  77. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin2.json +0 -4
  78. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin3.json +0 -8
  79. package/test/unit/ngsiv2/examples/contextRequests/updateContextStaticAttributesMetadata.json +7 -1
  80. package/test/unit/ngsiv2/expressions/jexlBasedTransformations-test.js +898 -28
  81. package/test/unit/ngsiv2/ngsiService/active-devices-test.js +0 -4
  82. package/test/unit/ngsiv2/ngsiService/staticAttributes-test.js +267 -0
  83. package/test/unit/ngsiv2/plugins/alias-plugin_test.js +19 -21
  84. package/test/unit/ngsiv2/plugins/multientity-plugin_test.js +21 -24
  85. package/test/unit/ngsiv2/provisioning/device-group-utils-test.js +1 -21
  86. package/test/unit/ngsiv2/provisioning/device-provisioning-api_test.js +4 -6
  87. package/CHANGES_NEXT_RELEASE +0 -0
  88. package/test/unit/ngsi-ld/ngsiService/autocast-test.js +0 -438
  89. package/test/unit/ngsi-ld/ngsiService/geoproperties-test.js +0 -381
  90. package/test/unit/ngsi-ld/provisioning/singleConfigurationMode-test.js +0 -311
  91. package/test/unit/ngsiv2/ngsiService/autocast-test.js +0 -325
  92. package/test/unit/ngsiv2/ngsiService/geoproperties-test.js +0 -427
  93. package/test/unit/ngsiv2/plugins/compress-timestamp-plugin_test.js +0 -217
  94. package/test/unit/ngsiv2/plugins/timestamp-processing-plugin_test.js +0 -119
  95. package/test/unit/ngsiv2/provisioning/singleConfigurationMode-test.js +0 -309
@@ -30,13 +30,11 @@
30
30
  const request = require('../../request-shim');
31
31
  const alarms = require('../common/alarmManagement');
32
32
  const errors = require('../../errors');
33
- const utils = require('../northBound/restUtils');
34
33
  const pluginUtils = require('../../plugins/pluginUtils');
35
34
  const config = require('../../commonConfig');
36
35
  const constants = require('../../constants');
37
36
  const jexlParser = require('../../plugins/jexlParser');
38
37
  const expressionPlugin = require('../../plugins/expressionPlugin');
39
- const compressTimestampPlugin = require('../../plugins/compressTimestamp');
40
38
  const moment = require('moment-timezone');
41
39
  const NGSIUtils = require('./ngsiUtils');
42
40
  const logger = require('logops');
@@ -94,83 +92,6 @@ function formatGeoAttrs(attr) {
94
92
  return obj;
95
93
  }
96
94
 
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) {
108
- const timestamp = {
109
- type: constants.TIMESTAMP_TYPE_NGSI2
110
- };
111
-
112
- if (timestampValue) {
113
- timestamp.value = timestampValue;
114
- } else if (!timezone) {
115
- timestamp.value = new Date().toISOString();
116
- } else {
117
- timestamp.value = moment().tz(timezone).format('YYYY-MM-DD[T]HH:mm:ss.SSSZ');
118
- }
119
-
120
- function addMetadata(attribute) {
121
- let timestampFound = false;
122
-
123
- if (!attribute.metadata) {
124
- attribute.metadata = {};
125
- }
126
-
127
- for (let i = 0; i < attribute.metadata.length; i++) {
128
- if (attribute.metadata[i] === constants.TIMESTAMP_ATTRIBUTE) {
129
- if (
130
- attribute.metadata[constants.TIMESTAMP_ATTRIBUTE].type === constants.TIMESTAMP_TYPE_NGSI2 &&
131
- attribute.metadata[constants.TIMESTAMP_ATTRIBUTE].value === timestamp.value
132
- ) {
133
- timestampFound = true;
134
- break;
135
- }
136
- }
137
- }
138
-
139
- if (!timestampFound) {
140
- attribute.metadata[constants.TIMESTAMP_ATTRIBUTE] = timestamp;
141
- }
142
-
143
- return attribute;
144
- }
145
- let keyCount = 0;
146
- for (const key in entity) {
147
- /* eslint-disable-next-line no-prototype-builtins */
148
- if (entity.hasOwnProperty(key) && key !== 'id' && key !== 'type') {
149
- addMetadata(entity[key]);
150
- keyCount += 1;
151
- }
152
- }
153
- // Add timestamp just to entity with attrs: multientity plugin could
154
- // create empty entities just with id and type.
155
- if (keyCount > 0) {
156
- entity[constants.TIMESTAMP_ATTRIBUTE] = timestamp;
157
- }
158
-
159
- return entity;
160
- }
161
-
162
- if (payload instanceof Array) {
163
- for (let i = 0; i < payload.length; i++) {
164
- if (!utils.isTimestampedNgsi2(payload[i])) {
165
- payload[i] = addTimestampEntity(payload[i], timezone, timestampValue);
166
- }
167
- }
168
-
169
- return payload;
170
- }
171
- return addTimestampEntity(payload, timezone, timestampValue);
172
- }
173
-
174
95
  /**
175
96
  * Generate an operation handler for NGSIv2-based operations (query and update). The handler takes care of identifiying
176
97
  * the errors and calling the appropriate callback with a success or a failure depending on how the operation ended.
@@ -328,689 +249,369 @@ function sendQueryValueNgsi2(entityName, attributes, typeInformation, token, cal
328
249
  * array should comply to the NGSIv2's attribute format.
329
250
  *
330
251
  * @param {String} entityName Name of the entity to register.
331
- * @param {Array} attributes Attribute array containing the values to update.
252
+ * @param {Array} measures measure array containing the values to update.
332
253
  * @param {Object} typeInformation Configuration information for the device.
333
254
  * @param {String} token User token to identify against the PEP Proxies (optional).
334
255
  */
335
- function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, callback) {
336
- logger.debug(
337
- context,
338
- 'sendUpdateValueNgsi2 called with: entityName=%s attributes=%j typeInformation=%j',
339
- entityName,
340
- attributes,
341
- typeInformation
342
- );
343
- // if any measure has name 'id' or 'type' it should be removed
344
- // (as they cannot be progressed as attribute names, given that 'id' or 'type' are forbidden names for attributes in CB)
345
- var attributesWithoutIdType = [];
346
- attributes.forEach(function (attribute) {
347
- if (attribute.name !== 'id' && attribute.name !== 'type') {
348
- attributesWithoutIdType.push(attribute);
256
+ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, callback) {
257
+ //aux function used to builf JEXL context.
258
+ //it returns a flat object from an Attr array
259
+ function reduceAttrToPlainObject(attrs, initObj = {}) {
260
+ if (attrs !== undefined && Array.isArray(attrs)) {
261
+ return attrs.reduce((result, item) => {
262
+ result[item.name] = item.value;
263
+ return result;
264
+ }, initObj);
265
+ } else {
266
+ return initObj;
349
267
  }
350
- });
351
- attributes = attributesWithoutIdType;
352
-
353
- const payload = {
354
- entities: [
355
- {
356
- // CB entity id should be always a String
357
- id: String(entityName)
358
- }
359
- ]
360
- };
361
-
362
- let url = '/v2/op/update';
363
-
364
- if (typeInformation && typeInformation.type) {
365
- // CB entity type should be always a String
366
- payload.entities[0].type = String(typeInformation.type);
367
268
  }
368
269
 
369
- payload.actionType = 'append';
370
-
371
- let options = NGSIUtils.createRequestObject(url, typeInformation, token);
270
+ let entities = {}; //{entityName:{entityType:[attrs]}} //SubGoal Populate entoties data striucture
271
+ let jexlctxt = {}; //will store the whole context (not just for JEXL)
272
+ let payload = {}; //will store the final payload
273
+ let timestamp = { type: constants.TIMESTAMP_TYPE_NGSI2 }; //timestamp scafold-attr for insertions.
274
+ let plainMeasures = null; //will contain measures POJO
275
+ let idTypeSSSList = pluginUtils.getIdTypeServSubServiceFromDevice(typeInformation);
372
276
 
373
- if (typeInformation && typeInformation.staticAttributes) {
374
- attributes = attributes.concat(typeInformation.staticAttributes);
375
- }
277
+ //Make a clone and overwrite
278
+ typeInformation = JSON.parse(JSON.stringify(typeInformation));
376
279
 
280
+ //Check mandatory information: type
377
281
  if (!typeInformation || !typeInformation.type) {
378
282
  callback(new errors.TypeNotFound(null, entityName));
379
283
  return;
380
284
  }
381
285
 
382
- let idTypeSSSList = pluginUtils.getIdTypeServSubServiceFromDevice(typeInformation);
383
- logger.debug(context, 'sendUpdateValueNgsi2 idTypeSSS are %j ', idTypeSSSList);
384
- let measureAttrsForCtxt = [];
286
+ //Make a copy of measures in an plain object: plainMeasures
287
+ plainMeasures = reduceAttrToPlainObject(measures);
385
288
 
386
- // Check explicitAttrs: adds all final needed attributes to payload
387
- if (
388
- typeInformation.explicitAttrs === undefined ||
389
- (typeof typeInformation.explicitAttrs === 'boolean' && !typeInformation.explicitAttrs)
390
- // explicitAttrs is not defined => default case: all attrs should be included
391
- ) {
392
- // This loop adds all measure values (attributes) into payload entities (entity[0])
393
- for (let i = 0; i < attributes.length; i++) {
394
- if (attributes[i].name && attributes[i].type) {
395
- payload.entities[0][attributes[i].name] = {
396
- value: attributes[i].value,
397
- type: attributes[i].type
398
- };
399
- const metadata = NGSIUtils.getMetaData(typeInformation, attributes[i].name, attributes[i].metadata);
400
- if (metadata) {
401
- payload.entities[0][attributes[i].name].metadata = metadata;
402
- }
403
- } else {
404
- callback(new errors.BadRequest(null, entityName));
405
- return;
406
- }
407
- }
408
- logger.debug(context, 'sendUpdateValueNgsi2 pre-initial non-explicitAttrs payload=%j', payload);
409
- // Loop for add attrs from type.information.active (and lazys?) into payload entities (entity[0])
410
- if (typeInformation.active) {
411
- typeInformation.active.forEach((attr) => {
412
- if (attr.expression) {
413
- if (attr.object_id) {
414
- payload.entities[0][attr.object_id] = {
415
- value: payload.entities[0][attr.object_id]
416
- ? payload.entities[0][attr.object_id].value
417
- : undefined,
418
- type: attr.type,
419
- object_id: attr.object_id
420
- };
421
- } else {
422
- payload.entities[0][attr.name] = {
423
- value: payload.entities[0][attr.name] ? payload.entities[0][attr.name].value : undefined,
424
- type: attr.type
425
- };
426
- }
427
- }
428
- });
429
- }
430
- } else {
431
- let selectedAttrs = [];
432
- if (typeof typeInformation.explicitAttrs === 'string') {
433
- // explicitAttrs is a jexlExpression
434
- // This ctxt should include all possible attrs
435
- const attributesCtxt = [];
436
- if (typeInformation.static) {
437
- typeInformation.static.forEach(function (att) {
438
- attributesCtxt.push(att);
439
- });
440
- }
441
- // Measures
442
- for (let i = 0; i < attributes.length; i++) {
443
- if (attributes[i].name && attributes[i].type) {
444
- const measureAttr = {
445
- name: attributes[i].name,
446
- value: attributes[i].value,
447
- type: attributes[i].type
448
- };
449
- attributesCtxt.push(measureAttr);
450
- // check measureAttr by object_id -> if in active
451
- let j = 0;
452
- let found = false;
453
- while (j < typeInformation.active.length && !found) {
454
- if (attributes[i].name === typeInformation.active[j].object_id) {
455
- let measureAttrByObjectId = {
456
- name: typeInformation.active[j].name,
457
- value: attributes[i].value,
458
- type: attributes[i].type
459
- };
460
- attributesCtxt.push(measureAttrByObjectId);
461
- found = true;
462
- }
463
- j++;
464
- }
465
- }
466
- }
467
- // This context is just to calculate explicitAttrs when is an expression
289
+ //Build the initital JEXL Context
290
+ //All the measures (avoid references make another copy instead)
291
+ jexlctxt = reduceAttrToPlainObject(measures);
292
+ //All the static
293
+ jexlctxt = reduceAttrToPlainObject(typeInformation.staticAttributes, jexlctxt);
294
+ //id type Service and Subservice
295
+ jexlctxt = reduceAttrToPlainObject(idTypeSSSList, jexlctxt);
468
296
 
469
- let ctxt = expressionPlugin.extractContext(attributesCtxt.concat(idTypeSSSList));
470
- // typeInformation.active all attrs with expressions
471
- if (typeInformation.active) {
472
- typeInformation.active.forEach(function (att) {
473
- if (att.expression !== undefined) {
474
- let expandedAttr = {
475
- name: att.name,
476
- value: att.expression,
477
- type: att.type
478
- };
479
- attributesCtxt.push(expandedAttr);
480
- if (att.object_id !== undefined) {
481
- let expandedAttrByObjectId = {
482
- name: att.object_id,
483
- value: att.expression,
484
- type: att.type
485
- };
486
- attributesCtxt.push(expandedAttrByObjectId);
487
- }
488
- ctxt = expressionPlugin.extractContext(attributesCtxt.concat(idTypeSSSList));
489
- }
490
- });
491
- }
492
- // calculate expression for explicitAttrs
493
- try {
494
- logger.debug(context, 'sendUpdateValueNgsi2 selectedAttrs ctxt %j', ctxt);
495
- let res = jexlParser.applyExpression(typeInformation.explicitAttrs, ctxt, typeInformation);
496
- if (res === true) {
497
- // like explicitAttrs == true
498
- // selectAttrs should be measures which are defined attributes
499
- typeInformation.active.forEach((attr) => {
500
- selectedAttrs.push(attr.name);
501
- selectedAttrs.push(attr.object_id);
502
- });
503
- } else if (res === false) {
504
- // like explicitAttrs == false
505
- // selectAttrs should be measures and defined attributes
506
- typeInformation.active.forEach((attr) => {
507
- selectedAttrs.push(attr.name);
508
- selectedAttrs.push(attr.object_id);
509
- });
510
- for (let i = 0; i < attributes.length; i++) {
511
- selectedAttrs.push(attributes[i].name);
512
- }
513
- } else {
514
- selectedAttrs = res; // TBD: Check ensure is an array of strings
515
- }
516
- if (selectedAttrs.length === 0) {
517
- // implies do nothing
518
- logger.info(
519
- context,
520
- 'sendUpdateValueNgsi2 none selectedAttrs with %j and ctxt %j',
521
- typeInformation.explicitAttrs,
522
- ctxt
523
- );
524
- return callback(null);
525
- }
526
- } catch (e) {
527
- // nothing to do: exception is already logged at info level
528
- }
297
+ //Managing timestamp (mustInsertTimeInstant flag to decide if we should insert Timestamp later on)
298
+ const mustInsertTimeInstant =
299
+ typeInformation.timestamp !== undefined
300
+ ? typeInformation.timestamp
301
+ : config.getConfig().timestamp !== undefined
302
+ ? config.getConfig().timestamp
303
+ : false;
529
304
 
530
- typeInformation.active.forEach((attr) => {
531
- if (selectedAttrs.includes(attr.name)) {
532
- selectedAttrs.push(attr.object_id);
533
- }
534
- // Check if selectedAttrs includes an attribute with format {object_id: xxxx}
535
- if (selectedAttrs.includes({ object_id: attr.object_id })) {
536
- selectedAttrs.push(attr.object_id);
537
- }
538
- });
539
- } else if (typeInformation.explicitAttrs && typeof typeInformation.explicitAttrs === 'boolean') {
540
- // explicitAtts is true => Add just measures which are defined in active attributes
541
- // and active attributes with expressions
542
- // and TimeInstant
543
- selectedAttrs = ['TimeInstant'];
544
- typeInformation.active.forEach((attr) => {
545
- // Measures
546
- if (attr.expression !== undefined) {
547
- selectedAttrs.push(attr.name);
548
- selectedAttrs.push(attr.object_id);
549
- } else {
550
- // check if active attr is receiving a measure
551
- let i = 0;
552
- let found = false;
553
- while (i < attributes.length && !found) {
554
- if (attributes[i].name && attributes[i].type) {
555
- if (attributes[i].name === attr.object_id || attributes[i].name === attr.name) {
556
- selectedAttrs.push(attr.name);
557
- selectedAttrs.push(attr.object_id);
558
- found = true;
559
- }
560
- }
561
- i++;
562
- }
563
- }
564
- });
565
- }
566
- // This loop adds selected measured values (attributes) into payload entities (entity[0])
567
- for (let i = 0; i < attributes.length; i++) {
568
- if (attributes[i].name && selectedAttrs.includes(attributes[i].name) && attributes[i].type) {
569
- const attr = typeInformation.active.find((obj) => {
570
- return obj.name === attributes[i].name;
571
- });
572
- payload.entities[0][attributes[i].name] = {
573
- value: attributes[i].value,
574
- type: attributes[i].type
575
- };
576
- // ensure payload has attr with proper object_id
577
- if (attr && attr.object_id) {
578
- payload.entities[0][attributes[i].name].object_id = attr.object_id;
579
- }
580
- const metadata = NGSIUtils.getMetaData(typeInformation, attributes[i].name, attributes[i].metadata);
581
- if (metadata) {
582
- payload.entities[0][attributes[i].name].metadata = metadata;
583
- }
584
- } else if (attributes[i].name && !selectedAttrs.includes(attributes[i].name) && attributes[i].type) {
585
- const att = {
586
- name: attributes[i].name,
587
- type: attributes[i].type,
588
- value: attributes[i].value
589
- };
590
- measureAttrsForCtxt.push(att);
591
- }
592
- }
593
- logger.debug(
594
- context,
595
- 'sendUpdateValueNgsi2 pre-initial explicitAttrs payload=%j selectedAttrs=%j',
596
- payload,
597
- selectedAttrs
598
- );
599
- let selectedAttrsByObjectId = selectedAttrs
600
- .filter((o) => o !== undefined && o.object_id)
601
- .map(function (el) {
602
- return el.object_id;
603
- });
305
+ if (mustInsertTimeInstant) {
306
+ //remove TimeInstant from measures
307
+ measures = measures.filter((item) => item.name !== constants.TIMESTAMP_ATTRIBUTE);
604
308
 
605
- // Loop for add seleted attrs from type.information.active into pyaload entities (entity[0])
606
- if (typeInformation.active) {
607
- typeInformation.active.forEach((attr) => {
608
- if (selectedAttrs.includes(attr.name)) {
609
- if (attr.object_id) {
610
- payload.entities[0][attr.object_id] = {
611
- value: payload.entities[0][attr.object_id]
612
- ? payload.entities[0][attr.object_id].value
613
- : payload.entities[0][attr.name]
614
- ? payload.entities[0][attr.name].value
615
- : undefined,
616
- type: attr.type,
617
- object_id: attr.object_id
618
- };
619
- } else {
620
- payload.entities[0][attr.name] = {
621
- value: payload.entities[0][attr.name] ? payload.entities[0][attr.name].value : undefined,
622
- type: attr.type
623
- };
624
- }
625
- } else if (attr.object_id !== undefined && selectedAttrsByObjectId.includes(attr.object_id)) {
626
- payload.entities[0][attr.object_id] = {
627
- value: payload.entities[0][attr.object_id]
628
- ? payload.entities[0][attr.object_id].value
629
- : payload.entities[0][attr.name]
630
- ? payload.entities[0][attr.name].value
631
- : undefined,
632
- type: attr.type,
633
- object_id: attr.object_id
634
- };
635
- }
636
- });
309
+ if (plainMeasures[constants.TIMESTAMP_ATTRIBUTE]) {
310
+ //if it comes from a measure
311
+ if (moment(plainMeasures[constants.TIMESTAMP_ATTRIBUTE], moment.ISO_8601, true).isValid()) {
312
+ timestamp.value = plainMeasures[constants.TIMESTAMP_ATTRIBUTE];
313
+ } else {
314
+ callback(new errors.BadTimestamp(plainMeasures[constants.TIMESTAMP_ATTRIBUTE], entityName));
315
+ }
316
+ } else if (!typeInformation.timezone) {
317
+ timestamp.value = new Date().toISOString();
318
+ jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value;
319
+ } else {
320
+ timestamp.value = moment().tz(typeInformation.timezone).format('YYYY-MM-DD[T]HH:mm:ss.SSSZ');
321
+ jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value;
637
322
  }
638
- } // END check explicitAttrs
639
- logger.debug(context, 'sendUpdateValueNgsi2 initial payload=%j', payload);
640
-
641
- const currentEntity = payload.entities[0];
642
-
643
- // Prepare attributes for expresionPlugin
644
- const attsArray = pluginUtils.extractAttributesArrayFromNgsi2Entity(currentEntity);
645
-
646
- // Exclude processing all attr expressions when current attr is of type 'commandStatus' or 'commandResult'
647
- let attsArrayFiltered = [];
648
- if (attsArray) {
649
- attsArrayFiltered = attsArray.filter((obj) => {
650
- return ![constants.COMMAND_STATUS, constants.COMMAND_RESULT].includes(obj.type);
651
- });
652
- }
653
- let attributesCtxt = [...attsArrayFiltered]; // just copy
654
- if (typeInformation.static) {
655
- typeInformation.static.forEach(function (att) {
656
- attributesCtxt.push(att);
657
- });
658
- }
659
- if (measureAttrsForCtxt) {
660
- measureAttrsForCtxt.forEach(function (att) {
661
- attributesCtxt.push(att);
662
- });
663
323
  }
664
- attributesCtxt = attributesCtxt.concat(idTypeSSSList);
665
- let ctxt = expressionPlugin.extractContext(attributesCtxt, typeInformation);
666
- logger.debug(context, 'sendUpdateValueNgsi2 initial ctxt %j ', ctxt);
667
324
 
668
- // Sort currentEntity to get first attrs without expressions (checking attrs in typeInformation.active)
669
- // attributes without expressions should be processed before
670
- logger.debug(context, 'sendUpdateValueNgsi2 currentEntity %j ', currentEntity);
671
- if (typeInformation.active && typeInformation.active.length > 0) {
672
- for (const k in currentEntity) {
673
- typeInformation.active.forEach(function (att) {
674
- if (
675
- (att.object_id && att.object_id === k && att.expression) ||
676
- (att.name && att.name === k && att.expression)
677
- ) {
678
- const m = currentEntity[k];
679
- delete currentEntity[k];
680
- currentEntity[k] = m; // put into the end of currentEntity
681
- }
682
- });
683
- }
684
- }
325
+ logger.debug(
326
+ context,
327
+ 'sendUpdateValueNgsi2 called with: entityName=%s, measures=%j, typeInformation=%j, initial jexlContext=%j, timestamp=%j with value=%j',
328
+ entityName,
329
+ plainMeasures,
330
+ typeInformation,
331
+ jexlctxt,
332
+ mustInsertTimeInstant,
333
+ timestamp.value
334
+ );
685
335
 
686
- // Evaluate entityNameExp with a context including measures
336
+ //Now we can calculate the EntityName of primary entity
337
+ let entityNameCalc = null;
687
338
  if (typeInformation.entityNameExp !== undefined && typeInformation.entityNameExp !== '') {
688
339
  try {
689
- logger.debug(context, 'sendUpdateValueNgsi2 entityNameExp %j ', typeInformation.entityNameExp);
690
- entityName = expressionPlugin.applyExpression(typeInformation.entityNameExp, ctxt, typeInformation);
691
- // CB entity id should be always a String
692
- entityName = String(entityName);
693
- payload.entities[0].id = entityName;
694
- ctxt['entity_name'] = entityName;
340
+ logger.debug(context, 'sendUpdateValueNgsi2 entityNameExp %j', typeInformation.entityNameExp);
341
+ entityNameCalc = expressionPlugin.applyExpression(typeInformation.entityNameExp, jexlctxt, typeInformation);
695
342
  } catch (e) {
696
343
  logger.debug(
697
344
  context,
698
- 'Error evaluating expression for entityName: %s with context: %s',
345
+ 'Error evaluating expression for entityName: %j with context: %j',
699
346
  typeInformation.entityNameExp,
700
- ctxt
347
+ jexlctxt
701
348
  );
702
349
  }
703
350
  }
704
351
 
705
- logger.debug(context, 'sendUpdateValueNgsi2 currentEntity sorted %j ', currentEntity);
706
- let timestampValue = undefined;
707
- // Loop for each final attribute to apply alias, multientity and expressions
708
- for (const j in currentEntity) {
709
- // discard id and type
710
- if (j !== 'id' || j !== 'type') {
711
- // Apply Mapping Alias: object_id in attributes are in typeInformation.active
712
- let attr;
713
- let newAttr = payload.entities[0][j];
714
- if (typeInformation.active) {
715
- attr = typeInformation.active.find((obj) => {
716
- return obj.object_id === j;
717
- });
718
- }
719
- if (!attr) {
720
- if (typeInformation.lazy) {
721
- attr = typeInformation.lazy.find((obj) => {
722
- return obj.object_id === j;
723
- });
724
- }
725
- }
726
- if (!attr) {
727
- if (typeInformation.active) {
728
- attr = typeInformation.active.find((obj) => {
729
- return obj.name === j;
730
- });
352
+ entityName = entityNameCalc ? entityNameCalc : entityName;
353
+ //enrich JEXL context
354
+ jexlctxt['entity_name'] = entityName;
355
+
356
+ let preprocessedAttr = [];
357
+ //Add Raw Static, Lazy, Command and Actives attr attributes
358
+ if (typeInformation && typeInformation.staticAttributes) {
359
+ preprocessedAttr = preprocessedAttr.concat(typeInformation.staticAttributes);
360
+ }
361
+ if (typeInformation && typeInformation.lazy) {
362
+ preprocessedAttr = preprocessedAttr.concat(typeInformation.lazy);
363
+ }
364
+ if (typeInformation && typeInformation.active) {
365
+ preprocessedAttr = preprocessedAttr.concat(typeInformation.active);
366
+ }
367
+
368
+ //Proccess every proto Attribute to populate entities data steuture
369
+ entities[entityName] = {};
370
+ entities[entityName][typeInformation.type] = [];
371
+
372
+ for (let currentAttr of preprocessedAttr) {
373
+ let hitted = false; //any measure, expressiom or value hit the attr (avoid propagate "silent attr" with null values )
374
+ let attrEntityName = entityName;
375
+ let attrEntityType = typeInformation.type;
376
+ let valueExpression = null;
377
+ //manage active attr without object__id (name by default)
378
+ currentAttr.object_id = currentAttr.object_id ? currentAttr.object_id : currentAttr.name;
379
+ //Enrich the attr (skip, hit, value, meta-timeInstant)
380
+ currentAttr.skipValue = currentAttr.skipValue ? currentAttr.skipValue : null;
381
+
382
+ //determine AttrEntityName for multientity
383
+ if (
384
+ currentAttr.entity_name !== null &&
385
+ currentAttr.entity_name !== undefined &&
386
+ currentAttr.entity_name !== '' &&
387
+ typeof currentAttr.entity_name == 'string'
388
+ ) {
389
+ try {
390
+ logger.debug(
391
+ context,
392
+ 'Evaluating attribute: %j, for entity_name(exp):%j, with ctxt: %j',
393
+ currentAttr.name,
394
+ currentAttr.entity_name,
395
+ jexlctxt
396
+ );
397
+ attrEntityName = jexlParser.applyExpression(currentAttr.entity_name, jexlctxt, typeInformation);
398
+ if (!attrEntityName) {
399
+ attrEntityName = currentAttr.entity_name;
731
400
  }
401
+ } catch (e) {
402
+ logger.debug(
403
+ context,
404
+ 'Exception evaluating entityNameExp:%j, with jexlctxt: %j',
405
+ currentAttr.entity_name,
406
+ jexlctxt
407
+ );
408
+ attrEntityName = currentAttr.entity_name;
732
409
  }
733
- if (attr && attr.name) {
734
- if (['id', 'type'].includes(attr.name)) {
735
- // invalid mapping
736
- logger.debug(
737
- context,
738
- 'sendUpdateValueNgsi2 invalid mapping for attr=%j newAttr=%j',
739
- attr,
740
- newAttr
741
- );
742
- if (!['id', 'type'].includes(attr.object_id)) {
743
- delete payload.entities[0][attr.object_id];
744
- }
745
- attr = undefined; // stop processing attr
746
- newAttr = undefined;
747
- } else {
748
- ctxt[attr.name] = payload.entities[0][j].value;
410
+ }
411
+
412
+ //determine AttrEntityType for multientity
413
+ if (
414
+ currentAttr.entity_type !== null &&
415
+ currentAttr.entity_type !== undefined &&
416
+ currentAttr.entity_type !== '' &&
417
+ typeof currentAttr.entity_type === 'string'
418
+ ) {
419
+ attrEntityType = currentAttr.entity_type;
420
+ }
421
+
422
+ //PRE POPULATE CONTEXT
423
+ jexlctxt[currentAttr.name] = plainMeasures[currentAttr.object_id];
424
+
425
+ //determine Value
426
+ if (currentAttr.value !== undefined) {
427
+ //static attributes already have a value
428
+ hitted = true;
429
+ valueExpression = currentAttr.value;
430
+ } else if (plainMeasures[currentAttr.object_id] !== undefined) {
431
+ //we have got a meaure for that Attr
432
+ //actives ¿lazis?
433
+ hitted = true;
434
+ valueExpression = plainMeasures[currentAttr.object_id];
435
+ }
436
+ //remove measures that has been shadowed by an alias (some may be left and managed later)
437
+ //Maybe we must filter object_id if there is name == object_id
438
+ measures = measures.filter((item) => item.name !== currentAttr.object_id && item.name !== currentAttr.name);
439
+
440
+ if (
441
+ currentAttr.expression !== undefined &&
442
+ currentAttr.expression !== '' &&
443
+ typeof currentAttr.expression == 'string'
444
+ ) {
445
+ try {
446
+ hitted = true;
447
+ valueExpression = jexlParser.applyExpression(currentAttr.expression, jexlctxt, typeInformation);
448
+ //we fallback to null if anything unexpecte happend
449
+ if (valueExpression === null || valueExpression === undefined || Number.isNaN(valueExpression)) {
450
+ valueExpression = null;
749
451
  }
452
+ } catch (e) {
453
+ valueExpression = null;
750
454
  }
751
455
  logger.debug(
752
456
  context,
753
- 'sendUpdateValueNgsi2 procesing j=%j attr=%j ctxt=%j newAttr=%j ',
754
- j,
755
- attr,
756
- ctxt,
757
- newAttr
457
+ 'Evaluated attr: %j, with expression: %j, and ctxt: %j resulting: %j',
458
+ currentAttr.name,
459
+ currentAttr.expression,
460
+ jexlctxt,
461
+ valueExpression
758
462
  );
759
- if (attr && attr.type) {
760
- newAttr.type = attr.type;
761
- }
463
+ }
762
464
 
763
- // Apply expression
764
- if (attr && attr.expression) {
765
- logger.debug(
766
- context,
767
- 'sendUpdateValueNgsi2 apply expression=%j over ctxt=%j and device=%j',
768
- attr.expression,
769
- ctxt,
770
- typeInformation
771
- );
772
- let res = null;
773
- try {
774
- if (expressionPlugin.contextAvailable(attr.expression, ctxt, typeInformation)) {
775
- res = expressionPlugin.applyExpression(attr.expression, ctxt, typeInformation);
776
- if (
777
- // By default undefined is handled like null: should not progress
778
- // Some op results (like nonexistent * 2) are a kind of null with a number type
779
- // but NaN value
780
- (attr.skipValue === undefined &&
781
- (res === null || (typeof res === 'number' && isNaN(res)))) ||
782
- (attr.skipValue !== undefined &&
783
- (res === attr.skipValue ||
784
- (typeof res === 'number' && isNaN(res) && attr.skipValue === null)))
785
- ) {
786
- logger.debug(
787
- context,
788
- 'sendUpdateValueNgsi2 skip value=%j for res=%j with expression=%j',
789
- attr.skipValue,
790
- res,
791
- attr.expression
792
- );
793
- delete payload.entities[0][j]; // remove measure attr
794
- attr = undefined; // stop process attr
795
- }
796
- } else {
797
- logger.info(
798
- context,
799
- 'sendUpdateValueNgsi2 no context available for apply expression=%j',
800
- attr.expression
801
- );
802
- res = newAttr.value; // keep newAttr value
803
- }
804
- } catch (e) {
805
- logger.error(context, 'sendUpdateValueNgsi2 apply expression exception=%j', e);
806
- if (attr && attr.name) {
807
- res = ctxt[attr.name];
808
- }
809
- }
810
- // jexl expression plugin
811
- newAttr.value = res;
465
+ currentAttr.hitted = hitted;
466
+ currentAttr.value = valueExpression;
812
467
 
813
- logger.debug(context, 'sendUpdateValueNgsi2 apply expression result=%j newAttr=%j', res, newAttr);
814
- // update current context with value
815
- if (attr && attr.name && ctxt[attr.name] !== undefined) {
816
- ctxt[attr.name] = newAttr.value;
817
- }
468
+ //add TimeInstant to attr metadata
469
+ if (mustInsertTimeInstant) {
470
+ if (!currentAttr.metadata) {
471
+ currentAttr.metadata = {};
818
472
  }
473
+ currentAttr.metadata[constants.TIMESTAMP_ATTRIBUTE] = timestamp;
474
+ }
819
475
 
820
- // Apply Multientity: entity_type and entity_name in attributes are in typeInformation.active
821
- if (attr && (attr.entity_type || attr.entity_name)) {
822
- // Create a newEntity for this attribute
823
- let newEntityName = null;
824
- if (attr.entity_name) {
825
- try {
826
- if (expressionPlugin.contextAvailable(attr.entity_name, ctxt, typeInformation)) {
827
- newEntityName = expressionPlugin.applyExpression(attr.entity_name, ctxt, typeInformation);
828
- } else {
829
- logger.info(
830
- context,
831
- 'sendUpdateValueNgsi2 MULTI no context available for apply expression=%j',
832
- attr.entity_name
833
- );
834
- newEntityName = attr.entity_name;
835
- }
836
- newEntityName = newEntityName ? newEntityName : attr.entity_name;
837
- } catch (e) {
838
- logger.error(context, 'sendUpdateValueNgsi2 MULTI apply expression exception=%j', e);
839
- newEntityName = attr.entity_name;
840
- }
841
- logger.debug(
842
- context,
843
- 'sendUpdateValueNgsi2 MULTI apply expression=%j result=%j payload=%j',
844
- attr.entity_name,
845
- newEntityName,
846
- payload
847
- );
848
- }
849
- // CB entity id and type should be always a String
850
- let newEntity = {
851
- id: newEntityName ? String(newEntityName) : String(payload.entities[0].id),
852
- type: attr.entity_type ? String(attr.entity_type) : String(payload.entities[0].type)
853
- };
854
- // Check if there is already a newEntity created
855
- const alreadyEntity = payload.entities.find((entity) => {
856
- return entity.id === newEntity.id && entity.type === newEntity.type;
857
- });
858
- if (alreadyEntity) {
859
- // Use alreadyEntity
860
- alreadyEntity[attr.name] = newAttr;
861
- } else {
862
- // Add newEntity to payload.entities
863
- newEntity[attr.name] = newAttr;
864
- if (
865
- 'timestamp' in typeInformation && typeInformation.timestamp !== undefined
866
- ? typeInformation.timestamp
867
- : config.getConfig().timestamp !== undefined
868
- ? config.getConfig().timestamp
869
- : timestampValue !== undefined
870
- ) {
871
- newEntity = addTimestampNgsi2(newEntity, typeInformation.timezone, timestampValue);
872
- logger.debug(context, 'sendUpdateValueNgsi2 timestamped newEntity=%j', newEntity);
873
- }
874
- payload.entities.push(newEntity);
875
- }
876
- if (attr && attr.name) {
877
- if (attr.name !== j) {
878
- logger.debug(
879
- context,
880
- 'sendUpdateValueNgsi2 MULTI remove measure attr=%j keep alias j=%j from %j',
881
- j,
882
- attr,
883
- payload
884
- );
885
- delete payload.entities[0][j];
886
- }
887
- }
888
- // if (attr && (attr.entity_type || attr.entity_name))
889
- } else {
890
- // Not a multientity attr
891
- if (attr && attr.name) {
892
- payload.entities[0][attr.name] = newAttr;
893
- if (attr.name !== j) {
894
- delete payload.entities[0][j]; // keep alias name, remove measure name
895
- }
896
- }
897
- if (newAttr && newAttr.type === constants.TIMESTAMP_TYPE_NGSI2 && newAttr.value) {
898
- const extendedTime = compressTimestampPlugin.fromBasicToExtended(newAttr.value);
899
- if (extendedTime) {
900
- // TBD: there is not flag about compressTimestamp in iotagent-node-lib,
901
- // but there is one in agents
902
- newAttr.value = extendedTime;
903
- }
904
- }
905
- if (j === constants.TIMESTAMP_ATTRIBUTE) {
906
- if (newAttr && newAttr.type === constants.TIMESTAMP_TYPE_NGSI2 && newAttr.value) {
907
- timestampValue = newAttr.value;
908
- logger.debug(
909
- context,
910
- 'sendUpdateValueNgsi2 newAttr is TimeInstant and new payload=%j',
911
- payload
912
- );
913
- }
914
- }
915
- if (
916
- newAttr &&
917
- newAttr.metadata &&
918
- newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE] &&
919
- newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].type === constants.TIMESTAMP_TYPE_NGSI2 &&
920
- newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].value
921
- ) {
922
- const extendedTime = compressTimestampPlugin.fromBasicToExtended(
923
- newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].value
924
- );
925
- if (extendedTime) {
926
- newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].value = extendedTime;
927
- }
928
- }
476
+ //store de New Attributte in entity data structure
477
+ if (hitted === true) {
478
+ if (entities[attrEntityName] === undefined) {
479
+ entities[attrEntityName] = {};
929
480
  }
930
- } // if (j !== 'id' || j !== 'type')
481
+ if (entities[attrEntityName][attrEntityType] === undefined) {
482
+ entities[attrEntityName][attrEntityType] = [];
483
+ }
484
+ //store de New Attributte
485
+ entities[attrEntityName][attrEntityType].push(currentAttr);
486
+ }
931
487
 
932
- // final attr loop
933
- logger.debug(
934
- context,
935
- 'sendUpdateValueNgsi2 after procesing attr=%j current entity=%j current payload=%j',
936
- j,
937
- currentEntity,
938
- payload
939
- );
488
+ //RE-Populate de JEXLcontext (except for null or NaN we preffer undefined)
489
+ jexlctxt[currentAttr.name] = valueExpression;
940
490
  }
941
- // for attr loop
942
491
 
943
- // Add timestamp to paylaod
944
- if (
945
- 'timestamp' in typeInformation && typeInformation.timestamp !== undefined
946
- ? typeInformation.timestamp
947
- : config.getConfig().timestamp !== undefined
948
- ? config.getConfig().timestamp
949
- : timestampValue !== undefined
950
- ) {
951
- if (timestampValue) {
952
- // timeInstant is provided as measure
953
- if (payload.entities.length > 0) {
954
- for (let n = 0; n < payload.entities.length; n++) {
955
- // include metadata with TimeInstant in attrs when TimeInstant is provided as measure in all entities
956
- payload.entities[n] = addTimestampNgsi2(
957
- payload.entities[n],
958
- typeInformation.timezone,
959
- timestampValue
960
- );
961
- }
492
+ //now we can compute explicit (Bool or Array) with the complete JexlContext
493
+ let explicit = false;
494
+ if (typeof typeInformation.explicitAttrs === 'string') {
495
+ try {
496
+ explicit = jexlParser.applyExpression(typeInformation.explicitAttrs, jexlctxt, typeInformation);
497
+ if (explicit instanceof Array && mustInsertTimeInstant) {
498
+ explicit.push(constants.TIMESTAMP_ATTRIBUTE);
962
499
  }
963
- } else {
964
- // jshint maxdepth:5
965
- for (let n = 0; n < payload.entities.length; n++) {
966
- if (!utils.isTimestampedNgsi2(payload.entities[n])) {
967
- // legacy check needed?
968
- payload.entities[n] = addTimestampNgsi2(payload.entities[n], typeInformation.timezone);
969
- // jshint maxdepth:5
970
- } else if (!utils.IsValidTimestampedNgsi2(payload.entities[n])) {
971
- // legacy check needed?
972
- logger.error(context, 'Invalid timestamp:%s', JSON.stringify(payload.entities[0]));
973
- callback(new errors.BadTimestamp(payload.entities));
974
- return;
500
+ logger.debug(
501
+ context,
502
+ 'Calculated explicitAttrs with expression: %j and ctxt: %j resulting: %j',
503
+ typeInformation.explicitAttrs,
504
+ jexlctxt,
505
+ explicit
506
+ );
507
+ } catch (e) {
508
+ // nothing to do: exception is already logged at info level
509
+ }
510
+ } else if (typeof typeInformation.explicitAttrs == 'boolean') {
511
+ explicit = typeInformation.explicitAttrs;
512
+ }
513
+
514
+ //more mesures may be added to the attribute list (unnhandled/left mesaures) l
515
+ if (explicit === false && Object.keys(measures).length > 0) {
516
+ //add Timestamp to measures if needed
517
+ if (mustInsertTimeInstant) {
518
+ for (let currentMeasure of measures) {
519
+ if (!currentMeasure.metadata) {
520
+ currentMeasure.metadata = {};
975
521
  }
522
+ currentMeasure.metadata[constants.TIMESTAMP_ATTRIBUTE] = timestamp;
976
523
  }
524
+ //If just measures in the principal entity we missed the Timestamp.
977
525
  }
526
+ entities[entityName][typeInformation.type] = entities[entityName][typeInformation.type].concat(measures);
978
527
  }
979
- logger.debug(context, 'sendUpdateValueNgsi2 ending payload=%j', payload);
980
528
 
981
- for (let m = 0; m < payload.entities.length; m++) {
982
- for (const key in payload.entities[m]) {
983
- // purge object_id from payload
984
- if (payload.entities[m][key] && payload.entities[m][key].object_id) {
985
- delete payload.entities[m][key].object_id;
529
+ //PRE-PROCESSING FINISHED
530
+ //Explicit ATTRS and SKIPVALUES will be managed while we build NGSI payload
531
+
532
+ //Get ready to build and send NGSI payload (entities-->payload)
533
+ payload.actionType = 'append';
534
+
535
+ payload.entities = [];
536
+ for (let ename in entities) {
537
+ for (let etype in entities[ename]) {
538
+ let e = {};
539
+ e.id = String(ename);
540
+ e.type = String(etype);
541
+ //extract attributes
542
+ let isEmpty = true;
543
+ for (let attr of entities[ename][etype]) {
544
+ //Handling id/type measures, skip, hit & explicit (condition)
545
+ if (
546
+ attr.name !== 'id' &&
547
+ attr.name !== 'type' &&
548
+ (attr.value !== attr.skipValue || attr.skipValue === undefined) &&
549
+ (attr.hitted || attr.hitted === undefined) && //undefined is for pure measures
550
+ (typeof explicit === 'boolean' || //true and false already handled
551
+ (explicit instanceof Array && //check the array version
552
+ (explicit.includes(attr.name) ||
553
+ explicit.some(
554
+ (item) => attr.object_id !== undefined && item.object_id === attr.object_id
555
+ ))))
556
+ ) {
557
+ isEmpty = false;
558
+ e[attr.name] = { type: attr.type, value: attr.value, metadata: attr.metadata };
559
+ }
560
+ }
561
+ if (!isEmpty) {
562
+ if (mustInsertTimeInstant) {
563
+ e[constants.TIMESTAMP_ATTRIBUTE] = timestamp;
564
+ }
565
+ payload.entities.push(e);
986
566
  }
987
567
  }
988
- payload.entities[m] = NGSIUtils.castJsonNativeAttributes(payload.entities[m]); // native types
989
568
  }
990
- logger.debug(context, 'sendUpdateValueNgsi2 payload with native types and without object_id=%j', payload);
991
569
 
570
+ let url = '/v2/op/update';
571
+ let options = NGSIUtils.createRequestObject(url, typeInformation, token);
992
572
  options.json = payload;
993
573
 
994
- // Prevent to update an entity with an empty payload
574
+ // Prevent to update an entity with an empty payload: more than id and type
995
575
  if (
996
576
  Object.keys(options.json).length > 0 &&
997
577
  (options.json.entities.length > 1 ||
998
- (options.json.entities.length === 1 && Object.keys(options.json.entities[0]).length > 2)) // more than id and type
578
+ (options.json.entities.length === 1 && Object.keys(options.json.entities[0]).length > 2))
999
579
  ) {
1000
580
  // Final check: (to keep tests unchanged) before do CB requests
1001
581
  // one entity -> request /v2/entities/ + entityName + /atts ?type=typeInformation.type
1002
582
  // multi entities -> request /v2/op/update
1003
583
  // Note that the options object is prepared for the second case (multi entity), so we "patch" it
1004
584
  // only in the first case
1005
- if (options.json.entities.length === 1) {
585
+
586
+ //Multientity more than one name o more than one type at primary entity
587
+ let multientity = Object.keys(entities).length > 1 || Object.keys(entities[entityName]).length > 1;
588
+
589
+ if (!multientity) {
1006
590
  // recreate options object to use single entity update
1007
591
  url = '/v2/entities?options=upsert';
1008
592
  options = NGSIUtils.createRequestObject(url, typeInformation, token);
1009
- options.json = payload.entities[0];
593
+ delete payload.actionType;
594
+
595
+ let entityAttrs = payload.entities[0];
596
+ const transformedObject = {};
597
+ for (let attrname in entityAttrs) {
598
+ let attr = entityAttrs[attrname];
599
+ transformedObject[attrname] = {
600
+ type: attr.type,
601
+ value: attr.value,
602
+ metadata: attr.metadata
603
+ };
604
+ }
605
+ transformedObject.id = entityAttrs.id;
606
+ transformedObject.type = entityAttrs.type;
607
+ options.json = transformedObject;
1010
608
  options.method = 'POST';
1011
609
  } // else: keep current options object created for a batch update
1012
- logger.debug(context, 'Updating device value in the Context Broker at [%s]', options.url);
1013
- logger.debug(context, 'Using the following NGSI v2 request:\n\n%s\n\n', JSON.stringify(options, null, 4));
610
+
611
+ //Send the NGSI request
612
+ logger.debug(context, 'Updating device value in the Context Broker at: %j', options.url);
613
+ logger.debug(context, 'Using the following NGSI v2 request: %j', options);
614
+
1014
615
  request(
1015
616
  options,
1016
617
  generateNGSI2OperationHandler('update', entityName, typeInformation, token, options, callback)
@@ -1018,19 +619,19 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
1018
619
  } else {
1019
620
  logger.debug(
1020
621
  context,
1021
- 'Not updating device value in the Context Broker at [%s] due to empty payload \n\n[%s]\n\n',
622
+ 'Not updating device value in the Context Broker at: %j, due to empty payload: %j',
1022
623
  options.url,
1023
- JSON.stringify(options, null, 4)
624
+ options
1024
625
  );
1025
626
  callback(null);
1026
627
  }
1027
628
  }
1028
629
 
1029
630
  exports.sendQueryValue = sendQueryValueNgsi2;
1030
- exports.sendUpdateValue = function (entityName, attributes, typeInformation, token, callback) {
1031
- NGSIUtils.applyMiddlewares(NGSIUtils.updateMiddleware, attributes, typeInformation, () => {
1032
- return sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, callback);
631
+ exports.sendUpdateValue = function (entityName, measures, typeInformation, token, callback) {
632
+ NGSIUtils.applyMiddlewares(NGSIUtils.updateMiddleware, measures, typeInformation, () => {
633
+ return sendUpdateValueNgsi2(entityName, measures, typeInformation, token, callback);
1033
634
  });
1034
635
  };
1035
- exports.addTimestamp = addTimestampNgsi2;
636
+
1036
637
  exports.formatGeoAttrs = formatGeoAttrs;