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.
- package/README.md +4 -0
- package/doc/admin.md +1 -13
- package/doc/api.md +116 -18
- package/doc/devel/architecture.md +0 -12
- package/doc/index.md +1 -1
- package/doc/roadmap.md +22 -10
- package/lib/commonConfig.js +0 -11
- package/lib/errors.js +2 -2
- package/lib/model/Device.js +2 -1
- package/lib/model/Group.js +2 -1
- package/lib/model/dbConn.js +22 -11
- package/lib/plugins/expressionPlugin.js +0 -5
- package/lib/plugins/jexlParser.js +15 -31
- package/lib/services/common/genericMiddleware.js +14 -2
- package/lib/services/common/iotManagerService.js +2 -1
- package/lib/services/devices/deviceRegistryMongoDB.js +3 -1
- package/lib/services/devices/deviceService.js +16 -21
- package/lib/services/devices/devices-NGSI-LD.js +5 -98
- package/lib/services/devices/devices-NGSI-mixed.js +0 -14
- package/lib/services/devices/devices-NGSI-v2.js +3 -0
- package/lib/services/groups/groupRegistryMemory.js +0 -25
- package/lib/services/groups/groupRegistryMongoDB.js +20 -19
- package/lib/services/groups/groupService.js +3 -14
- package/lib/services/ngsi/entities-NGSI-LD.js +82 -7
- package/lib/services/ngsi/entities-NGSI-v2.js +297 -696
- package/lib/services/ngsi/ngsiUtils.js +0 -30
- package/lib/services/northBound/deviceProvisioningServer.js +6 -3
- package/lib/templates/createDevice.json +4 -0
- package/lib/templates/createDeviceLax.json +4 -0
- package/lib/templates/deviceGroup.json +4 -0
- package/lib/templates/updateDevice.json +4 -0
- package/lib/templates/updateDeviceLax.json +4 -0
- package/package.json +6 -2
- package/test/functional/README.md +378 -0
- package/test/functional/config-test.js +70 -0
- package/test/functional/functional-tests-runner.js +126 -0
- package/test/functional/functional-tests.js +241 -0
- package/test/functional/testCases.js +2944 -0
- package/test/functional/testUtils.js +251 -0
- package/test/tools/utils.js +25 -0
- package/test/unit/mongodb/mongodb-connectionoptions-test.js +35 -22
- package/test/unit/ngsi-ld/examples/contextRequests/createProvisionedDeviceWithGroupAndStatic2.json +3 -34
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextAliasPlugin6.json +8 -1
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextAliasPlugin7.json +1 -4
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextAliasPlugin8.json +1 -6
- package/test/unit/ngsi-ld/general/contextBrokerOAuthSecurityAccess-test.js +67 -87
- package/test/unit/ngsi-ld/lazyAndCommands/command-test.js +7 -13
- package/test/unit/ngsi-ld/lazyAndCommands/merge-patch-test.js +43 -43
- package/test/unit/ngsi-ld/lazyAndCommands/polling-commands-test.js +19 -29
- package/test/unit/ngsi-ld/ngsiService/languageProperties-test.js +0 -1
- package/test/unit/ngsi-ld/ngsiService/subscriptions-test.js +35 -46
- package/test/unit/ngsi-ld/plugins/alias-plugin_test.js +8 -9
- package/test/unit/ngsi-ld/provisioning/device-provisioning-api_test.js +96 -221
- package/test/unit/ngsi-ld/provisioning/device-registration_test.js +18 -27
- package/test/unit/ngsi-ld/provisioning/device-update-registration_test.js +8 -16
- package/test/unit/ngsi-ld/provisioning/updateProvisionedDevices-test.js +0 -13
- package/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin8.json +4 -4
- package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin29b.json +8 -0
- package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin30.json +1 -1
- package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin32.json +0 -6
- package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin34.json +8 -0
- package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin34b.json +14 -0
- package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin35.json +1 -11
- package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin36b.json +13 -0
- package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin37.json +8 -0
- package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin41.json +1 -11
- package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin10b.json +37 -0
- package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin11.json +0 -4
- package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin15.json +0 -4
- package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin16.json +0 -4
- package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin25.json +4 -0
- package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin4.json +0 -3
- package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin5.json +10 -12
- package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin6.json +0 -4
- package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin7.json +1 -5
- package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin8.json +8 -12
- package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin2.json +0 -4
- package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin3.json +0 -8
- package/test/unit/ngsiv2/examples/contextRequests/updateContextStaticAttributesMetadata.json +7 -1
- package/test/unit/ngsiv2/expressions/jexlBasedTransformations-test.js +898 -28
- package/test/unit/ngsiv2/ngsiService/active-devices-test.js +0 -4
- package/test/unit/ngsiv2/ngsiService/staticAttributes-test.js +267 -0
- package/test/unit/ngsiv2/plugins/alias-plugin_test.js +19 -21
- package/test/unit/ngsiv2/plugins/multientity-plugin_test.js +21 -24
- package/test/unit/ngsiv2/provisioning/device-group-utils-test.js +1 -21
- package/test/unit/ngsiv2/provisioning/device-provisioning-api_test.js +4 -6
- package/CHANGES_NEXT_RELEASE +0 -0
- package/test/unit/ngsi-ld/ngsiService/autocast-test.js +0 -438
- package/test/unit/ngsi-ld/ngsiService/geoproperties-test.js +0 -381
- package/test/unit/ngsi-ld/provisioning/singleConfigurationMode-test.js +0 -311
- package/test/unit/ngsiv2/ngsiService/autocast-test.js +0 -325
- package/test/unit/ngsiv2/ngsiService/geoproperties-test.js +0 -427
- package/test/unit/ngsiv2/plugins/compress-timestamp-plugin_test.js +0 -217
- package/test/unit/ngsiv2/plugins/timestamp-processing-plugin_test.js +0 -119
- 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}
|
|
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,
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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
|
-
|
|
370
|
-
|
|
371
|
-
let
|
|
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
|
-
|
|
374
|
-
|
|
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
|
-
|
|
383
|
-
|
|
384
|
-
let measureAttrsForCtxt = [];
|
|
286
|
+
//Make a copy of measures in an plain object: plainMeasures
|
|
287
|
+
plainMeasures = reduceAttrToPlainObject(measures);
|
|
385
288
|
|
|
386
|
-
//
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
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
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
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
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
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
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
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
|
-
//
|
|
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
|
|
690
|
-
|
|
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: %
|
|
345
|
+
'Error evaluating expression for entityName: %j with context: %j',
|
|
699
346
|
typeInformation.entityNameExp,
|
|
700
|
-
|
|
347
|
+
jexlctxt
|
|
701
348
|
);
|
|
702
349
|
}
|
|
703
350
|
}
|
|
704
351
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
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
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
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
|
-
'
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
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
|
-
|
|
760
|
-
newAttr.type = attr.type;
|
|
761
|
-
}
|
|
463
|
+
}
|
|
762
464
|
|
|
763
|
-
|
|
764
|
-
|
|
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
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
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
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
933
|
-
|
|
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
|
-
//
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
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
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
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
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
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))
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1013
|
-
|
|
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
|
|
622
|
+
'Not updating device value in the Context Broker at: %j, due to empty payload: %j',
|
|
1022
623
|
options.url,
|
|
1023
|
-
|
|
624
|
+
options
|
|
1024
625
|
);
|
|
1025
626
|
callback(null);
|
|
1026
627
|
}
|
|
1027
628
|
}
|
|
1028
629
|
|
|
1029
630
|
exports.sendQueryValue = sendQueryValueNgsi2;
|
|
1030
|
-
exports.sendUpdateValue = function (entityName,
|
|
1031
|
-
NGSIUtils.applyMiddlewares(NGSIUtils.updateMiddleware,
|
|
1032
|
-
return sendUpdateValueNgsi2(entityName,
|
|
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
|
-
|
|
636
|
+
|
|
1036
637
|
exports.formatGeoAttrs = formatGeoAttrs;
|