iotagent-node-lib 4.4.0 → 4.6.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.
- package/README.md +67 -272
- package/config.js +2 -1
- package/doc/README.md +1 -1
- package/doc/admin.md +19 -22
- package/doc/api.md +621 -155
- package/doc/deprecated.md +4 -0
- package/doc/devel/architecture.md +5 -135
- package/doc/devel/development.md +224 -12
- package/doc/getting-started.md +114 -53
- package/doc/requirements.txt +1 -1
- package/doc/roadmap.md +5 -5
- package/docker/Mosquitto/Dockerfile +2 -2
- package/docker/Mosquitto/README.md +14 -11
- package/lib/commonConfig.js +7 -1
- package/lib/constants.js +3 -0
- package/lib/fiware-iotagent-lib.js +12 -15
- package/lib/jexlTranformsMap.js +3 -1
- package/lib/model/dbConn.js +1 -4
- package/lib/services/common/alarmManagement.js +3 -0
- package/lib/services/devices/deviceRegistryMemory.js +1 -1
- package/lib/services/devices/deviceRegistryMongoDB.js +2 -2
- package/lib/services/devices/deviceService.js +4 -3
- package/lib/services/devices/devices-NGSI-LD.js +5 -5
- package/lib/services/devices/devices-NGSI-mixed.js +3 -3
- package/lib/services/devices/devices-NGSI-v2.js +5 -5
- package/lib/services/groups/groupService.js +1 -1
- package/lib/services/ngsi/entities-NGSI-LD.js +321 -571
- package/lib/services/ngsi/entities-NGSI-v2.js +348 -281
- package/lib/services/ngsi/ngsiService.js +3 -1
- package/lib/services/ngsi/subscription-NGSI-LD.js +2 -2
- package/lib/services/ngsi/subscription-NGSI-v2.js +2 -2
- package/lib/services/northBound/deviceGroupAdministrationServer.js +42 -6
- package/lib/services/northBound/deviceProvisioningServer.js +5 -5
- package/lib/services/northBound/northboundServer.js +4 -2
- package/lib/services/stats/statsRegistry.js +128 -101
- package/lib/templates/createDevice.json +0 -24
- package/lib/templates/createDeviceLax.json +0 -23
- package/lib/templates/deviceGroup.json +1 -25
- package/lib/templates/updateDevice.json +0 -24
- package/lib/templates/updateDeviceLax.json +0 -23
- package/package.json +2 -2
- package/scripts/legacy_expression_tool/README.md +0 -1
- package/test/functional/README.md +75 -47
- package/test/functional/functional-tests-runner.js +9 -4
- package/test/functional/functional-tests.js +4 -4
- package/test/functional/testCases.js +1251 -257
- package/test/functional/testUtils.js +53 -20
- package/test/unit/examples/deviceProvisioningRequests/provisionFullDevice.json +1 -13
- package/test/unit/examples/deviceProvisioningRequests/updateProvisionDeviceWithApikey.json +4 -0
- package/test/unit/examples/groupProvisioningRequests/multipleConfigGroupsCreation.json +44 -0
- package/test/unit/examples/groupProvisioningRequests/provisionDuplicateConfigGroup.json +35 -0
- package/test/unit/examples/groupProvisioningRequests/provisionFullConfigGroup.json +36 -0
- package/test/unit/examples/groupProvisioningRequests/provisionFullConfigGroupAlternate.json +36 -0
- package/test/unit/general/deviceService-test.js +102 -0
- package/test/unit/general/statistics-service_test.js +1 -74
- package/test/unit/mongodb/mongodb-configGroup-registry-test.js +452 -0
- package/test/unit/mongodb/mongodb-connectionoptions-test.js +2 -3
- package/test/unit/mongodb/mongodb-group-registry-test.js +34 -33
- package/test/unit/mongodb/mongodb-service-registry-test.js +477 -0
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin1a.json +4 -4
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin2.json +22 -22
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin29.json +4 -4
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin32.json +14 -15
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin1.json +23 -23
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin15.json +0 -5
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin4.json +11 -16
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin5.json +23 -28
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin6.json +8 -13
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin7.json +0 -5
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin8.json +24 -29
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityTimestampPlugin2.json +12 -17
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextStaticLinkedAttributes.json +12 -10
- package/test/unit/ngsi-ld/expressions/jexlBasedTransformations-test.js +0 -102
- package/test/unit/ngsi-ld/plugins/multientity-plugin_test.js +4 -5
- package/test/unit/ngsi-ld/provisioning/device-update-registration_test.js +5 -5
- package/test/unit/ngsi-ld/provisioning/listProvisionedDevices-test.js +0 -4
- package/test/unit/ngsiv2/general/deviceService-test.js +94 -1
- package/test/unit/ngsiv2/general/iotam-autoregistration-test.js +195 -0
- package/test/unit/ngsiv2/provisioning/device-group-api-test.js +259 -0
- package/test/unit/ngsiv2/provisioning/device-provisioning-configGroup-api_test.js +1189 -0
- package/test/unit/ngsiv2/provisioning/device-update-registration_test.js +6 -6
- package/test/unit/ngsiv2/provisioning/listProvisionedDevices-test.js +0 -4
- package/test/unit/ngsiv2/provisioning/updateProvisionedDevices-test.js +35 -0
- package/test/unit/statsRegistry/openmetrics-test.js +167 -0
- package/lib/templates/queryContext.json +0 -25
- package/test/unit/examples/deviceProvisioningRequests/provisionBidirectionalDevice.json +0 -35
- package/test/unit/examples/deviceProvisioningRequests/provisionDeviceBidirectionalGroup.json +0 -17
- package/test/unit/examples/groupProvisioningRequests/bidirectionalGroup.json +0 -31
- package/test/unit/general/statistics-persistence_test.js +0 -121
- package/test/unit/ngsi-ld/examples/contextRequests/createBidirectionalDevice.json +0 -17
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextProcessTimestamp.json +0 -12
- package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotification.json +0 -13
- package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotificationWithDatasetId.json +0 -21
- package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotificationWithMetadata.json +0 -17
- package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalSubscriptionRequest.json +0 -23
- package/test/unit/ngsi-ld/plugins/timestamp-processing-plugin_test.js +0 -132
- package/test/unit/ngsiv2/examples/contextRequests/createBidirectionalDevice.json +0 -8
- package/test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalNotification.json +0 -13
- package/test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalNotificationWithMetadata.json +0 -19
- package/test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalSubscriptionRequest.json +0 -24
|
@@ -260,8 +260,8 @@ function sendQueryValueNgsi2(entityName, attributes, typeInformation, token, cal
|
|
|
260
260
|
* @param {Object} typeInformation Configuration information for the device.
|
|
261
261
|
* @param {String} token User token to identify against the PEP Proxies (optional).
|
|
262
262
|
*/
|
|
263
|
-
function sendUpdateValueNgsi2(entityName,
|
|
264
|
-
//aux
|
|
263
|
+
function sendUpdateValueNgsi2(entityName, originMeasures, originTypeInformation, token, callback) {
|
|
264
|
+
//aux functions used to builf JEXL context.
|
|
265
265
|
//it returns a flat object from an Attr array
|
|
266
266
|
function reduceAttrToPlainObject(attrs, initObj = {}) {
|
|
267
267
|
if (attrs !== undefined && Array.isArray(attrs)) {
|
|
@@ -273,339 +273,389 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call
|
|
|
273
273
|
return initObj;
|
|
274
274
|
}
|
|
275
275
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
276
|
+
//it returns a metadata object using the same structure described
|
|
277
|
+
// at https://github.com/telefonicaid/fiware-orion/blob/master/doc/manuals/orion-api.md#metadata-support
|
|
278
|
+
function reduceMetadataAttrToPlainObject(attrs, initObj = {}) {
|
|
279
|
+
if (attrs !== undefined && Array.isArray(attrs)) {
|
|
280
|
+
return attrs.reduce((result, item) => {
|
|
281
|
+
if (result['metadata'] === undefined) {
|
|
282
|
+
result['metadata'] = {};
|
|
283
|
+
}
|
|
284
|
+
if (item.metadata !== undefined) {
|
|
285
|
+
result['metadata'][item.name] = {};
|
|
286
|
+
for (var meta in item.metadata) {
|
|
287
|
+
result['metadata'][item.name][meta] = item.metadata[meta].value;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return result;
|
|
291
|
+
}, initObj);
|
|
292
|
+
} else {
|
|
293
|
+
return initObj;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
282
296
|
|
|
283
297
|
//Make a clone and overwrite
|
|
284
|
-
|
|
298
|
+
let idTypeSSSList = pluginUtils.getIdTypeServSubServiceFromDevice(originTypeInformation);
|
|
285
299
|
|
|
286
300
|
//Check mandatory information: type
|
|
287
|
-
if (!
|
|
288
|
-
callback(new errors.TypeNotFound(null, entityName,
|
|
301
|
+
if (!originTypeInformation || !originTypeInformation.type) {
|
|
302
|
+
callback(new errors.TypeNotFound(null, entityName, originTypeInformation));
|
|
289
303
|
return;
|
|
290
304
|
}
|
|
291
|
-
//Rename all measures with matches with id and type to measure_id and measure_type
|
|
292
|
-
for (let measure of measures) {
|
|
293
|
-
if (measure.name === 'id' || measure.name === 'type') {
|
|
294
|
-
measure.name = constants.MEASURE + measure.name;
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
305
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
//All the measures (avoid references make another copy instead)
|
|
303
|
-
jexlctxt = reduceAttrToPlainObject(measures);
|
|
304
|
-
//All the static
|
|
305
|
-
jexlctxt = reduceAttrToPlainObject(typeInformation.staticAttributes, jexlctxt);
|
|
306
|
-
//id type Service and Subservice
|
|
307
|
-
jexlctxt = reduceAttrToPlainObject(idTypeSSSList, jexlctxt);
|
|
306
|
+
let payload = {}; //will store the final payload
|
|
307
|
+
let entities = {};
|
|
308
|
+
payload.actionType = 'append';
|
|
309
|
+
payload.entities = [];
|
|
308
310
|
|
|
311
|
+
const currentIsoDate = new Date().toISOString();
|
|
312
|
+
const currentMoment = moment(currentIsoDate);
|
|
309
313
|
//Managing timestamp (mustInsertTimeInstant flag to decide if we should insert Timestamp later on)
|
|
310
|
-
const mustInsertTimeInstant =
|
|
311
|
-
|
|
312
|
-
logger.debug(
|
|
313
|
-
context,
|
|
314
|
-
'sendUpdateValueNgsi2 called with: entityName=%s, measures=%j, typeInformation=%j, initial jexlContext=%j, timestamp=%j',
|
|
315
|
-
entityName,
|
|
316
|
-
plainMeasures,
|
|
317
|
-
typeInformation,
|
|
318
|
-
jexlctxt,
|
|
319
|
-
mustInsertTimeInstant
|
|
320
|
-
);
|
|
314
|
+
const mustInsertTimeInstant =
|
|
315
|
+
originTypeInformation.timestamp !== undefined ? originTypeInformation.timestamp : false;
|
|
321
316
|
|
|
322
|
-
//
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
try {
|
|
326
|
-
logger.debug(context, 'sendUpdateValueNgsi2 entityNameExp %j', typeInformation.entityNameExp);
|
|
327
|
-
entityNameCalc = expressionPlugin.applyExpression(typeInformation.entityNameExp, jexlctxt, typeInformation);
|
|
328
|
-
} catch (e) {
|
|
329
|
-
logger.debug(
|
|
330
|
-
context,
|
|
331
|
-
'Error evaluating expression for entityName: %j with context: %j',
|
|
332
|
-
typeInformation.entityNameExp,
|
|
333
|
-
jexlctxt
|
|
334
|
-
);
|
|
335
|
-
}
|
|
317
|
+
// Check if measures is a single measure or a array of measures (a multimeasure)
|
|
318
|
+
if (originMeasures[0] && !originMeasures[0][0]) {
|
|
319
|
+
originMeasures = [originMeasures];
|
|
336
320
|
}
|
|
321
|
+
for (let measures of originMeasures) {
|
|
322
|
+
entities = {}; //{entityName:{entityType:[attrs]}} //SubGoal Populate entities data structure
|
|
323
|
+
let jexlctxt = {}; //will store the whole context (not just for JEXL)
|
|
324
|
+
|
|
325
|
+
let plainMeasures = null; //will contain measures POJO
|
|
326
|
+
//Make a clone and overwrite
|
|
327
|
+
let typeInformation = JSON.parse(JSON.stringify(originTypeInformation));
|
|
328
|
+
|
|
329
|
+
//Rename all measures with matches with id and type to measure_id and measure_type
|
|
330
|
+
for (let measure of measures) {
|
|
331
|
+
if (measure.name === 'id' || measure.name === 'type') {
|
|
332
|
+
measure.name = constants.MEASURE + measure.name;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
337
335
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
336
|
+
//Make a copy of measures in an plain object: plainMeasures
|
|
337
|
+
plainMeasures = reduceAttrToPlainObject(measures);
|
|
338
|
+
//Build the initital JEXL Context
|
|
339
|
+
//All the measures (avoid references make another copy instead)
|
|
340
|
+
jexlctxt = reduceAttrToPlainObject(measures);
|
|
341
|
+
//All the static
|
|
342
|
+
jexlctxt = reduceAttrToPlainObject(typeInformation.staticAttributes, jexlctxt);
|
|
343
|
+
//id type Service and Subservice
|
|
344
|
+
jexlctxt = reduceAttrToPlainObject(idTypeSSSList, jexlctxt);
|
|
345
|
+
//metadata attributes
|
|
346
|
+
jexlctxt = reduceMetadataAttrToPlainObject(typeInformation.active, jexlctxt);
|
|
347
|
+
//metadata static attributes
|
|
348
|
+
jexlctxt = reduceMetadataAttrToPlainObject(typeInformation.staticAttributes, jexlctxt);
|
|
341
349
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
preprocessedAttr = preprocessedAttr.concat(typeInformation.active);
|
|
352
|
-
}
|
|
350
|
+
logger.debug(
|
|
351
|
+
context,
|
|
352
|
+
'sendUpdateValueNgsi2 loop with: entityName=%s, measures=%j, typeInformation=%j, initial jexlContext=%j, timestamp=%j',
|
|
353
|
+
entityName,
|
|
354
|
+
plainMeasures,
|
|
355
|
+
typeInformation,
|
|
356
|
+
jexlctxt,
|
|
357
|
+
mustInsertTimeInstant
|
|
358
|
+
);
|
|
353
359
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
for (let currentAttr of preprocessedAttr) {
|
|
359
|
-
let hitted = false; //any measure, expressiom or value hit the attr (avoid propagate "silent attr" with null values )
|
|
360
|
-
let attrEntityName = entityName;
|
|
361
|
-
let attrEntityType = typeInformation.type;
|
|
362
|
-
let valueExpression = null;
|
|
363
|
-
//manage active attr without object__id (name by default)
|
|
364
|
-
currentAttr.object_id = currentAttr.object_id ? currentAttr.object_id : currentAttr.name;
|
|
365
|
-
//Enrich the attr (skip, hit, value, meta-timeInstant)
|
|
366
|
-
currentAttr.skipValue = currentAttr.skipValue ? currentAttr.skipValue : null;
|
|
367
|
-
|
|
368
|
-
//determine AttrEntityName for multientity
|
|
369
|
-
if (
|
|
370
|
-
currentAttr.entity_name !== null &&
|
|
371
|
-
currentAttr.entity_name !== undefined &&
|
|
372
|
-
currentAttr.entity_name !== '' &&
|
|
373
|
-
typeof currentAttr.entity_name == 'string'
|
|
374
|
-
) {
|
|
360
|
+
//Now we can calculate the EntityName of primary entity
|
|
361
|
+
let entityNameCalc = null;
|
|
362
|
+
if (typeInformation.entityNameExp !== undefined && typeInformation.entityNameExp !== '') {
|
|
375
363
|
try {
|
|
376
|
-
logger.debug(
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
jexlctxt
|
|
364
|
+
logger.debug(context, 'sendUpdateValueNgsi2 entityNameExp %j', typeInformation.entityNameExp);
|
|
365
|
+
entityNameCalc = expressionPlugin.applyExpression(
|
|
366
|
+
typeInformation.entityNameExp,
|
|
367
|
+
jexlctxt,
|
|
368
|
+
typeInformation
|
|
382
369
|
);
|
|
383
|
-
attrEntityName = jexlParser.applyExpression(currentAttr.entity_name, jexlctxt, typeInformation);
|
|
384
|
-
if (!attrEntityName) {
|
|
385
|
-
attrEntityName = currentAttr.entity_name;
|
|
386
|
-
}
|
|
387
370
|
} catch (e) {
|
|
388
371
|
logger.debug(
|
|
389
372
|
context,
|
|
390
|
-
'
|
|
391
|
-
|
|
373
|
+
'Error evaluating expression for entityName: %j with context: %j',
|
|
374
|
+
typeInformation.entityNameExp,
|
|
392
375
|
jexlctxt
|
|
393
376
|
);
|
|
394
|
-
attrEntityName = currentAttr.entity_name;
|
|
395
377
|
}
|
|
396
378
|
}
|
|
397
379
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
currentAttr.entity_type !== undefined &&
|
|
402
|
-
currentAttr.entity_type !== '' &&
|
|
403
|
-
typeof currentAttr.entity_type === 'string'
|
|
404
|
-
) {
|
|
405
|
-
attrEntityType = currentAttr.entity_type;
|
|
406
|
-
}
|
|
380
|
+
entityName = entityNameCalc ? entityNameCalc : entityName;
|
|
381
|
+
//enrich JEXL context
|
|
382
|
+
jexlctxt['entity_name'] = entityName;
|
|
407
383
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
if (currentAttr.value !== undefined) {
|
|
413
|
-
//static attributes already have a value
|
|
414
|
-
hitted = true;
|
|
415
|
-
valueExpression = currentAttr.value;
|
|
416
|
-
} else if (plainMeasures[currentAttr.object_id] !== undefined) {
|
|
417
|
-
//we have got a meaure for that Attr
|
|
418
|
-
//actives ¿lazis?
|
|
419
|
-
hitted = true;
|
|
420
|
-
valueExpression = plainMeasures[currentAttr.object_id];
|
|
384
|
+
let preprocessedAttr = [];
|
|
385
|
+
//Add Raw Static, Lazy, Command and Actives attr attributes
|
|
386
|
+
if (typeInformation && typeInformation.staticAttributes) {
|
|
387
|
+
preprocessedAttr = preprocessedAttr.concat(typeInformation.staticAttributes);
|
|
421
388
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
389
|
+
if (typeInformation && typeInformation.lazy) {
|
|
390
|
+
preprocessedAttr = preprocessedAttr.concat(typeInformation.lazy);
|
|
391
|
+
}
|
|
392
|
+
if (typeInformation && typeInformation.active) {
|
|
393
|
+
preprocessedAttr = preprocessedAttr.concat(typeInformation.active);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
//Proccess every proto Attribute to populate entities data steuture
|
|
397
|
+
entities[entityName] = {};
|
|
398
|
+
entities[entityName][typeInformation.type] = [];
|
|
399
|
+
|
|
400
|
+
for (let currentAttr of preprocessedAttr) {
|
|
401
|
+
let hitted = false; //any measure, expressiom or value hit the attr (avoid propagate "silent attr" with null values )
|
|
402
|
+
let attrEntityName = entityName;
|
|
403
|
+
let attrEntityType = typeInformation.type;
|
|
404
|
+
let valueExpression = null;
|
|
405
|
+
//manage active attr without object__id (name by default)
|
|
406
|
+
currentAttr.object_id = currentAttr.object_id ? currentAttr.object_id : currentAttr.name;
|
|
407
|
+
//Enrich the attr (skip, hit, value, meta-timeInstant)
|
|
408
|
+
currentAttr.skipValue = currentAttr.skipValue ? currentAttr.skipValue : null;
|
|
409
|
+
|
|
410
|
+
//determine AttrEntityName for multientity
|
|
411
|
+
if (
|
|
412
|
+
currentAttr.entity_name !== null &&
|
|
413
|
+
currentAttr.entity_name !== undefined &&
|
|
414
|
+
currentAttr.entity_name !== '' &&
|
|
415
|
+
typeof currentAttr.entity_name == 'string'
|
|
416
|
+
) {
|
|
417
|
+
try {
|
|
418
|
+
logger.debug(
|
|
419
|
+
context,
|
|
420
|
+
'Evaluating attribute: %j, for entity_name(exp):%j, with ctxt: %j',
|
|
421
|
+
currentAttr.name,
|
|
422
|
+
currentAttr.entity_name,
|
|
423
|
+
jexlctxt
|
|
424
|
+
);
|
|
425
|
+
attrEntityName = jexlParser.applyExpression(currentAttr.entity_name, jexlctxt, typeInformation);
|
|
426
|
+
if (!attrEntityName) {
|
|
427
|
+
attrEntityName = currentAttr.entity_name;
|
|
428
|
+
}
|
|
429
|
+
} catch (e) {
|
|
430
|
+
logger.debug(
|
|
431
|
+
context,
|
|
432
|
+
'Exception evaluating entityNameExp:%j, with jexlctxt: %j',
|
|
433
|
+
currentAttr.entity_name,
|
|
434
|
+
jexlctxt
|
|
435
|
+
);
|
|
436
|
+
attrEntityName = currentAttr.entity_name;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
//determine AttrEntityType for multientity
|
|
441
|
+
if (
|
|
442
|
+
currentAttr.entity_type !== null &&
|
|
443
|
+
currentAttr.entity_type !== undefined &&
|
|
444
|
+
currentAttr.entity_type !== '' &&
|
|
445
|
+
typeof currentAttr.entity_type === 'string'
|
|
446
|
+
) {
|
|
447
|
+
attrEntityType = currentAttr.entity_type;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
//PRE POPULATE CONTEXT
|
|
451
|
+
jexlctxt[currentAttr.name] = plainMeasures[currentAttr.object_id];
|
|
452
|
+
|
|
453
|
+
//determine Value
|
|
454
|
+
if (currentAttr.value !== undefined) {
|
|
455
|
+
//static attributes already have a value
|
|
456
|
+
hitted = true;
|
|
457
|
+
valueExpression = currentAttr.value;
|
|
458
|
+
} else if (plainMeasures[currentAttr.object_id] !== undefined) {
|
|
459
|
+
//we have got a meaure for that Attr
|
|
460
|
+
//actives ¿lazis?
|
|
432
461
|
hitted = true;
|
|
433
|
-
valueExpression =
|
|
434
|
-
|
|
435
|
-
|
|
462
|
+
valueExpression = plainMeasures[currentAttr.object_id];
|
|
463
|
+
}
|
|
464
|
+
//remove measures that has been shadowed by an alias (some may be left and managed later)
|
|
465
|
+
//Maybe we must filter object_id if there is name == object_id
|
|
466
|
+
measures = measures.filter((item) => item.name !== currentAttr.object_id && item.name !== currentAttr.name);
|
|
467
|
+
|
|
468
|
+
if (
|
|
469
|
+
currentAttr.expression !== undefined &&
|
|
470
|
+
currentAttr.expression !== '' &&
|
|
471
|
+
typeof currentAttr.expression == 'string'
|
|
472
|
+
) {
|
|
473
|
+
try {
|
|
474
|
+
hitted = true;
|
|
475
|
+
valueExpression = jexlParser.applyExpression(currentAttr.expression, jexlctxt, typeInformation);
|
|
476
|
+
//we fallback to null if anything unexpecte happend
|
|
477
|
+
if (valueExpression === null || valueExpression === undefined || Number.isNaN(valueExpression)) {
|
|
478
|
+
valueExpression = null;
|
|
479
|
+
}
|
|
480
|
+
} catch (e) {
|
|
436
481
|
valueExpression = null;
|
|
437
482
|
}
|
|
438
|
-
|
|
439
|
-
|
|
483
|
+
logger.debug(
|
|
484
|
+
context,
|
|
485
|
+
'Evaluated attr: %j, with expression: %j, and ctxt: %j resulting: %j',
|
|
486
|
+
currentAttr.name,
|
|
487
|
+
currentAttr.expression,
|
|
488
|
+
jexlctxt,
|
|
489
|
+
valueExpression
|
|
490
|
+
);
|
|
440
491
|
}
|
|
441
|
-
logger.debug(
|
|
442
|
-
context,
|
|
443
|
-
'Evaluated attr: %j, with expression: %j, and ctxt: %j resulting: %j',
|
|
444
|
-
currentAttr.name,
|
|
445
|
-
currentAttr.expression,
|
|
446
|
-
jexlctxt,
|
|
447
|
-
valueExpression
|
|
448
|
-
);
|
|
449
|
-
}
|
|
450
492
|
|
|
451
|
-
|
|
452
|
-
|
|
493
|
+
currentAttr.hitted = hitted;
|
|
494
|
+
currentAttr.value = valueExpression;
|
|
453
495
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
496
|
+
//store de New Attributte in entity data structure
|
|
497
|
+
if (hitted === true) {
|
|
498
|
+
if (entities[attrEntityName] === undefined) {
|
|
499
|
+
entities[attrEntityName] = {};
|
|
500
|
+
}
|
|
501
|
+
if (entities[attrEntityName][attrEntityType] === undefined) {
|
|
502
|
+
entities[attrEntityName][attrEntityType] = [];
|
|
503
|
+
}
|
|
504
|
+
//store de New Attributte
|
|
505
|
+
entities[attrEntityName][attrEntityType].push(currentAttr);
|
|
461
506
|
}
|
|
462
|
-
//store de New Attributte
|
|
463
|
-
entities[attrEntityName][attrEntityType].push(currentAttr);
|
|
464
|
-
}
|
|
465
507
|
|
|
466
|
-
|
|
467
|
-
|
|
508
|
+
//RE-Populate de JEXLcontext (except for null or NaN we preffer undefined)
|
|
509
|
+
jexlctxt[currentAttr.name] = valueExpression;
|
|
468
510
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
511
|
+
// Expand metadata value expression
|
|
512
|
+
if (currentAttr.metadata) {
|
|
513
|
+
for (var metaKey in currentAttr.metadata) {
|
|
514
|
+
if (currentAttr.metadata[metaKey].expression && metaKey !== constants.TIMESTAMP_ATTRIBUTE) {
|
|
515
|
+
let newAttrMeta = {};
|
|
516
|
+
if (currentAttr.metadata[metaKey].type) {
|
|
517
|
+
newAttrMeta['type'] = currentAttr.metadata[metaKey].type;
|
|
518
|
+
}
|
|
519
|
+
let metaValueExpression;
|
|
520
|
+
try {
|
|
521
|
+
metaValueExpression = jexlParser.applyExpression(
|
|
522
|
+
currentAttr.metadata[metaKey].expression,
|
|
523
|
+
jexlctxt,
|
|
524
|
+
typeInformation
|
|
525
|
+
);
|
|
526
|
+
//we fallback to null if anything unexpecte happend
|
|
527
|
+
if (
|
|
528
|
+
metaValueExpression === null ||
|
|
529
|
+
metaValueExpression === undefined ||
|
|
530
|
+
Number.isNaN(metaValueExpression)
|
|
531
|
+
) {
|
|
532
|
+
metaValueExpression = null;
|
|
533
|
+
}
|
|
534
|
+
} catch (e) {
|
|
490
535
|
metaValueExpression = null;
|
|
491
536
|
}
|
|
492
|
-
|
|
493
|
-
|
|
537
|
+
newAttrMeta['value'] = metaValueExpression;
|
|
538
|
+
currentAttr.metadata[metaKey] = newAttrMeta;
|
|
539
|
+
|
|
540
|
+
//RE-Populate de JEXLcontext
|
|
541
|
+
// It is possible metadata is still not in ctxt
|
|
542
|
+
if (!jexlctxt.metadata) {
|
|
543
|
+
jexlctxt.metadata = {};
|
|
544
|
+
}
|
|
545
|
+
if (!jexlctxt.metadata[currentAttr.name]) {
|
|
546
|
+
jexlctxt.metadata[currentAttr.name] = {};
|
|
547
|
+
}
|
|
548
|
+
jexlctxt.metadata[currentAttr.name][currentAttr.metadata[metaKey]] = metaValueExpression;
|
|
494
549
|
}
|
|
495
|
-
newAttrMeta['value'] = metaValueExpression;
|
|
496
|
-
currentAttr.metadata[metaKey] = newAttrMeta;
|
|
497
550
|
}
|
|
498
551
|
}
|
|
499
552
|
}
|
|
500
|
-
}
|
|
501
553
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
554
|
+
//now we can compute explicit (Bool or Array) with the complete JexlContext
|
|
555
|
+
let explicit = false;
|
|
556
|
+
if (typeof typeInformation.explicitAttrs === 'string') {
|
|
557
|
+
try {
|
|
558
|
+
explicit = jexlParser.applyExpression(typeInformation.explicitAttrs, jexlctxt, typeInformation);
|
|
559
|
+
if (explicit instanceof Array && explicit.length > 0 && mustInsertTimeInstant) {
|
|
560
|
+
explicit.push(constants.TIMESTAMP_ATTRIBUTE);
|
|
561
|
+
}
|
|
562
|
+
logger.debug(
|
|
563
|
+
context,
|
|
564
|
+
'Calculated explicitAttrs with expression: %j and ctxt: %j resulting: %j',
|
|
565
|
+
typeInformation.explicitAttrs,
|
|
566
|
+
jexlctxt,
|
|
567
|
+
explicit
|
|
568
|
+
);
|
|
569
|
+
} catch (e) {
|
|
570
|
+
// nothing to do: exception is already logged at info level
|
|
509
571
|
}
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
'Calculated explicitAttrs with expression: %j and ctxt: %j resulting: %j',
|
|
513
|
-
typeInformation.explicitAttrs,
|
|
514
|
-
jexlctxt,
|
|
515
|
-
explicit
|
|
516
|
-
);
|
|
517
|
-
} catch (e) {
|
|
518
|
-
// nothing to do: exception is already logged at info level
|
|
572
|
+
} else if (typeof typeInformation.explicitAttrs == 'boolean') {
|
|
573
|
+
explicit = typeInformation.explicitAttrs;
|
|
519
574
|
}
|
|
520
|
-
} else if (typeof typeInformation.explicitAttrs == 'boolean') {
|
|
521
|
-
explicit = typeInformation.explicitAttrs;
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
//more mesures may be added to the attribute list (unnhandled/left mesaures) l
|
|
525
|
-
if (explicit === false && Object.keys(measures).length > 0) {
|
|
526
|
-
entities[entityName][typeInformation.type] = entities[entityName][typeInformation.type].concat(measures);
|
|
527
|
-
}
|
|
528
575
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
payload.actionType = 'append';
|
|
576
|
+
//more mesures may be added to the attribute list (unnhandled/left mesaures) l
|
|
577
|
+
if (explicit === false && Object.keys(measures).length > 0) {
|
|
578
|
+
entities[entityName][typeInformation.type] = entities[entityName][typeInformation.type].concat(measures);
|
|
579
|
+
}
|
|
534
580
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
for (let
|
|
540
|
-
let
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
timestampAttrs = entities[ename][etype].filter((item) => item.name === constants.TIMESTAMP_ATTRIBUTE);
|
|
549
|
-
if (timestampAttrs && timestampAttrs.length > 0) {
|
|
550
|
-
timestamp.value = timestampAttrs[0]['value'];
|
|
551
|
-
}
|
|
581
|
+
//PRE-PROCESSING FINISHED
|
|
582
|
+
//Explicit ATTRS and SKIPVALUES will be managed while we build NGSI payload
|
|
583
|
+
//Get ready to build and send NGSI payload (entities-->payload)
|
|
584
|
+
|
|
585
|
+
for (let ename in entities) {
|
|
586
|
+
for (let etype in entities[ename]) {
|
|
587
|
+
let e = {};
|
|
588
|
+
e.id = String(ename);
|
|
589
|
+
e.type = String(etype);
|
|
590
|
+
let timestamp = { type: constants.TIMESTAMP_TYPE_NGSI2 }; //timestamp scafold-attr for insertions.
|
|
591
|
+
let timestampAttrs = null;
|
|
592
|
+
if (mustInsertTimeInstant) {
|
|
593
|
+
// get timestamp for current entity
|
|
552
594
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
595
|
+
timestampAttrs = entities[ename][etype].filter(
|
|
596
|
+
(item) => item.name === constants.TIMESTAMP_ATTRIBUTE
|
|
597
|
+
);
|
|
598
|
+
if (timestampAttrs && timestampAttrs.length > 0) {
|
|
599
|
+
timestamp.value = timestampAttrs[0]['value'];
|
|
557
600
|
}
|
|
558
|
-
|
|
559
|
-
if (
|
|
560
|
-
timestamp.value
|
|
561
|
-
|
|
601
|
+
|
|
602
|
+
if (timestamp.value) {
|
|
603
|
+
if (!moment(timestamp.value, moment.ISO_8601, true).isValid()) {
|
|
604
|
+
callback(new errors.BadTimestamp(timestamp.value, entityName, typeInformation));
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
562
607
|
} else {
|
|
563
|
-
|
|
564
|
-
.
|
|
565
|
-
.
|
|
566
|
-
|
|
608
|
+
if (!typeInformation.timezone) {
|
|
609
|
+
timestamp.value = currentIsoDate;
|
|
610
|
+
jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value;
|
|
611
|
+
} else {
|
|
612
|
+
timestamp.value = currentMoment
|
|
613
|
+
.tz(typeInformation.timezone)
|
|
614
|
+
.format('YYYY-MM-DD[T]HH:mm:ss.SSSZ');
|
|
615
|
+
jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value;
|
|
616
|
+
}
|
|
567
617
|
}
|
|
568
618
|
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
619
|
+
//extract attributes
|
|
620
|
+
let isEmpty = true;
|
|
621
|
+
for (let attr of entities[ename][etype]) {
|
|
622
|
+
if (
|
|
623
|
+
attr.name !== 'id' &&
|
|
624
|
+
attr.name !== 'type' &&
|
|
625
|
+
(attr.value !== attr.skipValue || attr.skipValue === undefined) &&
|
|
626
|
+
(attr.hitted || attr.hitted === undefined) && //undefined is for pure measures
|
|
627
|
+
(typeof explicit === 'boolean' || //true and false already handled
|
|
628
|
+
(explicit instanceof Array && //check the array version
|
|
629
|
+
(explicit.includes(attr.name) ||
|
|
630
|
+
explicit.some(
|
|
631
|
+
(item) => attr.object_id !== undefined && item.object_id === attr.object_id
|
|
632
|
+
))))
|
|
633
|
+
) {
|
|
634
|
+
isEmpty = false;
|
|
635
|
+
if (mustInsertTimeInstant) {
|
|
636
|
+
// Add TimeInstant to all attribute metadata of all entities
|
|
637
|
+
if (attr.name !== constants.TIMESTAMP_ATTRIBUTE) {
|
|
638
|
+
if (!attr.metadata) {
|
|
639
|
+
attr.metadata = {};
|
|
640
|
+
}
|
|
641
|
+
attr.metadata[constants.TIMESTAMP_ATTRIBUTE] = timestamp;
|
|
591
642
|
}
|
|
592
|
-
attr.metadata[constants.TIMESTAMP_ATTRIBUTE] = timestamp;
|
|
593
643
|
}
|
|
644
|
+
e[attr.name] = { type: attr.type, value: attr.value, metadata: attr.metadata };
|
|
594
645
|
}
|
|
595
|
-
e[attr.name] = { type: attr.type, value: attr.value, metadata: attr.metadata };
|
|
596
646
|
}
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
647
|
+
if (!isEmpty) {
|
|
648
|
+
if (mustInsertTimeInstant) {
|
|
649
|
+
e[constants.TIMESTAMP_ATTRIBUTE] = timestamp;
|
|
650
|
+
}
|
|
651
|
+
payload.entities.push(e);
|
|
601
652
|
}
|
|
602
|
-
payload.entities.push(e);
|
|
603
653
|
}
|
|
604
654
|
}
|
|
605
|
-
}
|
|
655
|
+
} // end for (let measures of originMeasures)
|
|
606
656
|
|
|
607
657
|
let url = '/v2/op/update';
|
|
608
|
-
let options = NGSIUtils.createRequestObject(url,
|
|
658
|
+
let options = NGSIUtils.createRequestObject(url, originTypeInformation, token);
|
|
609
659
|
options.json = payload;
|
|
610
660
|
|
|
611
661
|
// Prevent to update an entity with an empty payload: more than id and type
|
|
@@ -620,13 +670,17 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call
|
|
|
620
670
|
// Note that the options object is prepared for the second case (multi entity), so we "patch" it
|
|
621
671
|
// only in the first case
|
|
622
672
|
|
|
623
|
-
//
|
|
624
|
-
|
|
673
|
+
//Multi: multientity (more than one name o more than one type at primary entity)
|
|
674
|
+
// of multimeasure (originMeasures is an array of more than one element)
|
|
675
|
+
let multi =
|
|
676
|
+
Object.keys(entities).length > 1 ||
|
|
677
|
+
Object.keys(entities[entityName]).length > 1 ||
|
|
678
|
+
originMeasures.length > 1;
|
|
625
679
|
|
|
626
|
-
if (!
|
|
680
|
+
if (!multi) {
|
|
627
681
|
// recreate options object to use single entity update
|
|
628
682
|
url = '/v2/entities?options=upsert';
|
|
629
|
-
options = NGSIUtils.createRequestObject(url,
|
|
683
|
+
options = NGSIUtils.createRequestObject(url, originTypeInformation, token);
|
|
630
684
|
delete payload.actionType;
|
|
631
685
|
|
|
632
686
|
let entityAttrs = payload.entities[0];
|
|
@@ -643,7 +697,20 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call
|
|
|
643
697
|
transformedObject.type = entityAttrs.type;
|
|
644
698
|
options.json = transformedObject;
|
|
645
699
|
options.method = 'POST';
|
|
646
|
-
}
|
|
700
|
+
} else if (payload.entities.every((entity) => 'TimeInstant' in entity)) {
|
|
701
|
+
// Try sort entities by TimeInstant
|
|
702
|
+
payload.entities.sort(
|
|
703
|
+
(a, b) => new Date(a.TimeInstant.value).getTime() - new Date(b.TimeInstant.value).getTime()
|
|
704
|
+
);
|
|
705
|
+
options.json = payload;
|
|
706
|
+
} else {
|
|
707
|
+
// keep current options object created for a batch update
|
|
708
|
+
logger.debug(
|
|
709
|
+
context,
|
|
710
|
+
"some entities lack the 'TimeInstant' key. Sorting is not feasible: %j ",
|
|
711
|
+
payload.entities
|
|
712
|
+
);
|
|
713
|
+
}
|
|
647
714
|
|
|
648
715
|
//Send the NGSI request
|
|
649
716
|
logger.debug(context, 'Updating device value in the Context Broker at: %j', options.url);
|
|
@@ -651,7 +718,7 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call
|
|
|
651
718
|
|
|
652
719
|
request(
|
|
653
720
|
options,
|
|
654
|
-
generateNGSI2OperationHandler('update', entityName,
|
|
721
|
+
generateNGSI2OperationHandler('update', entityName, originTypeInformation, token, options, callback)
|
|
655
722
|
);
|
|
656
723
|
} else {
|
|
657
724
|
logger.debug(
|