iotagent-node-lib 2.25.0 → 2.26.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/CHANGES_NEXT_RELEASE +0 -1
- package/config.js +6 -1
- package/doc/deprecated.md +4 -1
- package/doc/howto.md +8 -0
- package/doc/installationguide.md +18 -0
- package/doc/usermanual.md +77 -0
- package/lib/commonConfig.js +15 -1
- package/lib/constants.js +1 -0
- package/lib/fiware-iotagent-lib.js +1 -0
- package/lib/services/devices/deviceService.js +1 -0
- package/lib/services/devices/registrationUtils.js +21 -1
- package/lib/services/northBound/contextServer-NGSI-LD.js +221 -38
- package/lib/services/northBound/contextServer.js +14 -1
- package/lib/services/northBound/northboundServer.js +1 -0
- package/package.json +1 -1
- package/test/unit/memoryRegistry/deviceRegistryMemory_test.js +1 -1
- package/test/unit/ngsi-ld/examples/contextAvailabilityRequests/registerIoTAgent1.json +24 -14
- package/test/unit/ngsi-ld/examples/contextAvailabilityRequests/registerIoTAgent2.json +25 -15
- package/test/unit/ngsi-ld/examples/contextAvailabilityRequests/registerIoTAgent4.json +15 -5
- package/test/unit/ngsi-ld/examples/contextAvailabilityRequests/registerIoTAgentCommands.json +9 -1
- package/test/unit/ngsi-ld/examples/contextAvailabilityRequests/registerIoTAgentCommandsAndLazy.json +32 -0
- package/test/unit/ngsi-ld/examples/contextAvailabilityRequests/registerProvisionedDevice.json +12 -1
- package/test/unit/ngsi-ld/examples/contextAvailabilityRequests/registerProvisionedDevice2.json +9 -1
- package/test/unit/ngsi-ld/examples/contextAvailabilityRequests/registerProvisionedDeviceWithGroup.json +12 -1
- package/test/unit/ngsi-ld/examples/contextAvailabilityRequests/registerProvisionedDeviceWithGroup2.json +12 -1
- package/test/unit/ngsi-ld/examples/contextAvailabilityRequests/registerProvisionedDeviceWithGroup3.json +9 -1
- package/test/unit/ngsi-ld/examples/contextAvailabilityRequests/updateCommands1.json +10 -2
- package/test/unit/ngsi-ld/examples/contextAvailabilityRequests/updateIoTAgent1.json +11 -1
- package/test/unit/ngsi-ld/examples/contextAvailabilityRequests/updateIoTAgent2.json +12 -1
- package/test/unit/ngsi-ld/general/startup-test.js +17 -2
- package/test/unit/ngsi-ld/lazyAndCommands/command-test.js +1 -12
- package/test/unit/ngsi-ld/lazyAndCommands/merge-patch-test.js +249 -0
- package/test/unit/ngsi-ld/ngsiService/unsupported-endpoints-test.js +171 -0
package/CHANGES_NEXT_RELEASE
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
package/config.js
CHANGED
package/doc/deprecated.md
CHANGED
|
@@ -18,7 +18,9 @@ A list of deprecated features and the version in which they were deprecated foll
|
|
|
18
18
|
- Support to Node.js v8 in iotagent-node-lib 2.12.0 (finally removed in 2.13.0)
|
|
19
19
|
- Support to Node.js v10 in iotagent-node-lib 2.15.0 (finally removed in 2.16.0)
|
|
20
20
|
- Support to Node.js v12 in iotagent-node-lib 2.24.0 (finally removed in 2.25.0)
|
|
21
|
-
- Support to NGSI-LD v1.3 in iotagent-node-lib 2.25.0
|
|
21
|
+
- Support to NGSI-LD v1.3 in iotagent-node-lib 2.25.0 (finally removed in 2.26.0)
|
|
22
|
+
- Support groups (provision) statically defined by configuration
|
|
23
|
+
- Support to in-memory registry (i.e.`deviceRegistry.type=memory`)
|
|
22
24
|
|
|
23
25
|
The use of Node.js v14 is highly recommended.
|
|
24
26
|
|
|
@@ -46,3 +48,4 @@ The following table provides information about the last iotagent-node-lib versio
|
|
|
46
48
|
| Support to Node.js v8 | 2.12.0 | April 7th, 2020 |
|
|
47
49
|
| Support to Node.js v10 | 2.15.0 | February 18th, 2021 |
|
|
48
50
|
| Support to Node.js v12 | 2.24.0 | September 2nd, 2022 |
|
|
51
|
+
| Support to NGSI-LD 1.3 | 2.25.0 | January 24th, 2023 |
|
package/doc/howto.md
CHANGED
|
@@ -430,6 +430,14 @@ iotAgentLib.setDataUpdateHandler(updateContextHandler);
|
|
|
430
430
|
iotAgentLib.setDataQueryHandler(queryContextHandler);
|
|
431
431
|
```
|
|
432
432
|
|
|
433
|
+
Where necessary, additional handlers to deal with command actuations and merge-patch operations may also be added when
|
|
434
|
+
necessary.
|
|
435
|
+
|
|
436
|
+
```javascript
|
|
437
|
+
iotAgentLib.setCommandHandler(commandHandler);
|
|
438
|
+
iotAgentLib.setMergePatchHandler(mergePatchHandler);
|
|
439
|
+
```
|
|
440
|
+
|
|
433
441
|
#### IOTA Testing
|
|
434
442
|
|
|
435
443
|
In order to test it, we need to create an HTTP server simulating the device. The quickest way to do that may be using
|
package/doc/installationguide.md
CHANGED
|
@@ -72,6 +72,22 @@ overriding the group setting.
|
|
|
72
72
|
}
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
+
When connected to an **NGSI-LD** context broker, an IoT Agent is able to indicate whether it is willing to accept `null`
|
|
76
|
+
values and also whether it is able to process the **NGSI-LD** `datasetId` metadata element. Setting these
|
|
77
|
+
values to `false` will cause the IoT Agent to return a 400 **Bad Request** HTTP status code explaining that the IoT
|
|
78
|
+
Agent does not support nulls or multi-attribute requests if they are encountered.
|
|
79
|
+
|
|
80
|
+
```javascript
|
|
81
|
+
{
|
|
82
|
+
baseRoot: '/',
|
|
83
|
+
port: 4041,
|
|
84
|
+
ldSupport : {
|
|
85
|
+
null: true,
|
|
86
|
+
datasetId: true
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
75
91
|
- **stats**: configure the periodic collection of statistics. Use `interval` in milliseconds to set the time between
|
|
76
92
|
stats writings.
|
|
77
93
|
|
|
@@ -301,6 +317,8 @@ overrides.
|
|
|
301
317
|
| IOTA_CB_NGSI_VERSION | `contextBroker.ngsiVersion` |
|
|
302
318
|
| IOTA_NORTH_HOST | `server.host` |
|
|
303
319
|
| IOTA_NORTH_PORT | `server.port` |
|
|
320
|
+
| IOTA_LD_SUPPORT_NULL | `server.ldSupport.null` |
|
|
321
|
+
| IOTA_LD_SUPPORT_DATASET_ID | `server.ldSupport.datasetId` |
|
|
304
322
|
| IOTA_PROVIDER_URL | `providerUrl` |
|
|
305
323
|
| IOTA_AUTH_ENABLED | `authentication.enabled` |
|
|
306
324
|
| IOTA_AUTH_TYPE | `authentication.type` |
|
package/doc/usermanual.md
CHANGED
|
@@ -354,6 +354,83 @@ values.
|
|
|
354
354
|
The handler is expected to call its callback once with no parameters (failing to do so may cause unexpected behaviors in
|
|
355
355
|
the IoT Agent).
|
|
356
356
|
|
|
357
|
+
##### iotagentLib.setCommandHandler()
|
|
358
|
+
|
|
359
|
+
###### Signature
|
|
360
|
+
|
|
361
|
+
```javascript
|
|
362
|
+
function setCommandHandler(newHandler)
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
###### Description
|
|
366
|
+
|
|
367
|
+
Sets the new user handler for registered entity commands. This handler will be called whenever a command request arrives, with
|
|
368
|
+
the following parameters: (`id`, `type`, `service`, `subservice`, `attributes`, `callback`). The handler must retrieve
|
|
369
|
+
all the corresponding information from the devices and return a NGSI entity with the requested values.
|
|
370
|
+
|
|
371
|
+
The callback must be invoked with the updated Context Element, using the information retrieved from the devices. E.g.:
|
|
372
|
+
|
|
373
|
+
```javascript
|
|
374
|
+
callback(null, {
|
|
375
|
+
type: "TheType",
|
|
376
|
+
isPattern: false,
|
|
377
|
+
id: "EntityID",
|
|
378
|
+
attributes: [
|
|
379
|
+
{
|
|
380
|
+
name: "lumniscence",
|
|
381
|
+
type: "Lumens",
|
|
382
|
+
value: "432"
|
|
383
|
+
}
|
|
384
|
+
]
|
|
385
|
+
});
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
In the case of NGSI requests affecting multiple entities, this handler will be called multiple times, one for each
|
|
389
|
+
entity, and all the results will be combined into a single response. Only IoT Agents which deal with actuator devices will include a handler for commands.
|
|
390
|
+
|
|
391
|
+
###### Params
|
|
392
|
+
|
|
393
|
+
- newHandler: User handler for command requests.
|
|
394
|
+
|
|
395
|
+
##### iotagentLib.setMergePatchHandler()
|
|
396
|
+
|
|
397
|
+
###### Signature
|
|
398
|
+
|
|
399
|
+
```javascript
|
|
400
|
+
function setMergePatchHandler(newHandler)
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
###### Description
|
|
404
|
+
|
|
405
|
+
Sets the new user handler for NGSI-LD Entity [merge-patch](https://datatracker.ietf.org/doc/html/rfc7386) requests. This handler will be called whenever a merge-patch request arrives, with
|
|
406
|
+
the following parameters: (`id`, `type`, `service`, `subservice`, `attributes`, `callback`). The handler must retrieve
|
|
407
|
+
all the corresponding information from the devices and return a NGSI entity with the requested values.
|
|
408
|
+
|
|
409
|
+
The callback must be invoked with the updated Context Element, using the information retrieved from the devices. E.g.:
|
|
410
|
+
|
|
411
|
+
```javascript
|
|
412
|
+
callback(null, {
|
|
413
|
+
type: "TheType",
|
|
414
|
+
isPattern: false,
|
|
415
|
+
id: "EntityID",
|
|
416
|
+
attributes: [
|
|
417
|
+
{
|
|
418
|
+
name: "lumniscence",
|
|
419
|
+
type: "Lumens",
|
|
420
|
+
value: "432"
|
|
421
|
+
}
|
|
422
|
+
]
|
|
423
|
+
});
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
In the case of NGSI-LD requests affecting multiple entities, this handler will be
|
|
427
|
+
called multiple times. Since merge-patch is an advanced function, not all IoT Agents
|
|
428
|
+
will include a handler for merge-patch.
|
|
429
|
+
|
|
430
|
+
###### Params
|
|
431
|
+
|
|
432
|
+
- newHandler: User handler for merge-patch requests.
|
|
433
|
+
|
|
357
434
|
##### iotagentLib.setProvisioningHandler()
|
|
358
435
|
|
|
359
436
|
###### Signature
|
package/lib/commonConfig.js
CHANGED
|
@@ -158,7 +158,9 @@ function processEnvironmentVariables() {
|
|
|
158
158
|
'IOTA_DEFAULT_ENTITY_NAME_CONJUNCTION',
|
|
159
159
|
'IOTA_JSON_LD_CONTEXT',
|
|
160
160
|
'IOTA_FALLBACK_TENANT',
|
|
161
|
-
'IOTA_FALLBACK_PATH'
|
|
161
|
+
'IOTA_FALLBACK_PATH',
|
|
162
|
+
'IOTA_LD_SUPPORT_NULL',
|
|
163
|
+
'IOTA_LD_SUPPORT_DATASET_ID'
|
|
162
164
|
];
|
|
163
165
|
const iotamVariables = [
|
|
164
166
|
'IOTA_IOTAM_URL',
|
|
@@ -263,6 +265,18 @@ function processEnvironmentVariables() {
|
|
|
263
265
|
config.server.port = process.env.IOTA_NORTH_PORT;
|
|
264
266
|
}
|
|
265
267
|
|
|
268
|
+
config.server.ldSupport = config.server.ldSupport || {null: true, datasetId: true, merge: false};
|
|
269
|
+
|
|
270
|
+
if (process.env.IOTA_LD_SUPPORT_NULL) {
|
|
271
|
+
config.server.ldSupport.null = process.env.IOTA_LD_SUPPORT_NULL === 'true';
|
|
272
|
+
}
|
|
273
|
+
if (process.env.IOTA_LD_SUPPORT_DATASET_ID) {
|
|
274
|
+
config.server.ldSupport.datasetId = process.env.IOTA_LD_SUPPORT_DATASET_ID === 'true';
|
|
275
|
+
}
|
|
276
|
+
if (process.env.IOTA_LD_SUPPORT_MERGE) {
|
|
277
|
+
config.server.ldSupport.datasetId = process.env.IOTA_LD_SUPPORT_MERGE === 'true';
|
|
278
|
+
}
|
|
279
|
+
|
|
266
280
|
if (process.env.IOTA_PROVIDER_URL) {
|
|
267
281
|
config.providerUrl = process.env.IOTA_PROVIDER_URL;
|
|
268
282
|
}
|
package/lib/constants.js
CHANGED
|
@@ -60,6 +60,7 @@ module.exports = {
|
|
|
60
60
|
SUBSERVICE_HEADER: 'fiware-servicepath',
|
|
61
61
|
NGSI_LD_TENANT_HEADER: 'NGSILD-Tenant',
|
|
62
62
|
NGSI_LD_PATH_HEADER: 'NGSILD-Path',
|
|
63
|
+
NGSI_LD_NULL: 'urn:ngsi-ld:null',
|
|
63
64
|
//FIXME: check Keystone support this in lowercase, then change
|
|
64
65
|
AUTH_HEADER: 'X-Auth-Token',
|
|
65
66
|
X_FORWARDED_FOR_HEADER: 'x-forwarded-for',
|
|
@@ -323,6 +323,7 @@ exports.getConfigurationSilently = groupConfig.getSilently;
|
|
|
323
323
|
exports.findConfiguration = groupConfig.find;
|
|
324
324
|
exports.setDataUpdateHandler = contextServer.setUpdateHandler;
|
|
325
325
|
exports.setCommandHandler = contextServer.setCommandHandler;
|
|
326
|
+
exports.setMergePatchHandler = contextServer.setMergePatchHandler;
|
|
326
327
|
exports.setDataQueryHandler = contextServer.setQueryHandler;
|
|
327
328
|
exports.setConfigurationHandler = contextServer.setConfigurationHandler;
|
|
328
329
|
exports.setRemoveConfigurationHandler = contextServer.setRemoveConfigurationHandler;
|
|
@@ -363,6 +363,7 @@ function registerDevice(deviceObj, callback) {
|
|
|
363
363
|
deviceObj.type = deviceData.type;
|
|
364
364
|
deviceObj.staticAttributes = deviceData.staticAttributes;
|
|
365
365
|
deviceObj.commands = deviceData.commands;
|
|
366
|
+
deviceObj.lazy = deviceData.lazy;
|
|
366
367
|
if ('timestamp' in deviceData && deviceData.timestamp !== undefined) {
|
|
367
368
|
deviceObj.timestamp = deviceData.timestamp;
|
|
368
369
|
}
|
|
@@ -310,9 +310,11 @@ function sendRegistrationsNgsiLD(unregister, deviceData, callback) {
|
|
|
310
310
|
return sendUnregistrationsNgsiLD(deviceData, callback);
|
|
311
311
|
}
|
|
312
312
|
|
|
313
|
+
const operations = [];
|
|
313
314
|
const properties = [];
|
|
314
315
|
const lazy = deviceData.lazy || [];
|
|
315
316
|
const commands = deviceData.commands || [];
|
|
317
|
+
const supportMerge = config.getConfig().server.ldSupport.merge;
|
|
316
318
|
|
|
317
319
|
lazy.forEach((element) => {
|
|
318
320
|
properties.push(element.name);
|
|
@@ -321,6 +323,16 @@ function sendRegistrationsNgsiLD(unregister, deviceData, callback) {
|
|
|
321
323
|
properties.push(element.name);
|
|
322
324
|
});
|
|
323
325
|
|
|
326
|
+
if (lazy.length > 0){
|
|
327
|
+
operations.push('retrieveOps');
|
|
328
|
+
}
|
|
329
|
+
if (commands.length > 0){
|
|
330
|
+
operations.push('updateOps');
|
|
331
|
+
}
|
|
332
|
+
if (supportMerge){
|
|
333
|
+
operations.push('mergeEntity');
|
|
334
|
+
}
|
|
335
|
+
|
|
324
336
|
if (properties.length === 0) {
|
|
325
337
|
logger.debug(context, 'Registration with Context Provider is not needed. Device without lazy atts or commands');
|
|
326
338
|
return callback(null, deviceData);
|
|
@@ -348,10 +360,18 @@ function sendRegistrationsNgsiLD(unregister, deviceData, callback) {
|
|
|
348
360
|
information: [
|
|
349
361
|
{
|
|
350
362
|
entities: [{ type: deviceData.type, id }],
|
|
351
|
-
properties
|
|
363
|
+
propertyNames: properties
|
|
352
364
|
}
|
|
353
365
|
],
|
|
366
|
+
mode: 'exclusive',
|
|
367
|
+
operations,
|
|
354
368
|
endpoint: config.getConfig().providerUrl,
|
|
369
|
+
contextSourceInfo: [
|
|
370
|
+
{
|
|
371
|
+
'key': 'jsonldContext',
|
|
372
|
+
'value': config.getConfig().contextBroker.jsonLdContext
|
|
373
|
+
}
|
|
374
|
+
],
|
|
355
375
|
'@context': config.getConfig().contextBroker.jsonLdContext
|
|
356
376
|
},
|
|
357
377
|
headers: {
|
|
@@ -31,6 +31,7 @@ const async = require('async');
|
|
|
31
31
|
const apply = async.apply;
|
|
32
32
|
const logger = require('logops');
|
|
33
33
|
const errors = require('../../errors');
|
|
34
|
+
const constants = require('../../constants');
|
|
34
35
|
const deviceService = require('../devices/deviceService');
|
|
35
36
|
const middlewares = require('../common/genericMiddleware');
|
|
36
37
|
const _ = require('underscore');
|
|
@@ -41,11 +42,98 @@ const updateContextTemplateNgsiLD = require('../../templates/updateContextNgsiLD
|
|
|
41
42
|
const notificationTemplateNgsiLD = require('../../templates/notificationTemplateNgsiLD.json');
|
|
42
43
|
const contextServerUtils = require('./contextServerUtils');
|
|
43
44
|
const ngsiLD = require('../ngsi/entities-NGSI-LD');
|
|
45
|
+
const config = require('../../commonConfig');
|
|
44
46
|
|
|
45
47
|
const overwritePaths = ['/ngsi-ld/v1/entities/:entity/attrs', '/ngsi-ld/v1/entities/:entity/attrs/:attr'];
|
|
46
48
|
const updatePaths = ['/ngsi-ld/v1/entities/:entity/attrs', '/ngsi-ld/v1/entities/:entity/attrs/:attr'];
|
|
47
49
|
const queryPaths = ['/ngsi-ld/v1/entities/:entity'];
|
|
48
50
|
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Replacement of NGSI-LD Null placeholders with real null values
|
|
54
|
+
*
|
|
55
|
+
*/
|
|
56
|
+
function replaceNGSILDNull(payload){
|
|
57
|
+
Object.keys(payload).forEach((key) =>{
|
|
58
|
+
const value = payload[key];
|
|
59
|
+
if ( value === constants.NGSI_LD_NULL){
|
|
60
|
+
payload[key] = null;
|
|
61
|
+
} else if (typeof value === 'object' &&
|
|
62
|
+
!Array.isArray(value) &&
|
|
63
|
+
value !== null){
|
|
64
|
+
payload[key] = replaceNGSILDNull(payload[key]);
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
return payload;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Check to see if the payload or its subattributes contain null values
|
|
72
|
+
*
|
|
73
|
+
*/
|
|
74
|
+
function containsNulls(payload, result){
|
|
75
|
+
Object.keys(payload).forEach((key) =>{
|
|
76
|
+
const value = payload[key];
|
|
77
|
+
if ( value === null){
|
|
78
|
+
result.nulls = true;
|
|
79
|
+
} else if (typeof value === 'object' &&
|
|
80
|
+
!Array.isArray(value) &&
|
|
81
|
+
value !== null){
|
|
82
|
+
containsNulls(payload[key], result);
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
return result;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* An Express middleware for preprocessing NGSI-LD payloads. Converts NGSI-LD Nulls
|
|
90
|
+
* to real nulls and checks for the presence of null and datasetId
|
|
91
|
+
*
|
|
92
|
+
*/
|
|
93
|
+
function preprocessNGSILD(req, res, next){
|
|
94
|
+
res.locals.hasDatasetId = false;
|
|
95
|
+
const payload = req.body
|
|
96
|
+
if (payload && typeof payload === 'object'){
|
|
97
|
+
Object.keys(payload).forEach((key) =>{
|
|
98
|
+
if (_.isArray(payload[key])){
|
|
99
|
+
payload[key].forEach((obj) => {
|
|
100
|
+
if (obj.datasetId){
|
|
101
|
+
res.locals.hasDatasetId = true;
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
} else if (payload[key] && payload[key].datasetId && payload[key].datasetId !== '@none'){
|
|
105
|
+
res.locals.hasDatasetId = true;
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
req.body = replaceNGSILDNull(payload);
|
|
109
|
+
const result = { nulls: false }
|
|
110
|
+
containsNulls(payload, result);
|
|
111
|
+
res.locals.hasNulls = result.nulls;
|
|
112
|
+
}
|
|
113
|
+
next();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* A configurable Middleware that makes additional NGSI-LD checks within the payload.
|
|
118
|
+
|
|
119
|
+
*
|
|
120
|
+
* @param {Boolean} supportNull Whether to support NGSI-LD nulls in the payload
|
|
121
|
+
* @param {Boolean} supportDatasetId Whether to support multiattributes in the payload.
|
|
122
|
+
* @return {Object} Express middleware used in request validation.
|
|
123
|
+
*/
|
|
124
|
+
function validateNGSILD(supportNull, supportDatasetId) {
|
|
125
|
+
return function validate(req, res, next) {
|
|
126
|
+
if (!supportNull && res.locals.hasNulls) {
|
|
127
|
+
next(new errors.BadRequest('NGSI-LD Null found within the payload. This IoT Agent does not support nulls for this endpoint.'));
|
|
128
|
+
} else if (!supportDatasetId && res.locals.hasDatasetId) {
|
|
129
|
+
next(new errors.BadRequest('datasetId found within the payload. This IoT Agent does not support multi-attribute requests.'));
|
|
130
|
+
} else {
|
|
131
|
+
next();
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
|
|
49
137
|
/**
|
|
50
138
|
* Extract metadata attributes from input.
|
|
51
139
|
*
|
|
@@ -318,6 +406,104 @@ function defaultQueryHandlerNgsiLD(id, type, service, subservice, attributes, ca
|
|
|
318
406
|
});
|
|
319
407
|
}
|
|
320
408
|
|
|
409
|
+
/**
|
|
410
|
+
* Generate a merge-patch action corresponding to the request using NGSI-LD.
|
|
411
|
+
* Merge-patch is an NGSI-LD specific action.
|
|
412
|
+
*
|
|
413
|
+
* @param {Object} req Update request to generate Actions from
|
|
414
|
+
*/
|
|
415
|
+
function generateMergePatchActionNgsiLD(req, callback) {
|
|
416
|
+
|
|
417
|
+
const entityId = req.params.entity;
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
function addAttributes(deviceData, body, attributes){
|
|
421
|
+
const keys = Object.keys(body);
|
|
422
|
+
|
|
423
|
+
for (const j in deviceData) {
|
|
424
|
+
if (keys.includes(deviceData[j].name)) {
|
|
425
|
+
const obj = body[deviceData[j].name]
|
|
426
|
+
if ( obj === null) {
|
|
427
|
+
attributes.push({
|
|
428
|
+
type: deviceData[j].type,
|
|
429
|
+
value: null,
|
|
430
|
+
name: deviceData[j].name
|
|
431
|
+
});
|
|
432
|
+
} else {
|
|
433
|
+
attributes.push({
|
|
434
|
+
type: deviceData[j].type,
|
|
435
|
+
value: obj.value,
|
|
436
|
+
name: deviceData[j].name
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return attributes;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
deviceService.getDeviceByName(
|
|
446
|
+
entityId,
|
|
447
|
+
contextServerUtils.getLDTenant(req),
|
|
448
|
+
contextServerUtils.getLDPath(req),
|
|
449
|
+
function (error, deviceObj) {
|
|
450
|
+
if (error) {
|
|
451
|
+
callback(error);
|
|
452
|
+
} else {
|
|
453
|
+
const attributes = [];
|
|
454
|
+
addAttributes(deviceObj.commands, req.body, attributes)
|
|
455
|
+
addAttributes(deviceObj.lazy, req.body, attributes)
|
|
456
|
+
const executeMergePatchHandler = apply(
|
|
457
|
+
contextServerUtils.mergePatchHandler,
|
|
458
|
+
entityId,
|
|
459
|
+
deviceObj.type,
|
|
460
|
+
contextServerUtils.getLDTenant(req),
|
|
461
|
+
contextServerUtils.getLDPath(req),
|
|
462
|
+
attributes
|
|
463
|
+
);
|
|
464
|
+
async.waterfall(
|
|
465
|
+
[executeMergePatchHandler],
|
|
466
|
+
callback()
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Express middleware to manage incoming merge-patch requests using NGSI-LD.
|
|
475
|
+
*
|
|
476
|
+
* @param {Object} req Request that was handled in first place.
|
|
477
|
+
* @param {Object} res Response that will be sent.
|
|
478
|
+
*/
|
|
479
|
+
function handleMergePatchNgsiLD(req, res, next) {
|
|
480
|
+
|
|
481
|
+
function handleMergePatchRequest(error, result) {
|
|
482
|
+
if (error) {
|
|
483
|
+
logger.debug(context, 'There was an error handling the merge-patch: %s.', error);
|
|
484
|
+
next(error);
|
|
485
|
+
} else {
|
|
486
|
+
logger.debug(context, 'Merge-patch from [%s] handled successfully.', req.get('host'));
|
|
487
|
+
res.status(200).json(result);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
logger.debug(context, 'Handling merge-patch from [%s]', req.get('host'));
|
|
492
|
+
if ((req.is('json') || req.is('application/ld+json')) === false) {
|
|
493
|
+
return handleMergePatchRequest(new errors.UnsupportedContentType(req.header('content-type')));
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (req.body) {
|
|
497
|
+
logger.debug(context, JSON.stringify(req.body, null, 4));
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
if (contextServerUtils.mergePatchHandler){
|
|
501
|
+
generateMergePatchActionNgsiLD(req, handleMergePatchRequest);
|
|
502
|
+
} else {
|
|
503
|
+
return handleMergePatchRequest(new errors.MethodNotSupported(req.method, req.path))
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
321
507
|
/**
|
|
322
508
|
* Express middleware to manage incoming query context requests using NGSI-LD.
|
|
323
509
|
*
|
|
@@ -489,19 +675,21 @@ function handleQueryNgsiLD(req, res, next) {
|
|
|
489
675
|
* @param {Object} req Request that was handled in first place.
|
|
490
676
|
* @param {Object} res Response that will be sent.
|
|
491
677
|
*/
|
|
492
|
-
function
|
|
493
|
-
|
|
678
|
+
function ErrorHandlingNgsiLD(action) {
|
|
679
|
+
return function errorHandle(error, req, res, next) {
|
|
680
|
+
let code = 500;
|
|
494
681
|
|
|
495
|
-
|
|
682
|
+
logger.debug(context, action + ' NGSI-LD error [%s] handling request: %s', error.name, error.message);
|
|
496
683
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
684
|
+
if (error.code && String(error.code).match(/^[2345]\d\d$/)) {
|
|
685
|
+
code = error.code;
|
|
686
|
+
}
|
|
500
687
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
688
|
+
res.status(code).json({
|
|
689
|
+
error: error.name,
|
|
690
|
+
description: error.message.replace(/[<>\"\'=;\(\)]/g, '')
|
|
691
|
+
});
|
|
692
|
+
}
|
|
505
693
|
}
|
|
506
694
|
|
|
507
695
|
/**
|
|
@@ -601,28 +789,6 @@ function handleNotificationNgsiLD(req, res, next) {
|
|
|
601
789
|
}
|
|
602
790
|
}
|
|
603
791
|
|
|
604
|
-
/**
|
|
605
|
-
* Error handler for NGSI-LD update requests.
|
|
606
|
-
*
|
|
607
|
-
* @param {Object} error Incoming error
|
|
608
|
-
* @param {Object} req Request that was handled in first place.
|
|
609
|
-
* @param {Object} res Response that will be sent.
|
|
610
|
-
*/
|
|
611
|
-
function updateErrorHandlingNgsiLD(error, req, res, next) {
|
|
612
|
-
let code = 500;
|
|
613
|
-
|
|
614
|
-
logger.debug(context, 'Update NGSI-LD error [%s] handing request: %s', error.name, error.message);
|
|
615
|
-
|
|
616
|
-
if (error.code && String(error.code).match(/^[2345]\d\d$/)) {
|
|
617
|
-
code = error.code;
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
res.status(code).json({
|
|
621
|
-
error: error.name,
|
|
622
|
-
description: error.message.replace(/[<>\"\'=;\(\)]/g, '')
|
|
623
|
-
});
|
|
624
|
-
}
|
|
625
|
-
|
|
626
792
|
/**
|
|
627
793
|
* Load unsupported NGSI-LD entity routes and return proper NGSI-LD not supported responses
|
|
628
794
|
*
|
|
@@ -645,36 +811,53 @@ function loadUnsupportedEndpointsNGSILD(router) {
|
|
|
645
811
|
*/
|
|
646
812
|
function loadContextRoutesNGSILD(router) {
|
|
647
813
|
// In a more evolved implementation, more endpoints could be added to queryPathsNgsi2
|
|
648
|
-
// according to
|
|
649
|
-
|
|
814
|
+
// according to https://www.etsi.org/standards-search#page=1&search=GS%20CIM%20009
|
|
815
|
+
|
|
816
|
+
const support = config.getConfig().server.ldSupport;
|
|
650
817
|
let i;
|
|
651
818
|
|
|
652
819
|
logger.info(context, 'Loading NGSI-LD Context server routes');
|
|
820
|
+
// Update Patch endpoints - this endpoint may accept NGSI-LD Null
|
|
653
821
|
for (i = 0; i < updatePaths.length; i++) {
|
|
654
822
|
router.patch(updatePaths[i], [
|
|
655
823
|
middlewares.ensureType,
|
|
656
824
|
middlewares.validateJson(updateContextTemplateNgsiLD),
|
|
825
|
+
preprocessNGSILD,
|
|
826
|
+
validateNGSILD(support.null, support.datasetId),
|
|
657
827
|
handleUpdateNgsiLD,
|
|
658
|
-
|
|
828
|
+
ErrorHandlingNgsiLD('Partial Update')
|
|
659
829
|
]);
|
|
660
830
|
}
|
|
831
|
+
// Merge Patch endpoints - this endpoint may accept NGSI-LD Null
|
|
832
|
+
router.patch('/ngsi-ld/v1/entities/:entity', [
|
|
833
|
+
preprocessNGSILD,
|
|
834
|
+
validateNGSILD(support.null, support.datasetId),
|
|
835
|
+
handleMergePatchNgsiLD,
|
|
836
|
+
ErrorHandlingNgsiLD('Merge-Patch')
|
|
837
|
+
]);
|
|
661
838
|
|
|
839
|
+
// Overwrite/PUT endpoints - this endpoint does not accept NGSI-LD Null
|
|
662
840
|
for (i = 0; i < overwritePaths.length; i++) {
|
|
663
841
|
router.put(overwritePaths[i], [
|
|
664
842
|
middlewares.ensureType,
|
|
665
843
|
middlewares.validateJson(updateContextTemplateNgsiLD),
|
|
844
|
+
preprocessNGSILD,
|
|
845
|
+
validateNGSILD(false, support.datasetId),
|
|
666
846
|
handleUpdateNgsiLD,
|
|
667
|
-
|
|
847
|
+
ErrorHandlingNgsiLD('Overwrite')
|
|
668
848
|
]);
|
|
669
849
|
}
|
|
850
|
+
// Query/GET endpoints - no payload to check.
|
|
670
851
|
for (i = 0; i < queryPaths.length; i++) {
|
|
671
|
-
router.get(queryPaths[i], [handleQueryNgsiLD,
|
|
852
|
+
router.get(queryPaths[i], [handleQueryNgsiLD, ErrorHandlingNgsiLD('Query')]);
|
|
672
853
|
}
|
|
673
854
|
router.post('/notify', [
|
|
674
855
|
middlewares.ensureType,
|
|
675
856
|
middlewares.validateJson(notificationTemplateNgsiLD),
|
|
857
|
+
preprocessNGSILD,
|
|
858
|
+
validateNGSILD(false, support.datasetId),
|
|
676
859
|
handleNotificationNgsiLD,
|
|
677
|
-
|
|
860
|
+
ErrorHandlingNgsiLD('Notify')
|
|
678
861
|
]);
|
|
679
862
|
loadUnsupportedEndpointsNGSILD(router);
|
|
680
863
|
}
|
|
@@ -64,7 +64,7 @@ function setUpdateHandler(newHandler) {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
|
-
* Sets the new user handler for
|
|
67
|
+
* Sets the new user handler for command execution requests. This handler will be called whenever an update request
|
|
68
68
|
* arrives to a with the following parameters: (id, type, attributes, callback). The callback is in charge of updating
|
|
69
69
|
* the corresponding values in the devices with the appropriate protocol.
|
|
70
70
|
*
|
|
@@ -77,6 +77,18 @@ function setCommandHandler(newHandler) {
|
|
|
77
77
|
contextServerUtils.commandHandler = newHandler;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Sets the new user handler for NGSI-LD merge-patch requests. This handler will be called whenever an update request
|
|
82
|
+
* arrives to a with the following parameters: (id, type, attributes, callback). The callback is in charge of updating
|
|
83
|
+
* the corresponding values in the devices with the appropriate protocol.
|
|
84
|
+
*
|
|
85
|
+
*
|
|
86
|
+
* @param {Function} newHandler User handler for update requests
|
|
87
|
+
*/
|
|
88
|
+
function setMergePatchHandler(newHandler) {
|
|
89
|
+
contextServerUtils.mergePatchHandler = newHandler;
|
|
90
|
+
}
|
|
91
|
+
|
|
80
92
|
/**
|
|
81
93
|
* Sets the new user handler for Entity query requests. This handler will be called whenever an update request arrives
|
|
82
94
|
* with the following parameters: (id, type, attributes, callback). The handler must retrieve all the corresponding
|
|
@@ -140,6 +152,7 @@ function clear(callback) {
|
|
|
140
152
|
exports.clear = clear;
|
|
141
153
|
exports.loadContextRoutes = intoTrans(context, loadContextRoutes);
|
|
142
154
|
exports.setUpdateHandler = intoTrans(context, setUpdateHandler);
|
|
155
|
+
exports.setMergePatchHandler = intoTrans(context, setMergePatchHandler);
|
|
143
156
|
exports.setCommandHandler = intoTrans(context, setCommandHandler);
|
|
144
157
|
exports.setNotificationHandler = intoTrans(context, setNotificationHandler);
|
|
145
158
|
exports.addNotificationMiddleware = intoTrans(context, addNotificationMiddleware);
|
|
@@ -110,6 +110,7 @@ function clear(callback) {
|
|
|
110
110
|
async.series([deviceProvisioning.clear, groupProvisioning.clear, contextServer.clear], callback);
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
exports.setMergePatchHandler = intoTrans(context, contextServer.setMergePatchHandler);
|
|
113
114
|
exports.setUpdateHandler = intoTrans(context, contextServer.setUpdateHandler);
|
|
114
115
|
exports.setQueryHandler = intoTrans(context, contextServer.setQueryHandler);
|
|
115
116
|
exports.setCommandHandler = intoTrans(context, contextServer.setCommandHandler);
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "iotagent-node-lib",
|
|
3
3
|
"license": "AGPL-3.0-only",
|
|
4
4
|
"description": "IoT Agent library to interface with NGSI Context Broker",
|
|
5
|
-
"version": "2.
|
|
5
|
+
"version": "2.26.0",
|
|
6
6
|
"homepage": "https://github.com/telefonicaid/iotagent-node-lib",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"fiware",
|
|
@@ -241,7 +241,7 @@ describe('NGSI-v2 - In memory device registry', function () {
|
|
|
241
241
|
should.exist(device.type);
|
|
242
242
|
device.name.should.equal('name5');
|
|
243
243
|
device.type.should.equal('Light5');
|
|
244
|
-
Object.keys(device).length.should.equal(
|
|
244
|
+
Object.keys(device).length.should.equal(12);
|
|
245
245
|
done();
|
|
246
246
|
});
|
|
247
247
|
});
|