iotagent-node-lib 4.5.0 → 4.7.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/.github/workflows/ci.yml +0 -1
- package/Changelog +12 -0
- package/README.md +67 -272
- package/config.js +3 -1
- package/doc/README.md +1 -1
- package/doc/admin.md +40 -18
- package/doc/api.md +532 -136
- 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 +21 -2
- package/lib/constants.js +3 -0
- package/lib/fiware-iotagent-lib.js +12 -15
- package/lib/jexlTranformsMap.js +3 -1
- package/lib/model/Command.js +2 -2
- package/lib/model/Device.js +7 -3
- package/lib/model/Group.js +5 -3
- package/lib/model/dbConn.js +53 -115
- package/lib/services/commands/commandRegistryMongoDB.js +115 -75
- package/lib/services/common/alarmManagement.js +3 -0
- package/lib/services/common/iotManagerService.js +3 -1
- package/lib/services/devices/deviceRegistryMemory.js +36 -0
- package/lib/services/devices/deviceRegistryMongoDB.js +160 -87
- package/lib/services/devices/deviceService.js +33 -3
- package/lib/services/devices/devices-NGSI-v2.js +6 -1
- package/lib/services/groups/groupRegistryMongoDB.js +120 -83
- package/lib/services/groups/groupService.js +1 -1
- package/lib/services/ngsi/entities-NGSI-LD.js +320 -570
- package/lib/services/ngsi/entities-NGSI-v2.js +51 -3
- package/lib/services/ngsi/ngsiService.js +34 -1
- package/lib/services/northBound/deviceGroupAdministrationServer.js +42 -6
- package/lib/services/northBound/deviceProvisioningServer.js +12 -4
- package/lib/services/northBound/northboundServer.js +2 -0
- 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 +12 -24
- package/lib/templates/updateDeviceLax.json +12 -23
- package/package.json +5 -5
- package/scripts/legacy_expression_tool/README.md +0 -1
- package/test/functional/README.md +22 -17
- package/test/functional/config-test.js +3 -2
- package/test/functional/functional-tests-runner.js +9 -4
- package/test/functional/functional-tests.js +4 -4
- package/test/functional/testCases.js +245 -4
- package/test/functional/testUtils.js +2 -2
- package/test/unit/examples/deviceProvisioningRequests/provisionFullDevice.json +1 -13
- 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/examples/groupProvisioningRequests/provisionFullGroup.json +1 -0
- package/test/unit/general/config-multi-core-test.js +1 -2
- package/test/unit/general/contextBrokerKeystoneSecurityAccess-test.js +5 -4
- package/test/unit/general/deviceService-test.js +106 -3
- package/test/unit/general/statistics-service_test.js +1 -74
- package/test/unit/memoryRegistry/deviceRegistryMemory_test.js +6 -5
- package/test/unit/mongodb/mongodb-configGroup-registry-test.js +452 -0
- package/test/unit/mongodb/mongodb-connectionoptions-test.js +9 -42
- 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 +1 -104
- package/test/unit/ngsi-ld/general/config-jsonld-contexts-test.js +1 -2
- package/test/unit/ngsi-ld/plugins/multientity-plugin_test.js +4 -5
- package/test/unit/ngsi-ld/provisioning/listProvisionedDevices-test.js +0 -4
- package/test/unit/ngsi-mixed/provisioning/ngsi-versioning-test.js +8 -5
- package/test/unit/ngsiv2/expressions/jexlBasedTransformations-test.js +42 -41
- package/test/unit/ngsiv2/general/contextBrokerOAuthSecurityAccess-test.js +11 -10
- package/test/unit/ngsiv2/general/deviceService-test.js +98 -4
- package/test/unit/ngsiv2/general/https-support-test.js +1 -1
- package/test/unit/ngsiv2/general/iotam-autoregistration-test.js +195 -0
- package/test/unit/ngsiv2/lazyAndCommands/active-devices-attribute-update-test.js +4 -3
- package/test/unit/ngsiv2/lazyAndCommands/command-test.js +6 -5
- package/test/unit/ngsiv2/lazyAndCommands/lazy-devices-test.js +17 -16
- package/test/unit/ngsiv2/lazyAndCommands/polling-commands-test.js +10 -18
- package/test/unit/ngsiv2/ngsiService/active-devices-test.js +21 -20
- package/test/unit/ngsiv2/ngsiService/staticAttributes-test.js +8 -7
- package/test/unit/ngsiv2/plugins/alias-plugin_test.js +12 -11
- package/test/unit/ngsiv2/plugins/custom-plugin_test.js +3 -2
- package/test/unit/ngsiv2/plugins/multientity-plugin_test.js +28 -27
- package/test/unit/ngsiv2/provisioning/device-group-api-test.js +265 -4
- package/test/unit/ngsiv2/provisioning/device-provisioning-api_test.js +12 -11
- package/test/unit/ngsiv2/provisioning/device-provisioning-configGroup-api_test.js +1190 -0
- package/test/unit/ngsiv2/provisioning/device-registration_test.js +5 -4
- package/test/unit/ngsiv2/provisioning/listProvisionedDevices-test.js +6 -9
- package/test/unit/ngsiv2/provisioning/provisionDeviceMultientity-test.js +1 -1
- package/test/unit/ngsiv2/provisioning/removeProvisionedDevice-test.js +5 -4
- package/test/unit/ngsiv2/provisioning/updateProvisionedDevices-test.js +8 -7
- 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
|
@@ -28,13 +28,11 @@
|
|
|
28
28
|
const request = require('../../request-shim');
|
|
29
29
|
const alarms = require('../common/alarmManagement');
|
|
30
30
|
const errors = require('../../errors');
|
|
31
|
-
const utils = require('../northBound/restUtils');
|
|
32
31
|
const pluginUtils = require('../../plugins/pluginUtils');
|
|
33
32
|
const config = require('../../commonConfig');
|
|
34
33
|
const constants = require('../../constants');
|
|
35
34
|
const jexlParser = require('../../plugins/jexlParser');
|
|
36
35
|
const expressionPlugin = require('../../plugins/expressionPlugin');
|
|
37
|
-
const compressTimestampPlugin = require('../../plugins/compressTimestamp');
|
|
38
36
|
const moment = require('moment-timezone');
|
|
39
37
|
const logger = require('logops');
|
|
40
38
|
const _ = require('underscore');
|
|
@@ -45,83 +43,6 @@ const NGSIUtils = require('./ngsiUtils');
|
|
|
45
43
|
|
|
46
44
|
const NGSI_LD_URN = 'urn:ngsi-ld:';
|
|
47
45
|
|
|
48
|
-
/**
|
|
49
|
-
* Adds timestamp to ngsi payload entities accoding to timezone, and an optional timestampvalue.
|
|
50
|
-
*
|
|
51
|
-
* @param {Object} payload NGSIv2 payload with one or more entities
|
|
52
|
-
* @param String timezone TimeZone value (optional)
|
|
53
|
-
* @param String timestampValue Timestamp value (optional). If not provided current timestamp is used
|
|
54
|
-
* @param Boolean skipMetadataAtt An optional flag to indicate if timestamp should be added to each metadata attribute. Default is false
|
|
55
|
-
* @return {Object} NGSIv2 payload entities with timestamp
|
|
56
|
-
*/
|
|
57
|
-
function addTimestamp(payload, timezone, timestampValue) {
|
|
58
|
-
function addTimestampEntity(entity, timezone, timestampValue) {
|
|
59
|
-
const timestamp = {
|
|
60
|
-
type: constants.TIMESTAMP_TYPE_NGSI2
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
if (timestampValue) {
|
|
64
|
-
timestamp.value = timestampValue;
|
|
65
|
-
} else if (!timezone) {
|
|
66
|
-
timestamp.value = new Date().toISOString();
|
|
67
|
-
} else {
|
|
68
|
-
timestamp.value = moment().tz(timezone).format('YYYY-MM-DD[T]HH:mm:ss.SSSZ');
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function addMetadata(attribute) {
|
|
72
|
-
let timestampFound = false;
|
|
73
|
-
|
|
74
|
-
if (!attribute.metadata) {
|
|
75
|
-
attribute.metadata = {};
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
for (let i = 0; i < attribute.metadata.length; i++) {
|
|
79
|
-
if (attribute.metadata[i] === constants.TIMESTAMP_ATTRIBUTE) {
|
|
80
|
-
if (
|
|
81
|
-
attribute.metadata[constants.TIMESTAMP_ATTRIBUTE].type === constants.TIMESTAMP_TYPE_NGSI2 &&
|
|
82
|
-
attribute.metadata[constants.TIMESTAMP_ATTRIBUTE].value === timestamp.value
|
|
83
|
-
) {
|
|
84
|
-
timestampFound = true;
|
|
85
|
-
break;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (!timestampFound) {
|
|
91
|
-
attribute.metadata[constants.TIMESTAMP_ATTRIBUTE] = timestamp;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return attribute;
|
|
95
|
-
}
|
|
96
|
-
let keyCount = 0;
|
|
97
|
-
for (const key in entity) {
|
|
98
|
-
/* eslint-disable-next-line no-prototype-builtins */
|
|
99
|
-
if (entity.hasOwnProperty(key) && key !== 'id' && key !== 'type') {
|
|
100
|
-
addMetadata(entity[key]);
|
|
101
|
-
keyCount += 1;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
// Add timestamp just to entity with attrs: multientity plugin could
|
|
105
|
-
// create empty entities just with id and type.
|
|
106
|
-
if (keyCount > 0) {
|
|
107
|
-
entity[constants.TIMESTAMP_ATTRIBUTE] = timestamp;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return entity;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (payload instanceof Array) {
|
|
114
|
-
for (let i = 0; i < payload.length; i++) {
|
|
115
|
-
if (!utils.isTimestampedNgsi2(payload[i])) {
|
|
116
|
-
payload[i] = addTimestampEntity(payload[i], timezone, timestampValue);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return payload;
|
|
121
|
-
}
|
|
122
|
-
return addTimestampEntity(payload, timezone, timestampValue);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
46
|
/**
|
|
126
47
|
* Amends an NGSIv2 attribute to NGSI-LD format
|
|
127
48
|
* All native JSON types are respected and cast as Property values
|
|
@@ -503,560 +424,389 @@ function addLinkedEntities(typeInformation, json) {
|
|
|
503
424
|
* @param {Object} typeInformation Configuration information for the device.
|
|
504
425
|
* @param {String} token User token to identify against the PEP Proxies (optional).
|
|
505
426
|
*/
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
427
|
+
/**
|
|
428
|
+
* Makes an update in the Device's entity in the context broker, with the values given in the 'attributes' array. This
|
|
429
|
+
* array should comply to the NGSIv2's attribute format.
|
|
430
|
+
*
|
|
431
|
+
* @param {String} entityName Name of the entity to register.
|
|
432
|
+
* @param {Array} measures measure array containing the values to update.
|
|
433
|
+
* @param {Object} typeInformation Configuration information for the device.
|
|
434
|
+
* @param {String} token User token to identify against the PEP Proxies (optional).
|
|
435
|
+
*/
|
|
436
|
+
function sendUpdateValueNgsiLD(entityName, originMeasures, originTypeInformation, token, callback) {
|
|
437
|
+
//aux function used to builf JEXL context.
|
|
438
|
+
//it returns a flat object from an Attr array
|
|
439
|
+
function reduceAttrToPlainObject(attrs, initObj = {}) {
|
|
440
|
+
if (attrs !== undefined && Array.isArray(attrs)) {
|
|
441
|
+
return attrs.reduce((result, item) => {
|
|
442
|
+
result[item.name] = item.value;
|
|
443
|
+
return result;
|
|
444
|
+
}, initObj);
|
|
445
|
+
} else {
|
|
446
|
+
return initObj;
|
|
517
447
|
}
|
|
518
|
-
];
|
|
519
|
-
|
|
520
|
-
const url = '/ngsi-ld/v1/entityOperations/upsert/?options=update';
|
|
521
|
-
|
|
522
|
-
if (typeInformation && typeInformation.type) {
|
|
523
|
-
payload[0].type = typeInformation.type;
|
|
524
448
|
}
|
|
449
|
+
//Make a clone and overwrite
|
|
450
|
+
const idTypeSSSList = pluginUtils.getIdTypeServSubServiceFromDevice(originTypeInformation);
|
|
525
451
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
452
|
+
//Check mandatory information: type
|
|
453
|
+
if (!originTypeInformation || !originTypeInformation.type) {
|
|
454
|
+
callback(new errors.TypeNotFound(null, entityName, originTypeInformation));
|
|
455
|
+
return;
|
|
530
456
|
}
|
|
531
457
|
|
|
532
|
-
const
|
|
533
|
-
|
|
458
|
+
const payload = []; //will store the final payload
|
|
459
|
+
let entities = {};
|
|
534
460
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
461
|
+
const currentIsoDate = new Date().toISOString();
|
|
462
|
+
const currentMoment = moment(currentIsoDate);
|
|
463
|
+
//Managing timestamp (mustInsertTimeInstant flag to decide if we should insert Timestamp later on)
|
|
464
|
+
const mustInsertTimeInstant =
|
|
465
|
+
originTypeInformation.timestamp !== undefined ? originTypeInformation.timestamp : false;
|
|
538
466
|
|
|
539
|
-
if (
|
|
540
|
-
|
|
541
|
-
|
|
467
|
+
// Check if measures is a single measure or a array of measures (a multimeasure)
|
|
468
|
+
if (originMeasures[0] && !originMeasures[0][0]) {
|
|
469
|
+
originMeasures = [originMeasures];
|
|
542
470
|
}
|
|
543
|
-
const idTypeSSSList = pluginUtils.getIdTypeServSubServiceFromDevice(typeInformation);
|
|
544
|
-
logger.debug(context, 'sendUpdateValueNgsiLD \n idTypeSSS are %j ', idTypeSSSList);
|
|
545
|
-
const measureAttrsForCtxt = [];
|
|
546
471
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
(typeof typeInformation.explicitAttrs === 'boolean' && !typeInformation.explicitAttrs)
|
|
551
|
-
// explicitAttrs is not defined => default case: all attrs should be included
|
|
552
|
-
) {
|
|
553
|
-
// This loop adds all measure values (attributes) into payload entities (entity[0])
|
|
554
|
-
for (let i = 0; i < attributes.length; i++) {
|
|
555
|
-
if (attributes[i].name && attributes[i].type) {
|
|
556
|
-
payload[0][attributes[i].name] = {
|
|
557
|
-
value: attributes[i].value,
|
|
558
|
-
type: attributes[i].type
|
|
559
|
-
};
|
|
560
|
-
const metadata = NGSIUtils.getMetaData(typeInformation, attributes[i].name, attributes[i].metadata);
|
|
561
|
-
if (metadata) {
|
|
562
|
-
payload[0][attributes[i].name].metadata = metadata;
|
|
563
|
-
}
|
|
564
|
-
} else {
|
|
565
|
-
callback(new errors.BadRequest(null, entityName));
|
|
566
|
-
return;
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
logger.debug(context, 'sendUpdateValueNgsiLD \n pre-initial non-explicitAttrs payload=%j', payload);
|
|
570
|
-
// Loop for add attrs from type.information.active (and lazys?) into payload entities (entity[0])
|
|
571
|
-
if (typeInformation.active) {
|
|
572
|
-
typeInformation.active.forEach((attr) => {
|
|
573
|
-
if (attr.expression) {
|
|
574
|
-
if (attr.object_id) {
|
|
575
|
-
payload[0][attr.object_id] = {
|
|
576
|
-
value: payload[0][attr.object_id] ? payload[0][attr.object_id].value : undefined,
|
|
577
|
-
type: attr.type,
|
|
578
|
-
object_id: attr.object_id
|
|
579
|
-
};
|
|
580
|
-
} else {
|
|
581
|
-
payload[0][attr.name] = {
|
|
582
|
-
value: payload[0][attr.name] ? payload[0][attr.name].value : undefined,
|
|
583
|
-
type: attr.type
|
|
584
|
-
};
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
});
|
|
588
|
-
}
|
|
589
|
-
} else {
|
|
590
|
-
let selectedAttrs = [];
|
|
591
|
-
if (typeof typeInformation.explicitAttrs === 'string') {
|
|
592
|
-
// explicitAttrs is a jexlExpression
|
|
593
|
-
// This ctxt should include all possible attrs
|
|
594
|
-
const attributesCtxt = [];
|
|
595
|
-
if (typeInformation.static) {
|
|
596
|
-
typeInformation.static.forEach(function (att) {
|
|
597
|
-
attributesCtxt.push(att);
|
|
598
|
-
});
|
|
599
|
-
}
|
|
600
|
-
// Measures
|
|
601
|
-
for (let i = 0; i < attributes.length; i++) {
|
|
602
|
-
if (attributes[i].name && attributes[i].type) {
|
|
603
|
-
const measureAttr = {
|
|
604
|
-
name: attributes[i].name,
|
|
605
|
-
value: attributes[i].value,
|
|
606
|
-
type: attributes[i].type
|
|
607
|
-
};
|
|
608
|
-
attributesCtxt.push(measureAttr);
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
// This context is just to calculate explicitAttrs when is an expression
|
|
612
|
-
let ctxt = expressionPlugin.extractContext(attributesCtxt.concat(idTypeSSSList));
|
|
613
|
-
// typeInformation.active attrs with expressions expanded by current ctxt
|
|
614
|
-
if (typeInformation.active) {
|
|
615
|
-
typeInformation.active.forEach(function (att) {
|
|
616
|
-
if (att.expression) {
|
|
617
|
-
if (expressionPlugin.contextAvailable(att.expression, ctxt, typeInformation)) {
|
|
618
|
-
const expandedAttr = {
|
|
619
|
-
name: att.name,
|
|
620
|
-
value: att.expression, // it doesn't matter final value here
|
|
621
|
-
type: att.type
|
|
622
|
-
};
|
|
623
|
-
attributesCtxt.push(expandedAttr);
|
|
624
|
-
ctxt = expressionPlugin.extractContext(attributesCtxt.concat(idTypeSSSList));
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
});
|
|
628
|
-
}
|
|
629
|
-
// calculate expression for explicitAttrs
|
|
630
|
-
try {
|
|
631
|
-
const res = jexlParser.applyExpression(typeInformation.explicitAttrs, ctxt, typeInformation);
|
|
632
|
-
if (res === true) {
|
|
633
|
-
// like explicitAttrs == true
|
|
634
|
-
// selectAttrs should be measures which are defined attributes
|
|
635
|
-
typeInformation.active.forEach((attr) => {
|
|
636
|
-
selectedAttrs.push(attr.name);
|
|
637
|
-
selectedAttrs.push(attr.object_id);
|
|
638
|
-
});
|
|
639
|
-
} else if (res === false) {
|
|
640
|
-
// like explicitAttrs == false
|
|
641
|
-
// selectAttrs should be measures and defined attributes
|
|
642
|
-
typeInformation.active.forEach((attr) => {
|
|
643
|
-
selectedAttrs.push(attr.name);
|
|
644
|
-
selectedAttrs.push(attr.object_id);
|
|
645
|
-
});
|
|
646
|
-
for (let i = 0; i < attributes.length; i++) {
|
|
647
|
-
selectedAttrs.push(attributes[i].name);
|
|
648
|
-
}
|
|
649
|
-
} else {
|
|
650
|
-
selectedAttrs = res; // TBD: Check ensure is an array of strings
|
|
651
|
-
}
|
|
652
|
-
if (selectedAttrs.length === 0) {
|
|
653
|
-
// implies do nothing
|
|
654
|
-
logger.info(
|
|
655
|
-
context,
|
|
656
|
-
'sendUpdateValueNgsiLD \n none selectedAttrs with %j and ctxt %j',
|
|
657
|
-
typeInformation.explicitAttrs,
|
|
658
|
-
ctxt
|
|
659
|
-
);
|
|
660
|
-
return callback(null);
|
|
661
|
-
}
|
|
662
|
-
} catch (e) {
|
|
663
|
-
// nothing to do: exception is already logged at info level
|
|
664
|
-
}
|
|
472
|
+
for (let measures of originMeasures) {
|
|
473
|
+
entities = {}; //{entityName:{entityType:[attrs]}} //SubGoal Populate entities data structure
|
|
474
|
+
let jexlctxt = {}; //will store the whole context (not just for JEXL)
|
|
665
475
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
typeInformation.active.forEach((attr) => {
|
|
675
|
-
selectedAttrs.push(attr.name);
|
|
676
|
-
selectedAttrs.push(attr.object_id);
|
|
677
|
-
});
|
|
678
|
-
}
|
|
679
|
-
// This loop adds selected measured values (attributes) into payload entities (entity[0])
|
|
680
|
-
for (let i = 0; i < attributes.length; i++) {
|
|
681
|
-
if (attributes[i].name && selectedAttrs.includes(attributes[i].name) && attributes[i].type) {
|
|
682
|
-
const attr = typeInformation.active.find((obj) => {
|
|
683
|
-
return obj.name === attributes[i].name;
|
|
684
|
-
});
|
|
685
|
-
payload[0][attributes[i].name] = {
|
|
686
|
-
value: attributes[i].value,
|
|
687
|
-
type: attributes[i].type
|
|
688
|
-
};
|
|
689
|
-
// ensure payload has attr with proper object_id
|
|
690
|
-
if (attr && attr.object_id) {
|
|
691
|
-
payload[0][attributes[i].name].object_id = attr.object_id;
|
|
692
|
-
}
|
|
693
|
-
const metadata = NGSIUtils.getMetaData(typeInformation, attributes[i].name, attributes[i].metadata);
|
|
694
|
-
if (metadata) {
|
|
695
|
-
payload[0][attributes[i].name].metadata = metadata;
|
|
696
|
-
}
|
|
697
|
-
} else if (attributes[i].name && !selectedAttrs.includes(attributes[i].name) && attributes[i].type) {
|
|
698
|
-
const att = {
|
|
699
|
-
name: attributes[i].name,
|
|
700
|
-
type: attributes[i].type,
|
|
701
|
-
value: attributes[i].value
|
|
702
|
-
};
|
|
703
|
-
measureAttrsForCtxt.push(att);
|
|
476
|
+
let plainMeasures = null; //will contain measures POJO
|
|
477
|
+
//Make a clone and overwrite
|
|
478
|
+
const typeInformation = JSON.parse(JSON.stringify(originTypeInformation));
|
|
479
|
+
|
|
480
|
+
//Rename all measures with matches with id and type to measure_id and measure_type
|
|
481
|
+
for (const measure of measures) {
|
|
482
|
+
if (measure.name === 'id' || measure.name === 'type') {
|
|
483
|
+
measure.name = constants.MEASURE + measure.name;
|
|
704
484
|
}
|
|
705
485
|
}
|
|
486
|
+
|
|
487
|
+
//Make a copy of measures in an plain object: plainMeasures
|
|
488
|
+
plainMeasures = reduceAttrToPlainObject(measures);
|
|
489
|
+
//Build the initital JEXL Context
|
|
490
|
+
//All the measures (avoid references make another copy instead)
|
|
491
|
+
jexlctxt = reduceAttrToPlainObject(measures);
|
|
492
|
+
//All the static
|
|
493
|
+
jexlctxt = reduceAttrToPlainObject(typeInformation.staticAttributes, jexlctxt);
|
|
494
|
+
//id type Service and Subservice
|
|
495
|
+
jexlctxt = reduceAttrToPlainObject(idTypeSSSList, jexlctxt);
|
|
496
|
+
|
|
706
497
|
logger.debug(
|
|
707
498
|
context,
|
|
708
|
-
'sendUpdateValueNgsiLD
|
|
709
|
-
|
|
710
|
-
|
|
499
|
+
'sendUpdateValueNgsiLD loop with: entityName=%s, measures=%j, typeInformation=%j, initial jexlContext=%j, timestamp=%j',
|
|
500
|
+
entityName,
|
|
501
|
+
plainMeasures,
|
|
502
|
+
typeInformation,
|
|
503
|
+
jexlctxt,
|
|
504
|
+
mustInsertTimeInstant
|
|
711
505
|
);
|
|
712
506
|
|
|
713
|
-
//
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
};
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
});
|
|
507
|
+
//Now we can calculate the EntityName of primary entity
|
|
508
|
+
let entityNameCalc = null;
|
|
509
|
+
if (typeInformation.entityNameExp !== undefined && typeInformation.entityNameExp !== '') {
|
|
510
|
+
try {
|
|
511
|
+
logger.debug(context, 'sendUpdateValueNgsiLD entityNameExp %j', typeInformation.entityNameExp);
|
|
512
|
+
entityNameCalc = expressionPlugin.applyExpression(
|
|
513
|
+
typeInformation.entityNameExp,
|
|
514
|
+
jexlctxt,
|
|
515
|
+
typeInformation
|
|
516
|
+
);
|
|
517
|
+
} catch (e) {
|
|
518
|
+
logger.debug(
|
|
519
|
+
context,
|
|
520
|
+
'Error evaluating expression for entityName: %j with context: %j',
|
|
521
|
+
typeInformation.entityNameExp,
|
|
522
|
+
jexlctxt
|
|
523
|
+
);
|
|
524
|
+
}
|
|
735
525
|
}
|
|
736
|
-
} // END check explicitAttrs
|
|
737
|
-
logger.debug(context, 'sendUpdateValueNgsiLD \n initial payload=%j', payload);
|
|
738
|
-
|
|
739
|
-
const currentEntity = payload[0];
|
|
740
526
|
|
|
741
|
-
|
|
742
|
-
|
|
527
|
+
entityName = entityNameCalc ? entityNameCalc : entityName;
|
|
528
|
+
//enrich JEXL context
|
|
529
|
+
jexlctxt.entity_name = entityName;
|
|
743
530
|
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
return ![constants.COMMAND_STATUS, constants.COMMAND_RESULT].includes(obj.type);
|
|
749
|
-
});
|
|
750
|
-
}
|
|
751
|
-
let attributesCtxt = [...attsArrayFiltered]; // just copy
|
|
752
|
-
if (typeInformation.static) {
|
|
753
|
-
typeInformation.static.forEach(function (att) {
|
|
754
|
-
attributesCtxt.push(att);
|
|
755
|
-
});
|
|
756
|
-
}
|
|
757
|
-
if (measureAttrsForCtxt) {
|
|
758
|
-
measureAttrsForCtxt.forEach(function (att) {
|
|
759
|
-
attributesCtxt.push(att);
|
|
760
|
-
});
|
|
761
|
-
}
|
|
762
|
-
attributesCtxt = attributesCtxt.concat(idTypeSSSList);
|
|
763
|
-
const ctxt = expressionPlugin.extractContext(attributesCtxt);
|
|
764
|
-
logger.debug(context, 'sendUpdateValueNgsiLD \n initial ctxt %j ', ctxt);
|
|
765
|
-
|
|
766
|
-
// Sort currentEntity to get first attrs without expressions (checking attrs in typeInformation.active)
|
|
767
|
-
// attributes without expressions should be processed before
|
|
768
|
-
logger.debug(context, 'sendUpdateValueNgsiLD \n currentEntity %j ', currentEntity);
|
|
769
|
-
if (typeInformation.active && typeInformation.active.length > 0) {
|
|
770
|
-
for (const k in currentEntity) {
|
|
771
|
-
typeInformation.active.forEach(function (att) {
|
|
772
|
-
if (
|
|
773
|
-
(att.object_id && att.object_id === k && att.expression) ||
|
|
774
|
-
(att.name && att.name === k && att.expression)
|
|
775
|
-
) {
|
|
776
|
-
const m = currentEntity[k];
|
|
777
|
-
delete currentEntity[k];
|
|
778
|
-
currentEntity[k] = m; // put into the end of currentEntity
|
|
779
|
-
}
|
|
780
|
-
});
|
|
531
|
+
let preprocessedAttr = [];
|
|
532
|
+
//Add Raw Static, Lazy, Command and Actives attr attributes
|
|
533
|
+
if (typeInformation && typeInformation.staticAttributes) {
|
|
534
|
+
preprocessedAttr = preprocessedAttr.concat(typeInformation.staticAttributes);
|
|
781
535
|
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
if (
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
536
|
+
if (typeInformation && typeInformation.lazy) {
|
|
537
|
+
preprocessedAttr = preprocessedAttr.concat(typeInformation.lazy);
|
|
538
|
+
}
|
|
539
|
+
if (typeInformation && typeInformation.active) {
|
|
540
|
+
preprocessedAttr = preprocessedAttr.concat(typeInformation.active);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
//Proccess every proto Attribute to populate entities data steuture
|
|
544
|
+
entities[entityName] = {};
|
|
545
|
+
entities[entityName][typeInformation.type] = [];
|
|
546
|
+
|
|
547
|
+
for (const currentAttr of preprocessedAttr) {
|
|
548
|
+
let hitted = false; //any measure, expressiom or value hit the attr (avoid propagate "silent attr" with null values )
|
|
549
|
+
let attrEntityName = entityName;
|
|
550
|
+
let attrEntityType = typeInformation.type;
|
|
551
|
+
let valueExpression = null;
|
|
552
|
+
//manage active attr without object__id (name by default)
|
|
553
|
+
currentAttr.object_id = currentAttr.object_id ? currentAttr.object_id : currentAttr.name;
|
|
554
|
+
//Enrich the attr (skip, hit, value, meta-timeInstant)
|
|
555
|
+
currentAttr.skipValue = currentAttr.skipValue ? currentAttr.skipValue : null;
|
|
556
|
+
|
|
557
|
+
//determine AttrEntityName for multientity
|
|
558
|
+
if (
|
|
559
|
+
currentAttr.entity_name !== null &&
|
|
560
|
+
currentAttr.entity_name !== undefined &&
|
|
561
|
+
currentAttr.entity_name !== '' &&
|
|
562
|
+
typeof currentAttr.entity_name === 'string'
|
|
563
|
+
) {
|
|
564
|
+
try {
|
|
565
|
+
logger.debug(
|
|
566
|
+
context,
|
|
567
|
+
'Evaluating attribute: %j, for entity_name(exp):%j, with ctxt: %j',
|
|
568
|
+
currentAttr.name,
|
|
569
|
+
currentAttr.entity_name,
|
|
570
|
+
jexlctxt
|
|
571
|
+
);
|
|
572
|
+
attrEntityName = jexlParser.applyExpression(currentAttr.entity_name, jexlctxt, typeInformation);
|
|
573
|
+
if (!attrEntityName) {
|
|
574
|
+
attrEntityName = currentAttr.entity_name;
|
|
575
|
+
}
|
|
576
|
+
} catch (e) {
|
|
814
577
|
logger.debug(
|
|
815
578
|
context,
|
|
816
|
-
'
|
|
817
|
-
|
|
818
|
-
|
|
579
|
+
'Exception evaluating entityNameExp:%j, with jexlctxt: %j',
|
|
580
|
+
currentAttr.entity_name,
|
|
581
|
+
jexlctxt
|
|
819
582
|
);
|
|
820
|
-
|
|
821
|
-
attr = undefined; // stop processing attr
|
|
822
|
-
newAttr = undefined;
|
|
823
|
-
} else {
|
|
824
|
-
ctxt[attr.name] = payload[0][j].value;
|
|
583
|
+
attrEntityName = currentAttr.entity_name;
|
|
825
584
|
}
|
|
826
585
|
}
|
|
827
|
-
logger.debug(
|
|
828
|
-
context,
|
|
829
|
-
'sendUpdateValueNgsiLD \n procesing j %j attr %j ctxt %j \n newAttr %j ',
|
|
830
|
-
j,
|
|
831
|
-
attr,
|
|
832
|
-
ctxt,
|
|
833
|
-
newAttr
|
|
834
|
-
);
|
|
835
586
|
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
587
|
+
//determine AttrEntityType for multientity
|
|
588
|
+
if (
|
|
589
|
+
currentAttr.entity_type !== null &&
|
|
590
|
+
currentAttr.entity_type !== undefined &&
|
|
591
|
+
currentAttr.entity_type !== '' &&
|
|
592
|
+
typeof currentAttr.entity_type === 'string'
|
|
593
|
+
) {
|
|
594
|
+
attrEntityType = currentAttr.entity_type;
|
|
840
595
|
}
|
|
841
596
|
|
|
842
|
-
//
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
597
|
+
//PRE POPULATE CONTEXT
|
|
598
|
+
jexlctxt[currentAttr.name] = plainMeasures[currentAttr.object_id];
|
|
599
|
+
|
|
600
|
+
//determine Value
|
|
601
|
+
if (currentAttr.value !== undefined) {
|
|
602
|
+
//static attributes already have a value
|
|
603
|
+
hitted = true;
|
|
604
|
+
valueExpression = currentAttr.value;
|
|
605
|
+
} else if (plainMeasures[currentAttr.object_id] !== undefined) {
|
|
606
|
+
//we have got a meaure for that Attr
|
|
607
|
+
//actives ¿lazis?
|
|
608
|
+
hitted = true;
|
|
609
|
+
valueExpression = plainMeasures[currentAttr.object_id];
|
|
610
|
+
}
|
|
611
|
+
//remove measures that has been shadowed by an alias (some may be left and managed later)
|
|
612
|
+
//Maybe we must filter object_id if there is name == object_id
|
|
613
|
+
measures = measures.filter((item) => item.name !== currentAttr.object_id && item.name !== currentAttr.name);
|
|
614
|
+
|
|
615
|
+
if (
|
|
616
|
+
currentAttr.expression !== undefined &&
|
|
617
|
+
currentAttr.expression !== '' &&
|
|
618
|
+
typeof currentAttr.expression === 'string'
|
|
619
|
+
) {
|
|
852
620
|
try {
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
'sendUpdateValueNgsiLD \n no context available for apply expression %j \n',
|
|
859
|
-
attr.expression
|
|
860
|
-
);
|
|
861
|
-
res = newAttr.value; // keep newAttr value
|
|
621
|
+
hitted = true;
|
|
622
|
+
valueExpression = jexlParser.applyExpression(currentAttr.expression, jexlctxt, typeInformation);
|
|
623
|
+
//we fallback to null if anything unexpecte happend
|
|
624
|
+
if (valueExpression === null || valueExpression === undefined || Number.isNaN(valueExpression)) {
|
|
625
|
+
valueExpression = null;
|
|
862
626
|
}
|
|
863
627
|
} catch (e) {
|
|
864
|
-
|
|
865
|
-
res = ctxt[attr.name]; // TBD: add reference to test
|
|
628
|
+
valueExpression = null;
|
|
866
629
|
}
|
|
867
|
-
|
|
868
|
-
// jexl expression plugin
|
|
869
|
-
newAttr.value = res;
|
|
870
|
-
|
|
871
630
|
logger.debug(
|
|
872
631
|
context,
|
|
873
|
-
'
|
|
874
|
-
|
|
875
|
-
|
|
632
|
+
'Evaluated attr: %j, with expression: %j, and ctxt: %j resulting: %j',
|
|
633
|
+
currentAttr.name,
|
|
634
|
+
currentAttr.expression,
|
|
635
|
+
jexlctxt,
|
|
636
|
+
valueExpression
|
|
876
637
|
);
|
|
877
638
|
}
|
|
878
639
|
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
640
|
+
currentAttr.hitted = hitted;
|
|
641
|
+
currentAttr.value = valueExpression;
|
|
642
|
+
|
|
643
|
+
//store de New Attributte in entity data structure
|
|
644
|
+
if (hitted === true) {
|
|
645
|
+
if (entities[attrEntityName] === undefined) {
|
|
646
|
+
entities[attrEntityName] = {};
|
|
647
|
+
}
|
|
648
|
+
if (entities[attrEntityName][attrEntityType] === undefined) {
|
|
649
|
+
entities[attrEntityName][attrEntityType] = [];
|
|
650
|
+
}
|
|
651
|
+
//store de New Attributte
|
|
652
|
+
entities[attrEntityName][attrEntityType].push(currentAttr);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
//RE-Populate de JEXLcontext (except for null or NaN we preffer undefined)
|
|
656
|
+
jexlctxt[currentAttr.name] = valueExpression;
|
|
657
|
+
|
|
658
|
+
// Expand metadata value expression
|
|
659
|
+
if (currentAttr.metadata) {
|
|
660
|
+
for (const metaKey in currentAttr.metadata) {
|
|
661
|
+
if (currentAttr.metadata[metaKey].expression && metaKey !== constants.TIMESTAMP_ATTRIBUTE) {
|
|
662
|
+
const newAttrMeta = {};
|
|
663
|
+
if (currentAttr.metadata[metaKey].type) {
|
|
664
|
+
newAttrMeta.type = currentAttr.metadata[metaKey].type;
|
|
665
|
+
}
|
|
666
|
+
let metaValueExpression;
|
|
667
|
+
try {
|
|
668
|
+
metaValueExpression = jexlParser.applyExpression(
|
|
669
|
+
currentAttr.metadata[metaKey].expression,
|
|
670
|
+
jexlctxt,
|
|
671
|
+
typeInformation
|
|
892
672
|
);
|
|
893
|
-
|
|
673
|
+
//we fallback to null if anything unexpecte happend
|
|
674
|
+
if (
|
|
675
|
+
metaValueExpression === null ||
|
|
676
|
+
metaValueExpression === undefined ||
|
|
677
|
+
Number.isNaN(metaValueExpression)
|
|
678
|
+
) {
|
|
679
|
+
metaValueExpression = null;
|
|
680
|
+
}
|
|
681
|
+
} catch (e) {
|
|
682
|
+
metaValueExpression = null;
|
|
894
683
|
}
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
logger.error(context, 'sendUpdateValueNgsiLD \n MULTI apply expression exception %j \n', e);
|
|
898
|
-
newEntityName = attr.entity_name;
|
|
684
|
+
newAttrMeta.value = metaValueExpression;
|
|
685
|
+
currentAttr.metadata[metaKey] = newAttrMeta;
|
|
899
686
|
}
|
|
900
|
-
logger.debug(
|
|
901
|
-
context,
|
|
902
|
-
'sendUpdateValueNgsiLD \n MULTI apply expression %j \n result %j \n payload %j',
|
|
903
|
-
attr.entity_name,
|
|
904
|
-
newEntityName,
|
|
905
|
-
payload
|
|
906
|
-
);
|
|
907
687
|
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
908
690
|
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
});
|
|
917
|
-
if (alreadyEntity) {
|
|
918
|
-
// Use alreadyEntity
|
|
919
|
-
alreadyEntity[attr.name] = newAttr;
|
|
920
|
-
} else {
|
|
921
|
-
// Add newEntity to payload
|
|
922
|
-
newEntity[attr.name] = newAttr;
|
|
923
|
-
if (
|
|
924
|
-
'timestamp' in typeInformation && typeInformation.timestamp !== undefined
|
|
925
|
-
? typeInformation.timestamp
|
|
926
|
-
: config.getConfig().timestamp !== undefined
|
|
927
|
-
? config.getConfig().timestamp
|
|
928
|
-
: timestampValue !== undefined
|
|
929
|
-
) {
|
|
930
|
-
newEntity = addTimestamp(newEntity, typeInformation.timezone, timestampValue);
|
|
931
|
-
logger.debug(context, 'sendUpdateValueNgsiLD \n timestamped newEntity=%j', newEntity);
|
|
932
|
-
}
|
|
933
|
-
payload.push(newEntity);
|
|
934
|
-
}
|
|
935
|
-
if (attr && attr.name) {
|
|
936
|
-
if (attr.name !== j) {
|
|
937
|
-
logger.debug(
|
|
938
|
-
context,
|
|
939
|
-
'sendUpdateValueNgsiLD \n MULTI remove measure attr %j keep alias j %j from %j \n',
|
|
940
|
-
j,
|
|
941
|
-
attr,
|
|
942
|
-
payload
|
|
943
|
-
);
|
|
944
|
-
delete payload[0][j];
|
|
945
|
-
}
|
|
691
|
+
//now we can compute explicit (Bool or Array) with the complete JexlContext
|
|
692
|
+
let explicit = false;
|
|
693
|
+
if (typeof typeInformation.explicitAttrs === 'string') {
|
|
694
|
+
try {
|
|
695
|
+
explicit = jexlParser.applyExpression(typeInformation.explicitAttrs, jexlctxt, typeInformation);
|
|
696
|
+
if (explicit instanceof Array && explicit.length > 0 && mustInsertTimeInstant) {
|
|
697
|
+
explicit.push(constants.TIMESTAMP_ATTRIBUTE);
|
|
946
698
|
}
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
699
|
+
logger.debug(
|
|
700
|
+
context,
|
|
701
|
+
'Calculated explicitAttrs with expression: %j and ctxt: %j resulting: %j',
|
|
702
|
+
typeInformation.explicitAttrs,
|
|
703
|
+
jexlctxt,
|
|
704
|
+
explicit
|
|
705
|
+
);
|
|
706
|
+
} catch (e) {
|
|
707
|
+
// nothing to do: exception is already logged at info level
|
|
708
|
+
}
|
|
709
|
+
} else if (typeof typeInformation.explicitAttrs === 'boolean') {
|
|
710
|
+
explicit = typeInformation.explicitAttrs;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
//more mesures may be added to the attribute list (unnhandled/left mesaures) l
|
|
714
|
+
if (explicit === false && Object.keys(measures).length > 0) {
|
|
715
|
+
entities[entityName][typeInformation.type] = entities[entityName][typeInformation.type].concat(measures);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
//PRE-PROCESSING FINISHED
|
|
719
|
+
//Explicit ATTRS and SKIPVALUES will be managed while we build NGSI payload
|
|
720
|
+
//Get ready to build and send NGSI payload (entities-->payload)
|
|
721
|
+
|
|
722
|
+
for (const ename in entities) {
|
|
723
|
+
for (const etype in entities[ename]) {
|
|
724
|
+
const e = {};
|
|
725
|
+
e.id = String(ename);
|
|
726
|
+
e.type = String(etype);
|
|
727
|
+
const timestamp = { type: constants.TIMESTAMP_TYPE_NGSI2 }; //timestamp scafold-attr for insertions.
|
|
728
|
+
let timestampAttrs = null;
|
|
729
|
+
if (mustInsertTimeInstant) {
|
|
730
|
+
// get timestamp for current entity
|
|
731
|
+
|
|
732
|
+
timestampAttrs = entities[ename][etype].filter(
|
|
733
|
+
(item) => item.name === constants.TIMESTAMP_ATTRIBUTE
|
|
734
|
+
);
|
|
735
|
+
if (timestampAttrs && timestampAttrs.length > 0) {
|
|
736
|
+
timestamp.value = timestampAttrs[0].value;
|
|
954
737
|
}
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
738
|
+
|
|
739
|
+
if (timestamp.value) {
|
|
740
|
+
if (!moment(timestamp.value, moment.ISO_8601, true).isValid()) {
|
|
741
|
+
callback(new errors.BadTimestamp(timestamp.value, entityName, typeInformation));
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
} else if (!typeInformation.timezone) {
|
|
745
|
+
timestamp.value = currentIsoDate;
|
|
746
|
+
jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value;
|
|
747
|
+
} else {
|
|
748
|
+
timestamp.value = currentMoment
|
|
749
|
+
.tz(typeInformation.timezone)
|
|
750
|
+
.format('YYYY-MM-DD[T]HH:mm:ss.SSSZ');
|
|
751
|
+
jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value;
|
|
962
752
|
}
|
|
963
753
|
}
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
)
|
|
754
|
+
//extract attributes
|
|
755
|
+
let isEmpty = true;
|
|
756
|
+
for (const attr of entities[ename][etype]) {
|
|
757
|
+
if (
|
|
758
|
+
attr.name !== 'id' &&
|
|
759
|
+
attr.name !== 'type' &&
|
|
760
|
+
(attr.value !== attr.skipValue || attr.skipValue === undefined) &&
|
|
761
|
+
(attr.hitted || attr.hitted === undefined) && //undefined is for pure measures
|
|
762
|
+
(typeof explicit === 'boolean' || //true and false already handled
|
|
763
|
+
(explicit instanceof Array && //check the array version
|
|
764
|
+
(explicit.includes(attr.name) ||
|
|
765
|
+
explicit.some(
|
|
766
|
+
(item) => attr.object_id !== undefined && item.object_id === attr.object_id
|
|
767
|
+
))))
|
|
768
|
+
) {
|
|
769
|
+
isEmpty = false;
|
|
770
|
+
if (mustInsertTimeInstant) {
|
|
771
|
+
// Add TimeInstant to all attribute metadata of all entities
|
|
772
|
+
if (attr.name !== constants.TIMESTAMP_ATTRIBUTE) {
|
|
773
|
+
if (!attr.metadata) {
|
|
774
|
+
attr.metadata = {};
|
|
775
|
+
}
|
|
776
|
+
attr.metadata[constants.TIMESTAMP_ATTRIBUTE] = timestamp;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
e[attr.name] = { type: attr.type, value: attr.value, metadata: attr.metadata };
|
|
972
780
|
}
|
|
973
781
|
}
|
|
974
|
-
if (
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE] &&
|
|
978
|
-
newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].type === constants.TIMESTAMP_TYPE_NGSI2 &&
|
|
979
|
-
newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].value
|
|
980
|
-
) {
|
|
981
|
-
const extendedTime = compressTimestampPlugin.fromBasicToExtended(
|
|
982
|
-
newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].value
|
|
983
|
-
);
|
|
984
|
-
if (extendedTime) {
|
|
985
|
-
newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].value = extendedTime;
|
|
782
|
+
if (!isEmpty) {
|
|
783
|
+
if (mustInsertTimeInstant) {
|
|
784
|
+
e[constants.TIMESTAMP_ATTRIBUTE] = timestamp;
|
|
986
785
|
}
|
|
786
|
+
payload.push(e);
|
|
987
787
|
}
|
|
988
788
|
}
|
|
989
|
-
} // if (j !== 'id' || j !== 'type')
|
|
990
|
-
|
|
991
|
-
// final attr loop
|
|
992
|
-
logger.debug(
|
|
993
|
-
context,
|
|
994
|
-
'sendUpdateValueNgsiLD \n after procesing attr %j \n current entity %j \n current payload=%j',
|
|
995
|
-
j,
|
|
996
|
-
currentEntity,
|
|
997
|
-
payload
|
|
998
|
-
);
|
|
999
|
-
}
|
|
1000
|
-
// for attr loop
|
|
1001
|
-
|
|
1002
|
-
// Add timestamp to paylaod
|
|
1003
|
-
if (
|
|
1004
|
-
'timestamp' in typeInformation && typeInformation.timestamp !== undefined
|
|
1005
|
-
? typeInformation.timestamp
|
|
1006
|
-
: config.getConfig().timestamp !== undefined
|
|
1007
|
-
? config.getConfig().timestamp
|
|
1008
|
-
: timestampValue !== undefined
|
|
1009
|
-
) {
|
|
1010
|
-
if (timestampValue) {
|
|
1011
|
-
// timeInstant is provided as measure
|
|
1012
|
-
if (Object.keys(payload[0]).length > 1) {
|
|
1013
|
-
// include metadata with TimeInstant in attrs when TimeInstant is provided as measure in all entities
|
|
1014
|
-
payload[0] = addTimestamp(payload[0], typeInformation.timezone, timestampValue);
|
|
1015
|
-
}
|
|
1016
|
-
} else {
|
|
1017
|
-
// jshint maxdepth:5
|
|
1018
|
-
for (let n = 0; n < payload.length; n++) {
|
|
1019
|
-
if (!utils.isTimestampedNgsi2(payload[n])) {
|
|
1020
|
-
// legacy check needed?
|
|
1021
|
-
payload[n] = addTimestamp(payload[n], typeInformation.timezone);
|
|
1022
|
-
// jshint maxdepth:5
|
|
1023
|
-
} else if (!utils.IsValidTimestampedNgsi2(payload[n])) {
|
|
1024
|
-
// legacy check needed?
|
|
1025
|
-
logger.error(context, 'Invalid timestamp:%s', JSON.stringify(payload[0]));
|
|
1026
|
-
callback(new errors.BadTimestamp(payload, entityName, typeInformation));
|
|
1027
|
-
return;
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
logger.debug(context, 'sendUpdateValueNgsiLD \n ending payload=%j', payload);
|
|
1034
|
-
|
|
1035
|
-
for (let m = 0; m < payload.length; m++) {
|
|
1036
|
-
for (const key in payload[m]) {
|
|
1037
|
-
// purge object_id from payload
|
|
1038
|
-
if (payload[m][key] && payload[m][key].object_id) {
|
|
1039
|
-
delete payload[m][key].object_id;
|
|
1040
|
-
}
|
|
1041
789
|
}
|
|
1042
|
-
}
|
|
1043
|
-
logger.debug(context, 'sendUpdateValueNgsiLD \n payload and without object_id %j', payload);
|
|
790
|
+
} // end for (let measures of originMeasures)
|
|
1044
791
|
|
|
792
|
+
const url = '/ngsi-ld/v1/entityOperations/upsert/?options=update';
|
|
793
|
+
const options = NGSIUtils.createRequestObject(url, originTypeInformation, token);
|
|
1045
794
|
options.json = payload;
|
|
795
|
+
|
|
1046
796
|
try {
|
|
1047
797
|
if (payload instanceof Array) {
|
|
1048
798
|
options.json = _.map(options.json, formatAsNGSILD);
|
|
1049
799
|
} else {
|
|
1050
800
|
options.json.id = entityName;
|
|
1051
|
-
options.json.type =
|
|
801
|
+
options.json.type = originTypeInformation.type;
|
|
1052
802
|
options.json = [formatAsNGSILD(options.json)];
|
|
1053
803
|
}
|
|
1054
804
|
} catch (error) {
|
|
1055
|
-
return callback(new errors.BadGeocoordinates(JSON.stringify(payload),
|
|
805
|
+
return callback(new errors.BadGeocoordinates(JSON.stringify(payload), originTypeInformation));
|
|
1056
806
|
}
|
|
1057
807
|
|
|
1058
|
-
if (
|
|
1059
|
-
addLinkedEntities(
|
|
808
|
+
if (originTypeInformation.active) {
|
|
809
|
+
addLinkedEntities(originTypeInformation, options.json);
|
|
1060
810
|
}
|
|
1061
811
|
|
|
1062
812
|
// Prevent to update an entity with an empty payload
|
|
@@ -1068,7 +818,7 @@ function sendUpdateValueNgsiLD(entityName, attributes, typeInformation, token, c
|
|
|
1068
818
|
logger.debug(context, 'Using the following NGSI LD request:\n\n%s\n\n', JSON.stringify(options, null, 4));
|
|
1069
819
|
request(
|
|
1070
820
|
options,
|
|
1071
|
-
generateNGSILDOperationHandler('update', entityName,
|
|
821
|
+
generateNGSILDOperationHandler('update', entityName, originTypeInformation, token, options, callback)
|
|
1072
822
|
);
|
|
1073
823
|
} else {
|
|
1074
824
|
logger.debug(
|