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.
Files changed (33) hide show
  1. package/CHANGES_NEXT_RELEASE +0 -1
  2. package/config.js +6 -1
  3. package/doc/deprecated.md +4 -1
  4. package/doc/howto.md +8 -0
  5. package/doc/installationguide.md +18 -0
  6. package/doc/usermanual.md +77 -0
  7. package/lib/commonConfig.js +15 -1
  8. package/lib/constants.js +1 -0
  9. package/lib/fiware-iotagent-lib.js +1 -0
  10. package/lib/services/devices/deviceService.js +1 -0
  11. package/lib/services/devices/registrationUtils.js +21 -1
  12. package/lib/services/northBound/contextServer-NGSI-LD.js +221 -38
  13. package/lib/services/northBound/contextServer.js +14 -1
  14. package/lib/services/northBound/northboundServer.js +1 -0
  15. package/package.json +1 -1
  16. package/test/unit/memoryRegistry/deviceRegistryMemory_test.js +1 -1
  17. package/test/unit/ngsi-ld/examples/contextAvailabilityRequests/registerIoTAgent1.json +24 -14
  18. package/test/unit/ngsi-ld/examples/contextAvailabilityRequests/registerIoTAgent2.json +25 -15
  19. package/test/unit/ngsi-ld/examples/contextAvailabilityRequests/registerIoTAgent4.json +15 -5
  20. package/test/unit/ngsi-ld/examples/contextAvailabilityRequests/registerIoTAgentCommands.json +9 -1
  21. package/test/unit/ngsi-ld/examples/contextAvailabilityRequests/registerIoTAgentCommandsAndLazy.json +32 -0
  22. package/test/unit/ngsi-ld/examples/contextAvailabilityRequests/registerProvisionedDevice.json +12 -1
  23. package/test/unit/ngsi-ld/examples/contextAvailabilityRequests/registerProvisionedDevice2.json +9 -1
  24. package/test/unit/ngsi-ld/examples/contextAvailabilityRequests/registerProvisionedDeviceWithGroup.json +12 -1
  25. package/test/unit/ngsi-ld/examples/contextAvailabilityRequests/registerProvisionedDeviceWithGroup2.json +12 -1
  26. package/test/unit/ngsi-ld/examples/contextAvailabilityRequests/registerProvisionedDeviceWithGroup3.json +9 -1
  27. package/test/unit/ngsi-ld/examples/contextAvailabilityRequests/updateCommands1.json +10 -2
  28. package/test/unit/ngsi-ld/examples/contextAvailabilityRequests/updateIoTAgent1.json +11 -1
  29. package/test/unit/ngsi-ld/examples/contextAvailabilityRequests/updateIoTAgent2.json +12 -1
  30. package/test/unit/ngsi-ld/general/startup-test.js +17 -2
  31. package/test/unit/ngsi-ld/lazyAndCommands/command-test.js +1 -12
  32. package/test/unit/ngsi-ld/lazyAndCommands/merge-patch-test.js +249 -0
  33. package/test/unit/ngsi-ld/ngsiService/unsupported-endpoints-test.js +171 -0
@@ -1 +0,0 @@
1
-
package/config.js CHANGED
@@ -29,7 +29,12 @@ var config = {
29
29
  },
30
30
  server: {
31
31
  port: 4041,
32
- host: '0.0.0.0'
32
+ host: '0.0.0.0',
33
+ ldSupport : {
34
+ null: true,
35
+ datasetId: true,
36
+ merge: false
37
+ }
33
38
  },
34
39
  authentication: {
35
40
  enabled: true,
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
@@ -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
@@ -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 queryErrorHandlingNgsiLD(error, req, res, next) {
493
- let code = 500;
678
+ function ErrorHandlingNgsiLD(action) {
679
+ return function errorHandle(error, req, res, next) {
680
+ let code = 500;
494
681
 
495
- logger.debug(context, 'Query NGSI-LD error [%s] handling request: %s', error.name, error.message);
682
+ logger.debug(context, action + ' NGSI-LD error [%s] handling request: %s', error.name, error.message);
496
683
 
497
- if (error.code && String(error.code).match(/^[2345]\d\d$/)) {
498
- code = error.code;
499
- }
684
+ if (error.code && String(error.code).match(/^[2345]\d\d$/)) {
685
+ code = error.code;
686
+ }
500
687
 
501
- res.status(code).json({
502
- error: error.name,
503
- description: error.message.replace(/[<>\"\'=;\(\)]/g, '')
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 http://fiware.github.io/specifications/ngsiv2/stable.
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
- updateErrorHandlingNgsiLD
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
- updateErrorHandlingNgsiLD
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, queryErrorHandlingNgsiLD]);
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
- queryErrorHandlingNgsiLD
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 commadn execution requests. This handler will be called whenever an update request
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.25.0",
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(11);
244
+ Object.keys(device).length.should.equal(12);
245
245
  done();
246
246
  });
247
247
  });