iotagent-node-lib 3.4.3 → 4.0.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 (94) hide show
  1. package/CHANGES_NEXT_RELEASE +0 -1
  2. package/README.md +4 -0
  3. package/doc/admin.md +1 -13
  4. package/doc/api.md +118 -14
  5. package/doc/devel/architecture.md +0 -12
  6. package/doc/index.md +1 -1
  7. package/doc/roadmap.md +22 -10
  8. package/lib/commonConfig.js +0 -11
  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 +81 -6
  25. package/lib/services/ngsi/entities-NGSI-v2.js +304 -687
  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 +188 -1
  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/test/unit/ngsi-ld/ngsiService/autocast-test.js +0 -438
  88. package/test/unit/ngsi-ld/ngsiService/geoproperties-test.js +0 -381
  89. package/test/unit/ngsi-ld/provisioning/singleConfigurationMode-test.js +0 -311
  90. package/test/unit/ngsiv2/ngsiService/autocast-test.js +0 -325
  91. package/test/unit/ngsiv2/ngsiService/geoproperties-test.js +0 -427
  92. package/test/unit/ngsiv2/plugins/compress-timestamp-plugin_test.js +0 -217
  93. package/test/unit/ngsiv2/plugins/timestamp-processing-plugin_test.js +0 -119
  94. 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,677 +249,373 @@ 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
- const payload = {
344
- entities: [
345
- {
346
- // CB entity id should be always a String
347
- id: String(entityName)
348
- }
349
- ]
350
- };
351
-
352
- let url = '/v2/op/update';
353
-
354
- if (typeInformation && typeInformation.type) {
355
- // CB entity type should be always a String
356
- payload.entities[0].type = String(typeInformation.type);
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;
267
+ }
357
268
  }
358
269
 
359
- payload.actionType = 'append';
360
-
361
- 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);
362
276
 
363
- if (typeInformation && typeInformation.staticAttributes) {
364
- attributes = attributes.concat(typeInformation.staticAttributes);
365
- }
277
+ //Make a clone and overwrite
278
+ typeInformation = JSON.parse(JSON.stringify(typeInformation));
366
279
 
280
+ //Check mandatory information: type
367
281
  if (!typeInformation || !typeInformation.type) {
368
282
  callback(new errors.TypeNotFound(null, entityName));
369
283
  return;
370
284
  }
371
285
 
372
- let idTypeSSSList = pluginUtils.getIdTypeServSubServiceFromDevice(typeInformation);
373
- logger.debug(context, 'sendUpdateValueNgsi2 idTypeSSS are %j ', idTypeSSSList);
374
- let measureAttrsForCtxt = [];
286
+ //Make a copy of measures in an plain object: plainMeasures
287
+ plainMeasures = reduceAttrToPlainObject(measures);
375
288
 
376
- // Check explicitAttrs: adds all final needed attributes to payload
377
- if (
378
- typeInformation.explicitAttrs === undefined ||
379
- (typeof typeInformation.explicitAttrs === 'boolean' && !typeInformation.explicitAttrs)
380
- // explicitAttrs is not defined => default case: all attrs should be included
381
- ) {
382
- // This loop adds all measure values (attributes) into payload entities (entity[0])
383
- for (let i = 0; i < attributes.length; i++) {
384
- if (attributes[i].name && attributes[i].type) {
385
- payload.entities[0][attributes[i].name] = {
386
- value: attributes[i].value,
387
- type: attributes[i].type
388
- };
389
- const metadata = NGSIUtils.getMetaData(typeInformation, attributes[i].name, attributes[i].metadata);
390
- if (metadata) {
391
- payload.entities[0][attributes[i].name].metadata = metadata;
392
- }
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);
296
+
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;
304
+
305
+ if (mustInsertTimeInstant) {
306
+ //remove TimeInstant from measures
307
+ measures = measures.filter((item) => item.name !== constants.TIMESTAMP_ATTRIBUTE);
308
+
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];
393
313
  } else {
394
- callback(new errors.BadRequest(null, entityName));
395
- return;
314
+ callback(new errors.BadTimestamp(null, entityName));
396
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;
397
322
  }
398
- logger.debug(context, 'sendUpdateValueNgsi2 pre-initial non-explicitAttrs payload=%j', payload);
399
- // Loop for add attrs from type.information.active (and lazys?) into payload entities (entity[0])
400
- if (typeInformation.active) {
401
- typeInformation.active.forEach((attr) => {
402
- if (attr.expression) {
403
- if (attr.object_id) {
404
- payload.entities[0][attr.object_id] = {
405
- value: payload.entities[0][attr.object_id]
406
- ? payload.entities[0][attr.object_id].value
407
- : undefined,
408
- type: attr.type,
409
- object_id: attr.object_id
410
- };
411
- } else {
412
- payload.entities[0][attr.name] = {
413
- value: payload.entities[0][attr.name] ? payload.entities[0][attr.name].value : undefined,
414
- type: attr.type
415
- };
416
- }
417
- }
418
- });
323
+ }
324
+
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
+ );
335
+
336
+ //Now we can calculate the EntityName of primary entity
337
+ let entityNameCalc = null;
338
+ if (typeInformation.entityNameExp !== undefined && typeInformation.entityNameExp !== '') {
339
+ try {
340
+ logger.debug(context, 'sendUpdateValueNgsi2 entityNameExp %j', typeInformation.entityNameExp);
341
+ entityNameCalc = expressionPlugin.applyExpression(typeInformation.entityNameExp, jexlctxt, typeInformation);
342
+ } catch (e) {
343
+ logger.debug(
344
+ context,
345
+ 'Error evaluating expression for entityName: %j with context: %j',
346
+ typeInformation.entityNameExp,
347
+ jexlctxt
348
+ );
419
349
  }
420
- } else {
421
- let selectedAttrs = [];
422
- if (typeof typeInformation.explicitAttrs === 'string') {
423
- // explicitAttrs is a jexlExpression
424
- // This ctxt should include all possible attrs
425
- const attributesCtxt = [];
426
- if (typeInformation.static) {
427
- typeInformation.static.forEach(function (att) {
428
- attributesCtxt.push(att);
429
- });
430
- }
431
- // Measures
432
- for (let i = 0; i < attributes.length; i++) {
433
- if (attributes[i].name && attributes[i].type) {
434
- const measureAttr = {
435
- name: attributes[i].name,
436
- value: attributes[i].value,
437
- type: attributes[i].type
438
- };
439
- attributesCtxt.push(measureAttr);
440
- // check measureAttr by object_id -> if in active
441
- let j = 0;
442
- let found = false;
443
- while (j < typeInformation.active.length && !found) {
444
- if (attributes[i].name === typeInformation.active[j].object_id) {
445
- let measureAttrByObjectId = {
446
- name: typeInformation.active[j].name,
447
- value: attributes[i].value,
448
- type: attributes[i].type
449
- };
450
- attributesCtxt.push(measureAttrByObjectId);
451
- found = true;
452
- }
453
- j++;
454
- }
455
- }
456
- }
457
- // This context is just to calculate explicitAttrs when is an expression
350
+ }
458
351
 
459
- let ctxt = expressionPlugin.extractContext(attributesCtxt.concat(idTypeSSSList));
460
- // typeInformation.active all attrs with expressions
461
- if (typeInformation.active) {
462
- typeInformation.active.forEach(function (att) {
463
- if (att.expression !== undefined) {
464
- let expandedAttr = {
465
- name: att.name,
466
- value: att.expression,
467
- type: att.type
468
- };
469
- attributesCtxt.push(expandedAttr);
470
- if (att.object_id !== undefined) {
471
- let expandedAttrByObjectId = {
472
- name: att.object_id,
473
- value: att.expression,
474
- type: att.type
475
- };
476
- attributesCtxt.push(expandedAttrByObjectId);
477
- }
478
- ctxt = expressionPlugin.extractContext(attributesCtxt.concat(idTypeSSSList));
479
- }
480
- });
481
- }
482
- // calculate expression for explicitAttrs
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
+ ) {
483
389
  try {
484
- logger.debug(context, 'sendUpdateValueNgsi2 selectedAttrs ctxt %j', ctxt);
485
- let res = jexlParser.applyExpression(typeInformation.explicitAttrs, ctxt, typeInformation);
486
- if (res === true) {
487
- // like explicitAttrs == true
488
- // selectAttrs should be measures which are defined attributes
489
- typeInformation.active.forEach((attr) => {
490
- selectedAttrs.push(attr.name);
491
- selectedAttrs.push(attr.object_id);
492
- });
493
- } else if (res === false) {
494
- // like explicitAttrs == false
495
- // selectAttrs should be measures and defined attributes
496
- typeInformation.active.forEach((attr) => {
497
- selectedAttrs.push(attr.name);
498
- selectedAttrs.push(attr.object_id);
499
- });
500
- for (let i = 0; i < attributes.length; i++) {
501
- selectedAttrs.push(attributes[i].name);
502
- }
503
- } else {
504
- selectedAttrs = res; // TBD: Check ensure is an array of strings
505
- }
506
- if (selectedAttrs.length === 0) {
507
- // implies do nothing
508
- logger.info(
509
- context,
510
- 'sendUpdateValueNgsi2 none selectedAttrs with %j and ctxt %j',
511
- typeInformation.explicitAttrs,
512
- ctxt
513
- );
514
- return callback(null);
390
+ logger.debug(context,
391
+ 'Evaluating attribute: %j, for entity_name(exp):%j, with ctxt: %j',
392
+ currentAttr.name,
393
+ currentAttr.entity_name,
394
+ jexlctxt
395
+ );
396
+ attrEntityName = jexlParser.applyExpression(currentAttr.entity_name, jexlctxt, typeInformation);
397
+ if (!attrEntityName) {
398
+ attrEntityName = currentAttr.entity_name;
515
399
  }
516
400
  } catch (e) {
517
- // nothing to do: exception is already logged at info level
401
+ logger.debug(
402
+ context,
403
+ 'Exception evaluating entityNameExp:%j, with jexlctxt: %j',
404
+ currentAttr.entity_name,
405
+ jexlctxt
406
+ );
407
+ attrEntityName = currentAttr.entity_name;
518
408
  }
519
-
520
- typeInformation.active.forEach((attr) => {
521
- if (selectedAttrs.includes(attr.name)) {
522
- selectedAttrs.push(attr.object_id);
523
- }
524
- // Check if selectedAttrs includes an attribute with format {object_id: xxxx}
525
- if (selectedAttrs.includes({ object_id: attr.object_id })) {
526
- selectedAttrs.push(attr.object_id);
527
- }
528
- });
529
- } else if (typeInformation.explicitAttrs && typeof typeInformation.explicitAttrs === 'boolean') {
530
- // explicitAtts is true => Add just measures which are defined in active attributes
531
- // and active attributes with expressions
532
- // and TimeInstant
533
- selectedAttrs = ['TimeInstant'];
534
- typeInformation.active.forEach((attr) => {
535
- // Measures
536
- if (attr.expression !== undefined) {
537
- selectedAttrs.push(attr.name);
538
- selectedAttrs.push(attr.object_id);
539
- } else {
540
- // check if active attr is receiving a measure
541
- let i = 0;
542
- let found = false;
543
- while (i < attributes.length && !found) {
544
- if (attributes[i].name && attributes[i].type) {
545
- if (attributes[i].name === attr.object_id || attributes[i].name === attr.name) {
546
- selectedAttrs.push(attr.name);
547
- selectedAttrs.push(attr.object_id);
548
- found = true;
549
- }
550
- }
551
- i++;
552
- }
553
- }
554
- });
555
409
  }
556
- // This loop adds selected measured values (attributes) into payload entities (entity[0])
557
- for (let i = 0; i < attributes.length; i++) {
558
- if (attributes[i].name && selectedAttrs.includes(attributes[i].name) && attributes[i].type) {
559
- const attr = typeInformation.active.find((obj) => {
560
- return obj.name === attributes[i].name;
561
- });
562
- payload.entities[0][attributes[i].name] = {
563
- value: attributes[i].value,
564
- type: attributes[i].type
565
- };
566
- // ensure payload has attr with proper object_id
567
- if (attr && attr.object_id) {
568
- payload.entities[0][attributes[i].name].object_id = attr.object_id;
569
- }
570
- const metadata = NGSIUtils.getMetaData(typeInformation, attributes[i].name, attributes[i].metadata);
571
- if (metadata) {
572
- payload.entities[0][attributes[i].name].metadata = metadata;
573
- }
574
- } else if (attributes[i].name && !selectedAttrs.includes(attributes[i].name) && attributes[i].type) {
575
- const att = {
576
- name: attributes[i].name,
577
- type: attributes[i].type,
578
- value: attributes[i].value
579
- };
580
- measureAttrsForCtxt.push(att);
581
- }
410
+
411
+ //determine AttrEntityType for multientity
412
+ if (
413
+ currentAttr.entity_type !== null &&
414
+ currentAttr.entity_type !== undefined &&
415
+ currentAttr.entity_type !== '' &&
416
+ typeof currentAttr.entity_type === 'string'
417
+ ) {
418
+ attrEntityType = currentAttr.entity_type;
582
419
  }
583
- logger.debug(
584
- context,
585
- 'sendUpdateValueNgsi2 pre-initial explicitAttrs payload=%j selectedAttrs=%j',
586
- payload,
587
- selectedAttrs
588
- );
589
- let selectedAttrsByObjectId = selectedAttrs
590
- .filter((o) => o !== undefined && o.object_id)
591
- .map(function (el) {
592
- return el.object_id;
593
- });
594
420
 
595
- // Loop for add seleted attrs from type.information.active into pyaload entities (entity[0])
596
- if (typeInformation.active) {
597
- typeInformation.active.forEach((attr) => {
598
- if (selectedAttrs.includes(attr.name)) {
599
- if (attr.object_id) {
600
- payload.entities[0][attr.object_id] = {
601
- value: payload.entities[0][attr.object_id]
602
- ? payload.entities[0][attr.object_id].value
603
- : payload.entities[0][attr.name]
604
- ? payload.entities[0][attr.name].value
605
- : undefined,
606
- type: attr.type,
607
- object_id: attr.object_id
608
- };
609
- } else {
610
- payload.entities[0][attr.name] = {
611
- value: payload.entities[0][attr.name] ? payload.entities[0][attr.name].value : undefined,
612
- type: attr.type
613
- };
614
- }
615
- } else if (attr.object_id !== undefined && selectedAttrsByObjectId.includes(attr.object_id)) {
616
- payload.entities[0][attr.object_id] = {
617
- value: payload.entities[0][attr.object_id]
618
- ? payload.entities[0][attr.object_id].value
619
- : payload.entities[0][attr.name]
620
- ? payload.entities[0][attr.name].value
621
- : undefined,
622
- type: attr.type,
623
- object_id: attr.object_id
624
- };
421
+ //PRE POPULATE CONTEXT
422
+ jexlctxt[currentAttr.name] = plainMeasures[currentAttr.object_id];
423
+
424
+ //determine Value
425
+ if (currentAttr.value !== undefined) {
426
+ //static attributes already have a value
427
+ hitted = true;
428
+ valueExpression = currentAttr.value;
429
+ } else if (plainMeasures[currentAttr.object_id] !== undefined) {
430
+ //we have got a meaure for that Attr
431
+ //actives ¿lazis?
432
+ hitted = true;
433
+ valueExpression = plainMeasures[currentAttr.object_id];
434
+ }
435
+ //remove measures that has been shadowed by an alias (some may be left and managed later)
436
+ //Maybe we must filter object_id if there is name == object_id
437
+ measures = measures.filter((item) => item.name !== currentAttr.object_id && item.name !== currentAttr.name);
438
+
439
+ if (
440
+ currentAttr.expression !== undefined &&
441
+ currentAttr.expression !== '' &&
442
+ typeof currentAttr.expression == 'string'
443
+ ) {
444
+ try {
445
+ hitted = true;
446
+ valueExpression = jexlParser.applyExpression(currentAttr.expression, jexlctxt, typeInformation);
447
+ //we fallback to null if anything unexpecte happend
448
+ if (valueExpression === null || valueExpression === undefined || Number.isNaN(valueExpression)) {
449
+ valueExpression = null;
625
450
  }
626
- });
451
+ } catch (e) {
452
+ valueExpression = null;
453
+ }
454
+ logger.debug(context,
455
+ 'Evaluated attr: %j, with expression: %j, and ctxt: %j resulting: %j',
456
+ currentAttr.name,
457
+ currentAttr.expression,
458
+ jexlctxt,
459
+ valueExpression
460
+ );
627
461
  }
628
- } // END check explicitAttrs
629
- logger.debug(context, 'sendUpdateValueNgsi2 initial payload=%j', payload);
630
-
631
- const currentEntity = payload.entities[0];
632
462
 
633
- // Prepare attributes for expresionPlugin
634
- const attsArray = pluginUtils.extractAttributesArrayFromNgsi2Entity(currentEntity);
463
+ currentAttr.hitted = hitted;
464
+ currentAttr.value = valueExpression;
635
465
 
636
- // Exclude processing all attr expressions when current attr is of type 'commandStatus' or 'commandResult'
637
- let attsArrayFiltered = [];
638
- if (attsArray) {
639
- attsArrayFiltered = attsArray.filter((obj) => {
640
- return ![constants.COMMAND_STATUS, constants.COMMAND_RESULT].includes(obj.type);
641
- });
642
- }
643
- let attributesCtxt = [...attsArrayFiltered]; // just copy
644
- if (typeInformation.static) {
645
- typeInformation.static.forEach(function (att) {
646
- attributesCtxt.push(att);
647
- });
648
- }
649
- if (measureAttrsForCtxt) {
650
- measureAttrsForCtxt.forEach(function (att) {
651
- attributesCtxt.push(att);
652
- });
653
- }
654
- attributesCtxt = attributesCtxt.concat(idTypeSSSList);
655
- let ctxt = expressionPlugin.extractContext(attributesCtxt, typeInformation);
656
- logger.debug(context, 'sendUpdateValueNgsi2 initial ctxt %j ', ctxt);
466
+ //add TimeInstant to attr metadata
467
+ if (mustInsertTimeInstant) {
468
+ if (!currentAttr.metadata) {
469
+ currentAttr.metadata = {};
470
+ }
471
+ currentAttr.metadata[constants.TIMESTAMP_ATTRIBUTE] = timestamp;
472
+ }
657
473
 
658
- // Sort currentEntity to get first attrs without expressions (checking attrs in typeInformation.active)
659
- // attributes without expressions should be processed before
660
- logger.debug(context, 'sendUpdateValueNgsi2 currentEntity %j ', currentEntity);
661
- if (typeInformation.active && typeInformation.active.length > 0) {
662
- for (const k in currentEntity) {
663
- typeInformation.active.forEach(function (att) {
664
- if (
665
- (att.object_id && att.object_id === k && att.expression) ||
666
- (att.name && att.name === k && att.expression)
667
- ) {
668
- const m = currentEntity[k];
669
- delete currentEntity[k];
670
- currentEntity[k] = m; // put into the end of currentEntity
671
- }
672
- });
474
+ //store de New Attributte in entity data structure
475
+ if (hitted === true) {
476
+ if (entities[attrEntityName] === undefined) {
477
+ entities[attrEntityName] = {};
478
+ }
479
+ if (entities[attrEntityName][attrEntityType] === undefined) {
480
+ entities[attrEntityName][attrEntityType] = [];
481
+ }
482
+ //store de New Attributte
483
+ entities[attrEntityName][attrEntityType].push(currentAttr);
673
484
  }
485
+
486
+ //RE-Populate de JEXLcontext (except for null or NaN we preffer undefined)
487
+ jexlctxt[currentAttr.name] = valueExpression;
674
488
  }
675
489
 
676
- // Evaluate entityNameExp with a context including measures
677
- if (typeInformation.entityNameExp !== undefined && typeInformation.entityNameExp !== '') {
490
+ //now we can compute explicit (Bool or Array) with the complete JexlContext
491
+ let explicit = false;
492
+ if (typeof typeInformation.explicitAttrs === 'string') {
678
493
  try {
679
- logger.debug(context, 'sendUpdateValueNgsi2 entityNameExp %j ', typeInformation.entityNameExp);
680
- entityName = expressionPlugin.applyExpression(typeInformation.entityNameExp, ctxt, typeInformation);
681
- // CB entity id should be always a String
682
- entityName = String(entityName);
683
- payload.entities[0].id = entityName;
684
- ctxt['entity_name'] = entityName;
685
- } catch (e) {
494
+ explicit = jexlParser.applyExpression(typeInformation.explicitAttrs, jexlctxt, typeInformation);
495
+ if (explicit instanceof Array && mustInsertTimeInstant) {
496
+ explicit.push(constants.TIMESTAMP_ATTRIBUTE);
497
+ }
686
498
  logger.debug(
687
499
  context,
688
- 'Error evaluating expression for entityName: %s with context: %s',
689
- typeInformation.entityNameExp,
690
- ctxt
500
+ 'Calculated explicitAttrs with expression: %j and ctxt: %j resulting: %j',
501
+ typeInformation.explicitAttrs,
502
+ jexlctxt,
503
+ explicit
691
504
  );
505
+ } catch (e) {
506
+ // nothing to do: exception is already logged at info level
692
507
  }
508
+ } else if (typeof typeInformation.explicitAttrs == 'boolean') {
509
+ explicit = typeInformation.explicitAttrs;
693
510
  }
694
511
 
695
- logger.debug(context, 'sendUpdateValueNgsi2 currentEntity sorted %j ', currentEntity);
696
- let timestampValue = undefined;
697
- // Loop for each final attribute to apply alias, multientity and expressions
698
- for (const j in currentEntity) {
699
- // discard id and type
700
- if (j !== 'id' || j !== 'type') {
701
- // Apply Mapping Alias: object_id in attributes are in typeInformation.active
702
- let attr;
703
- let newAttr = payload.entities[0][j];
704
- if (typeInformation.active) {
705
- attr = typeInformation.active.find((obj) => {
706
- return obj.object_id === j;
707
- });
708
- }
709
- if (!attr) {
710
- if (typeInformation.lazy) {
711
- attr = typeInformation.lazy.find((obj) => {
712
- return obj.object_id === j;
713
- });
714
- }
715
- }
716
- if (!attr) {
717
- if (typeInformation.active) {
718
- attr = typeInformation.active.find((obj) => {
719
- return obj.name === j;
720
- });
721
- }
722
- }
723
- if (attr && attr.name) {
724
- if (['id', 'type'].includes(attr.name)) {
725
- // invalid mapping
726
- logger.debug(
727
- context,
728
- 'sendUpdateValueNgsi2 invalid mapping for attr=%j newAttr=%j',
729
- attr,
730
- newAttr
731
- );
732
- delete payload.entities[0][attr.object_id];
733
- attr = undefined; // stop processing attr
734
- newAttr = undefined;
735
- } else {
736
- ctxt[attr.name] = payload.entities[0][j].value;
512
+ //more mesures may be added to the attribute list (unnhandled/left mesaures) l
513
+ if (explicit === false && Object.keys(measures).length > 0) {
514
+ //add Timestamp to measures if needed
515
+ if (mustInsertTimeInstant) {
516
+ for (let currentMeasure of measures) {
517
+ if (!currentMeasure.metadata) {
518
+ currentMeasure.metadata = {};
737
519
  }
520
+ currentMeasure.metadata[constants.TIMESTAMP_ATTRIBUTE] = timestamp;
738
521
  }
739
- logger.debug(
740
- context,
741
- 'sendUpdateValueNgsi2 procesing j=%j attr=%j ctxt=%j newAttr=%j ',
742
- j,
743
- attr,
744
- ctxt,
745
- newAttr
746
- );
747
- if (attr && attr.type) {
748
- newAttr.type = attr.type;
749
- }
522
+ //If just measures in the principal entity we missed the Timestamp.
523
+ }
524
+ entities[entityName][typeInformation.type] = entities[entityName][typeInformation.type].concat(measures);
525
+ }
750
526
 
751
- // Apply expression
752
- if (attr && attr.expression) {
753
- logger.debug(
754
- context,
755
- 'sendUpdateValueNgsi2 apply expression=%j over ctxt=%j and device=%j',
756
- attr.expression,
757
- ctxt,
758
- typeInformation
759
- );
760
- let res = null;
761
- try {
762
- if (expressionPlugin.contextAvailable(attr.expression, ctxt, typeInformation)) {
763
- res = expressionPlugin.applyExpression(attr.expression, ctxt, typeInformation);
764
- if (
765
- // By default undefined is handled like null: should not progress
766
- // Some op results (like nonexistent * 2) are a kind of null with a number type
767
- // but NaN value
768
- (attr.skipValue === undefined &&
769
- (res === null || (typeof res === 'number' && isNaN(res)))) ||
770
- (attr.skipValue !== undefined &&
771
- (res === attr.skipValue ||
772
- (typeof res === 'number' && isNaN(res) && attr.skipValue === null)))
773
- ) {
774
- logger.debug(
775
- context,
776
- 'sendUpdateValueNgsi2 skip value=%j for res=%j with expression=%j',
777
- attr.skipValue,
778
- res,
779
- attr.expression
780
- );
781
- delete payload.entities[0][j]; // remove measure attr
782
- attr = undefined; // stop process attr
783
- }
784
- } else {
785
- logger.info(
786
- context,
787
- 'sendUpdateValueNgsi2 no context available for apply expression=%j',
788
- attr.expression
789
- );
790
- res = newAttr.value; // keep newAttr value
791
- }
792
- } catch (e) {
793
- logger.error(context, 'sendUpdateValueNgsi2 apply expression exception=%j', e);
794
- if (attr && attr.name) {
795
- res = ctxt[attr.name];
796
- }
797
- }
798
- // jexl expression plugin
799
- newAttr.value = res;
527
+ //PRE-PROCESSING FINISHED
528
+ //Explicit ATTRS and SKIPVALUES will be managed while we build NGSI payload
800
529
 
801
- logger.debug(context, 'sendUpdateValueNgsi2 apply expression result=%j newAttr=%j', res, newAttr);
802
- // update current context with value
803
- if (attr && attr.name && ctxt[attr.name] !== undefined) {
804
- ctxt[attr.name] = newAttr.value;
805
- }
806
- }
530
+ //Get ready to build and send NGSI payload (entities-->payload)
531
+ payload.actionType = 'append';
807
532
 
808
- // Apply Multientity: entity_type and entity_name in attributes are in typeInformation.active
809
- if (attr && (attr.entity_type || attr.entity_name)) {
810
- // Create a newEntity for this attribute
811
- let newEntityName = null;
812
- if (attr.entity_name) {
813
- try {
814
- if (expressionPlugin.contextAvailable(attr.entity_name, ctxt, typeInformation)) {
815
- newEntityName = expressionPlugin.applyExpression(attr.entity_name, ctxt, typeInformation);
816
- } else {
817
- logger.info(
818
- context,
819
- 'sendUpdateValueNgsi2 MULTI no context available for apply expression=%j',
820
- attr.entity_name
821
- );
822
- newEntityName = attr.entity_name;
823
- }
824
- newEntityName = newEntityName ? newEntityName : attr.entity_name;
825
- } catch (e) {
826
- logger.error(context, 'sendUpdateValueNgsi2 MULTI apply expression exception=%j', e);
827
- newEntityName = attr.entity_name;
828
- }
829
- logger.debug(
830
- context,
831
- 'sendUpdateValueNgsi2 MULTI apply expression=%j result=%j payload=%j',
832
- attr.entity_name,
833
- newEntityName,
834
- payload
835
- );
836
- }
837
- // CB entity id and type should be always a String
838
- let newEntity = {
839
- id: newEntityName ? String(newEntityName) : String(payload.entities[0].id),
840
- type: attr.entity_type ? String(attr.entity_type) : String(payload.entities[0].type)
841
- };
842
- // Check if there is already a newEntity created
843
- const alreadyEntity = payload.entities.find((entity) => {
844
- return entity.id === newEntity.id && entity.type === newEntity.type;
845
- });
846
- if (alreadyEntity) {
847
- // Use alreadyEntity
848
- alreadyEntity[attr.name] = newAttr;
849
- } else {
850
- // Add newEntity to payload.entities
851
- newEntity[attr.name] = newAttr;
852
- if (
853
- 'timestamp' in typeInformation && typeInformation.timestamp !== undefined
854
- ? typeInformation.timestamp
855
- : config.getConfig().timestamp !== undefined
856
- ? config.getConfig().timestamp
857
- : timestampValue !== undefined
858
- ) {
859
- newEntity = addTimestampNgsi2(newEntity, typeInformation.timezone, timestampValue);
860
- logger.debug(context, 'sendUpdateValueNgsi2 timestamped newEntity=%j', newEntity);
861
- }
862
- payload.entities.push(newEntity);
863
- }
864
- if (attr && attr.name) {
865
- if (attr.name !== j) {
866
- logger.debug(
867
- context,
868
- 'sendUpdateValueNgsi2 MULTI remove measure attr=%j keep alias j=%j from %j',
869
- j,
870
- attr,
871
- payload
872
- );
873
- delete payload.entities[0][j];
874
- }
875
- }
876
- // if (attr && (attr.entity_type || attr.entity_name))
877
- } else {
878
- // Not a multientity attr
879
- if (attr && attr.name) {
880
- payload.entities[0][attr.name] = newAttr;
881
- if (attr.name !== j) {
882
- delete payload.entities[0][j]; // keep alias name, remove measure name
883
- }
884
- }
885
- if (newAttr && newAttr.type === constants.TIMESTAMP_TYPE_NGSI2 && newAttr.value) {
886
- const extendedTime = compressTimestampPlugin.fromBasicToExtended(newAttr.value);
887
- if (extendedTime) {
888
- // TBD: there is not flag about compressTimestamp in iotagent-node-lib,
889
- // but there is one in agents
890
- newAttr.value = extendedTime;
891
- }
892
- }
893
- if (j === constants.TIMESTAMP_ATTRIBUTE) {
894
- if (newAttr && newAttr.type === constants.TIMESTAMP_TYPE_NGSI2 && newAttr.value) {
895
- timestampValue = newAttr.value;
896
- logger.debug(
897
- context,
898
- 'sendUpdateValueNgsi2 newAttr is TimeInstant and new payload=%j',
899
- payload
900
- );
901
- }
902
- }
533
+ payload.entities = [];
534
+ for (let ename in entities) {
535
+ for (let etype in entities[ename]) {
536
+ let e = {};
537
+ e.id = String(ename);
538
+ e.type = String(etype);
539
+ //extract attributes
540
+ let isEmpty = true;
541
+ for (let attr of entities[ename][etype]) {
542
+ //Handling id/type measures, skip, hit & explicit (condition)
903
543
  if (
904
- newAttr &&
905
- newAttr.metadata &&
906
- newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE] &&
907
- newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].type === constants.TIMESTAMP_TYPE_NGSI2 &&
908
- newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].value
544
+ attr.name !== 'id' &&
545
+ attr.name !== 'type' &&
546
+ (attr.value !== attr.skipValue || attr.skipValue === undefined) &&
547
+ (attr.hitted || attr.hitted === undefined) && //undefined is for pure measures
548
+ (typeof explicit === 'boolean' || //true and false already handled
549
+ (explicit instanceof Array && //check the array version
550
+ (explicit.includes(attr.name) ||
551
+ explicit.some(
552
+ (item) => attr.object_id !== undefined && item.object_id === attr.object_id
553
+ ))))
909
554
  ) {
910
- const extendedTime = compressTimestampPlugin.fromBasicToExtended(
911
- newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].value
912
- );
913
- if (extendedTime) {
914
- newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].value = extendedTime;
915
- }
555
+ isEmpty = false;
556
+ e[attr.name] = { type: attr.type, value: attr.value, metadata: attr.metadata };
916
557
  }
917
558
  }
918
- } // if (j !== 'id' || j !== 'type')
919
-
920
- // final attr loop
921
- logger.debug(
922
- context,
923
- 'sendUpdateValueNgsi2 after procesing attr=%j current entity=%j current payload=%j',
924
- j,
925
- currentEntity,
926
- payload
927
- );
928
- }
929
- // for attr loop
930
-
931
- // Add timestamp to paylaod
932
- if (
933
- 'timestamp' in typeInformation && typeInformation.timestamp !== undefined
934
- ? typeInformation.timestamp
935
- : config.getConfig().timestamp !== undefined
936
- ? config.getConfig().timestamp
937
- : timestampValue !== undefined
938
- ) {
939
- if (timestampValue) {
940
- // timeInstant is provided as measure
941
- if (payload.entities.length > 0) {
942
- for (let n = 0; n < payload.entities.length; n++) {
943
- // include metadata with TimeInstant in attrs when TimeInstant is provided as measure in all entities
944
- payload.entities[n] = addTimestampNgsi2(
945
- payload.entities[n],
946
- typeInformation.timezone,
947
- timestampValue
948
- );
559
+ if (!isEmpty) {
560
+ if (mustInsertTimeInstant) {
561
+ e[constants.TIMESTAMP_ATTRIBUTE] = timestamp;
949
562
  }
950
- }
951
- } else {
952
- // jshint maxdepth:5
953
- for (let n = 0; n < payload.entities.length; n++) {
954
- if (!utils.isTimestampedNgsi2(payload.entities[n])) {
955
- // legacy check needed?
956
- payload.entities[n] = addTimestampNgsi2(payload.entities[n], typeInformation.timezone);
957
- // jshint maxdepth:5
958
- } else if (!utils.IsValidTimestampedNgsi2(payload.entities[n])) {
959
- // legacy check needed?
960
- logger.error(context, 'Invalid timestamp:%s', JSON.stringify(payload.entities[0]));
961
- callback(new errors.BadTimestamp(payload.entities));
962
- return;
963
- }
964
- }
965
- }
966
- }
967
- logger.debug(context, 'sendUpdateValueNgsi2 ending payload=%j', payload);
968
-
969
- for (let m = 0; m < payload.entities.length; m++) {
970
- for (const key in payload.entities[m]) {
971
- // purge object_id from payload
972
- if (payload.entities[m][key] && payload.entities[m][key].object_id) {
973
- delete payload.entities[m][key].object_id;
563
+ payload.entities.push(e);
974
564
  }
975
565
  }
976
- payload.entities[m] = NGSIUtils.castJsonNativeAttributes(payload.entities[m]); // native types
977
566
  }
978
- logger.debug(context, 'sendUpdateValueNgsi2 payload with native types and without object_id=%j', payload);
979
-
567
+
568
+ let url = '/v2/op/update';
569
+ let options = NGSIUtils.createRequestObject(url, typeInformation, token);
980
570
  options.json = payload;
981
571
 
982
- // Prevent to update an entity with an empty payload
572
+ // Prevent to update an entity with an empty payload: more than id and type
983
573
  if (
984
574
  Object.keys(options.json).length > 0 &&
985
575
  (options.json.entities.length > 1 ||
986
- (options.json.entities.length === 1 && Object.keys(options.json.entities[0]).length > 2)) // more than id and type
576
+ (options.json.entities.length === 1 && Object.keys(options.json.entities[0]).length > 2))
987
577
  ) {
988
578
  // Final check: (to keep tests unchanged) before do CB requests
989
579
  // one entity -> request /v2/entities/ + entityName + /atts ?type=typeInformation.type
990
580
  // multi entities -> request /v2/op/update
991
581
  // Note that the options object is prepared for the second case (multi entity), so we "patch" it
992
582
  // only in the first case
993
- if (options.json.entities.length === 1) {
583
+
584
+ //Multientity more than one name o more than one type at primary entity
585
+ let multientity = Object.keys(entities).length > 1 || Object.keys(entities[entityName]).length > 1;
586
+
587
+ if (!multientity) {
994
588
  // recreate options object to use single entity update
995
589
  url = '/v2/entities?options=upsert';
996
590
  options = NGSIUtils.createRequestObject(url, typeInformation, token);
997
- options.json = payload.entities[0];
591
+ delete payload.actionType;
592
+
593
+ let entityAttrs = payload.entities[0];
594
+ const transformedObject = {};
595
+ for (let attrname in entityAttrs) {
596
+ let attr = entityAttrs[attrname];
597
+ transformedObject[attrname] = {
598
+ type: attr.type,
599
+ value: attr.value,
600
+ metadata: attr.metadata
601
+ };
602
+ }
603
+ transformedObject.id = entityAttrs.id;
604
+ transformedObject.type = entityAttrs.type;
605
+ options.json = transformedObject;
998
606
  options.method = 'POST';
999
607
  } // else: keep current options object created for a batch update
1000
- logger.debug(context, 'Updating device value in the Context Broker at [%s]', options.url);
1001
- logger.debug(context, 'Using the following NGSI v2 request:\n\n%s\n\n', JSON.stringify(options, null, 4));
608
+
609
+ //Send the NGSI request
610
+ logger.debug(context,
611
+ 'Updating device value in the Context Broker at: %j',
612
+ options.url
613
+ );
614
+ logger.debug(context,
615
+ 'Using the following NGSI v2 request: %j',
616
+ options
617
+ );
618
+
1002
619
  request(
1003
620
  options,
1004
621
  generateNGSI2OperationHandler('update', entityName, typeInformation, token, options, callback)
@@ -1006,19 +623,19 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
1006
623
  } else {
1007
624
  logger.debug(
1008
625
  context,
1009
- 'Not updating device value in the Context Broker at [%s] due to empty payload \n\n[%s]\n\n',
626
+ 'Not updating device value in the Context Broker at: %j, due to empty payload: %j',
1010
627
  options.url,
1011
- JSON.stringify(options, null, 4)
628
+ options
1012
629
  );
1013
630
  callback(null);
1014
631
  }
1015
632
  }
1016
633
 
1017
634
  exports.sendQueryValue = sendQueryValueNgsi2;
1018
- exports.sendUpdateValue = function (entityName, attributes, typeInformation, token, callback) {
1019
- NGSIUtils.applyMiddlewares(NGSIUtils.updateMiddleware, attributes, typeInformation, () => {
1020
- return sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, callback);
635
+ exports.sendUpdateValue = function (entityName, measures, typeInformation, token, callback) {
636
+ NGSIUtils.applyMiddlewares(NGSIUtils.updateMiddleware, measures, typeInformation, () => {
637
+ return sendUpdateValueNgsi2(entityName, measures, typeInformation, token, callback);
1021
638
  });
1022
639
  };
1023
- exports.addTimestamp = addTimestampNgsi2;
640
+
1024
641
  exports.formatGeoAttrs = formatGeoAttrs;