From 7dc6a4abf64db55638887c37093deab7b68c96a5 Mon Sep 17 00:00:00 2001 From: ranim-n Date: Mon, 10 Feb 2025 16:58:31 +0100 Subject: [PATCH 1/5] feat: add a README file --- README.md | 120 ++++++++++++++++++++++++++++++++++++ docs/images/Ckan config.png | Bin 0 -> 52543 bytes 2 files changed, 120 insertions(+) create mode 100644 README.md create mode 100644 docs/images/Ckan config.png diff --git a/README.md b/README.md new file mode 100644 index 0000000..4c32721 --- /dev/null +++ b/README.md @@ -0,0 +1,120 @@ +# NgsiLdToCkan + +[![FIWARE](https://nexus.lab.fiware.org/repository/raw/public/badges/chapters/core.svg)](https://www.fiware.org/developers/catalogue/) +[![NGSI-LD badge](https://img.shields.io/badge/NGSI-LD-red.svg)](https://www.etsi.org/deliver/etsi_gs/CIM/001_099/009/01.06.01_60/gs_CIM009v010601p.pdf) +[![License: Apache-2.0](https://img.shields.io/github/license/stellio-hub/stellio-context-broker.svg)](https://spdx.org/licenses/Apache-2.0.html) +![Build](https://github.com/easy-global-market/nifi-ngsild-ckan/actions/workflows/maven.yml/badge.svg) + +## Table of Contents +- [Overview](#overview) +- [Functionality](#functionality) + - [CKAN Data Structures And Mapping](#ckan-data-structures-and-mapping) + - [Input Data](#input-data) + - [Output](#output) +- [Configuration](#configuration) + - [Processor Properties](#processor-properties) +- [Metadata](#metadata) + - [Metadata List](#metadata-list) + +## Overview +`NgsiLdToCkan` is a Nifi processor developed by FIWARE to persist NGSI-LD context data events within a [CKAN](https://ckan.org/) server. Context data is received as a notification sent by a Context Broker or any system that supports NGSI-LD. + + +## Functionality + +The processor receives NGSI-LD events (notifications) and converts them internally into an `NGSIEvent` objects, which contain the entities to be published on the CKAN server. + +### CKAN Data Structures And Mapping + +CKAN is a powerful platform for managing and publishing collections of data. These collections are owned by organizations. Each organization contains multiple datasets (also called packages) and every dataset consists of several resources. +A resource is the structure that holds the data itself. In addition, a dataset can also contain metadata (information about the context data). + +When context data is consumed by the `NgsiLdToCkan` processor, each entity will be persisted in the CKAN server as a resource belonging to an organization and a dataset. + +### Input Data + +The input data must be a valid NGSI-LD context data with a `data` element containing the entities to be persisted in the CKAN server. Each entity must have a `title` attribute that will +be used as a resource name. A `datasetTitle` must exist as a flowfile attribute to be used as a dataset name. + +In addition, metadata can also be added as flowfile attributes to be extracted by the processor ([metadata list](#metadata-list)). The `keywords` attribute however must always be a present. +It is a comma seperated list of terms that will be added as tags to the dataset: ["keyword1", "keyword2", "keyword3"] . + +**Naming conventions** + +Names for an organization, a dataset, or a resource must only contain alphanumeric characters, `-` , or `_`. The length must be between 2 and 100 characters. + +**Example of an NGSI-LD event** +``` +{ + "id": "urn:ngsi-ld:Notification:1", + "type": "Notification", + "subscriptionId": "urn:ngsi-ld:Subscription:CKAN", + "notifiedAt": "2025-02-10T13:29:53.903986Z", + "data": [ + { + "id": "urn:ngsi-ld:Entity:1", + "type": "Entity", + "title": { + "type": "Property", + "value": "Entity example" + }, + "name": { + "type": "Property", + "value": "Entity name", + "observedAt": "2025-02-10T10:18:20Z" + } + } + ] +} +``` +### Output + +The `NgsiLdToCkan` processor publishes all entities received in the notified context data in the same dataset. Each entity will be added as a resource +with a default `application/ld+json` format. + +## Configuration + +### Processor Properties + + + +`CKAN Viewer` property specifies the visualization of the resource data on the CKAN resource page. + +`CKAN API Key` property is a token generated from the user account on the CKAN site. + +`Create DataStore` property creates the resource when set to true. + +## Metadata + +### Metadata List + +| Metadata for datasets | +|-----------------------| +| packageDescription | +| version | +| landingPage | +| visibility | +| organizationType | +| contactPoint | +| contactName | +| contactEmail | +| spatialUri | +| spatialCoverage | +| temporalStart | +| temporalEnd | +| themes | +| datasetRights | +| keywords | + + +| Metadata for resources | +|------------------------| +| accessURL | +| availability | +| resourceDescription | +| mimeType | +| license | +| downloadURL | +| byteSize | +| resourceRights | +| licenseType | diff --git a/docs/images/Ckan config.png b/docs/images/Ckan config.png new file mode 100644 index 0000000000000000000000000000000000000000..f2ebbd141ba98f2b34dedf649d4c8c3aa4205660 GIT binary patch literal 52543 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 xBkXPJ>xl{IqaUV-C7$>yqj Date: Tue, 11 Feb 2025 17:17:32 +0100 Subject: [PATCH 2/5] fix: add more details about mapping --- README.md | 40 ++++++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 4c32721..95d46cd 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # NgsiLdToCkan -[![FIWARE](https://nexus.lab.fiware.org/repository/raw/public/badges/chapters/core.svg)](https://www.fiware.org/developers/catalogue/) -[![NGSI-LD badge](https://img.shields.io/badge/NGSI-LD-red.svg)](https://www.etsi.org/deliver/etsi_gs/CIM/001_099/009/01.06.01_60/gs_CIM009v010601p.pdf) [![License: Apache-2.0](https://img.shields.io/github/license/stellio-hub/stellio-context-broker.svg)](https://spdx.org/licenses/Apache-2.0.html) ![Build](https://github.com/easy-global-market/nifi-ngsild-ckan/actions/workflows/maven.yml/badge.svg) @@ -15,21 +13,42 @@ - [Processor Properties](#processor-properties) - [Metadata](#metadata) - [Metadata List](#metadata-list) +- [Limitations](#limitations) ## Overview -`NgsiLdToCkan` is a Nifi processor developed by FIWARE to persist NGSI-LD context data events within a [CKAN](https://ckan.org/) server. Context data is received as a notification sent by a Context Broker or any system that supports NGSI-LD. +`NgsiLdToCkan` is a NiFi processor developed to persist NGSI-LD context data events within a [CKAN](https://ckan.org/) server. Context data is received as a notification sent by a Context Broker or any system that supports NGSI-LD. ## Functionality -The processor receives NGSI-LD events (notifications) and converts them internally into an `NGSIEvent` objects, which contain the entities to be published on the CKAN server. +The processor receives NGSI-LD events (notifications) which contain the entities to be published on the CKAN server. ### CKAN Data Structures And Mapping CKAN is a powerful platform for managing and publishing collections of data. These collections are owned by organizations. Each organization contains multiple datasets (also called packages) and every dataset consists of several resources. A resource is the structure that holds the data itself. In addition, a dataset can also contain metadata (information about the context data). -When context data is consumed by the `NgsiLdToCkan` processor, each entity will be persisted in the CKAN server as a resource belonging to an organization and a dataset. +CKAN offers a generic mapping between its model and [DCAT](https://www.w3.org/TR/vocab-dcat-3/) classes like `dcat:Dataset` and `dcat:Distribution`. + +In this mapping, a `Dataset` entity in the Context Broker is equivalent to a dataset in CKAN, while a `Distribution` entity is equivalent to a resource. However, a `Catalog` entity is not directly equivalent to an organization +because the concept of an organization in CKAN does not directly align with the concept of a `dcat:Catalog`. Therefore, a `X-CKAN-OrganizationName` key-value pair must be added to the `receiverInfo` in the `Subscription` that triggers +the notified context data. This value will be used as the name of the organization owning the resource. + +``` +"endpoint": { + "uri": "http://localhost:8080/ckanListener", + "accept": "application/json", + "receiverInfo": [ + { + "key": "X-CKAN-OrganizationName", + "value": "Organization Name" + } + ] +} +``` + + +A more straight-forward mapping from CKAN metadata to DCAT metadata is also done. ### Input Data @@ -69,7 +88,7 @@ Names for an organization, a dataset, or a resource must only contain alphanumer ``` ### Output -The `NgsiLdToCkan` processor publishes all entities received in the notified context data in the same dataset. Each entity will be added as a resource +The `NgsiLdToCkan` processor publishes all entities of the same type received in the notified context data in the same dataset. Each entity will be added as a resource with a default `application/ld+json` format. ## Configuration @@ -118,3 +137,12 @@ with a default `application/ld+json` format. | byteSize | | resourceRights | | licenseType | + + +## Limitations + +* The processor does not support multi-attributes instances. +* The processor only supports attributes of type `Property`, `Relationship` and `GeoProperty`. +* An already existing resource can't be updated with new attributes. +* There's no description for datasets. +* Not all DCAT metadata available in a dataset is utilized (for example `publisher` metadata which can be used when creating an organization) \ No newline at end of file From 1d9d66485a066b00eab2178ae5989eadcb0c9c43 Mon Sep 17 00:00:00 2001 From: ranim-n Date: Thu, 13 Feb 2025 18:11:24 +0100 Subject: [PATCH 3/5] feat: add NiFi flow template and requirements section --- CKAN_User_Guide_Template.xml | 544 +++++++++++++++++++++++++++++++++++ README.md | 150 ++++++---- 2 files changed, 637 insertions(+), 57 deletions(-) create mode 100644 CKAN_User_Guide_Template.xml diff --git a/CKAN_User_Guide_Template.xml b/CKAN_User_Guide_Template.xml new file mode 100644 index 0000000..c0ac381 --- /dev/null +++ b/CKAN_User_Guide_Template.xml @@ -0,0 +1,544 @@ + + diff --git a/README.md b/README.md index 95d46cd..c4ace6a 100644 --- a/README.md +++ b/README.md @@ -9,19 +9,21 @@ - [CKAN Data Structures And Mapping](#ckan-data-structures-and-mapping) - [Input Data](#input-data) - [Output](#output) +- [Requirements](#requirements) - [Configuration](#configuration) - [Processor Properties](#processor-properties) - [Metadata](#metadata) - - [Metadata List](#metadata-list) +- [Template](#template) - [Limitations](#limitations) +- [Roadmap & Issues](#roadmap--issues) ## Overview -`NgsiLdToCkan` is a NiFi processor developed to persist NGSI-LD context data events within a [CKAN](https://ckan.org/) server. Context data is received as a notification sent by a Context Broker or any system that supports NGSI-LD. +`NgsiLdToCkan` is a NiFi processor developed to persist NGSI-LD context data within a [CKAN](https://ckan.org/) server. Context data is received as a notification sent by a Context Broker or any system that supports NGSI-LD. ## Functionality -The processor receives NGSI-LD events (notifications) which contain the entities to be published on the CKAN server. +The processor receives NGSI-LD notifications which contain the entities to be published on the CKAN server. ### CKAN Data Structures And Mapping @@ -31,38 +33,26 @@ A resource is the structure that holds the data itself. In addition, a dataset c CKAN offers a generic mapping between its model and [DCAT](https://www.w3.org/TR/vocab-dcat-3/) classes like `dcat:Dataset` and `dcat:Distribution`. In this mapping, a `Dataset` entity in the Context Broker is equivalent to a dataset in CKAN, while a `Distribution` entity is equivalent to a resource. However, a `Catalog` entity is not directly equivalent to an organization -because the concept of an organization in CKAN does not directly align with the concept of a `dcat:Catalog`. Therefore, a `X-CKAN-OrganizationName` key-value pair must be added to the `receiverInfo` in the `Subscription` that triggers -the notified context data. This value will be used as the name of the organization owning the resource. +because the concept of an organization in CKAN does not directly align with the concept of a `dcat:Catalog`. -``` -"endpoint": { - "uri": "http://localhost:8080/ckanListener", - "accept": "application/json", - "receiverInfo": [ - { - "key": "X-CKAN-OrganizationName", - "value": "Organization Name" - } - ] -} -``` +A more straight-forward mapping from CKAN metadata to DCAT metadata is also done. -A more straight-forward mapping from CKAN metadata to DCAT metadata is also done. +For more in-depth information about `dcat:Dataset` and `dcat:Distribution` see [Smart data models - DCAT data model](https://github.com/smart-data-models/dataModel.DCAT-AP). ### Input Data The input data must be a valid NGSI-LD context data with a `data` element containing the entities to be persisted in the CKAN server. Each entity must have a `title` attribute that will be used as a resource name. A `datasetTitle` must exist as a flowfile attribute to be used as a dataset name. -In addition, metadata can also be added as flowfile attributes to be extracted by the processor ([metadata list](#metadata-list)). The `keywords` attribute however must always be a present. +In addition, metadata can also be added as flowfile attributes to be extracted by the processor. The `keywords` attribute however must always be present. It is a comma seperated list of terms that will be added as tags to the dataset: ["keyword1", "keyword2", "keyword3"] . **Naming conventions** Names for an organization, a dataset, or a resource must only contain alphanumeric characters, `-` , or `_`. The length must be between 2 and 100 characters. -**Example of an NGSI-LD event** +**Example of a NGSI-LD notification the processor receives** ``` { "id": "urn:ngsi-ld:Notification:1", @@ -91,53 +81,94 @@ Names for an organization, a dataset, or a resource must only contain alphanumer The `NgsiLdToCkan` processor publishes all entities of the same type received in the notified context data in the same dataset. Each entity will be added as a resource with a default `application/ld+json` format. +## Requirements + +A `Subscription` must be created to trigger the notifications sent when entities are created or updated. + +Since there is no direct mapping between `dcat:Catalog` and an organization in CKAN, a `X-CKAN-OrganizationName` key-value pair must be added to the `receiverInfo` in the `Subscription`. +This value will be extracted in the processor as a flowfile attribute and used as the name of the organization owning the resource. + +``` +{ + "id": "urn:ngsi-ld:Subscription:CKAN", + "type": "Subscription", + "entities": [ + { + "type": "Dataset" + } + ], + "notificationTrigger": [ + "entityCreated", + "entityUpdated" + ], + "notification": { + "format": "normalized", + "endpoint": { + "uri": "http://localhost:8080/ckanListener", + "accept": "application/json", + "receiverInfo": [ + { + "key": "X-CKAN-OrganizationName", + "value": "Organization Name" + } + ] + } + } +} +``` + ## Configuration ### Processor Properties -`CKAN Viewer` property specifies the visualization of the resource data on the CKAN resource page. +* `CKAN Viewer` property specifies the visualization of the resource data on the CKAN resource page. -`CKAN API Key` property is a token generated from the user account on the CKAN site. +* `CKAN API Key` property is a token generated from the user account on the CKAN site. -`Create DataStore` property creates the resource when set to true. +* `Create DataStore` property creates the resource when set to true. ## Metadata -### Metadata List - -| Metadata for datasets | -|-----------------------| -| packageDescription | -| version | -| landingPage | -| visibility | -| organizationType | -| contactPoint | -| contactName | -| contactEmail | -| spatialUri | -| spatialCoverage | -| temporalStart | -| temporalEnd | -| themes | -| datasetRights | -| keywords | - - -| Metadata for resources | -|------------------------| -| accessURL | -| availability | -| resourceDescription | -| mimeType | -| license | -| downloadURL | -| byteSize | -| resourceRights | -| licenseType | - +Metadata treated by the processor and added in the CKAN server can be mapped to DCAT metadata. The following tables provide a list of metadata the processor extracts +as flowfile attributes and its equivalent in `dcat:Dataset` and `dcat:Distribution`. + + +| CKAN Metadata for datasets | Equivalent in `dcat:Dataset` | flowfile attribute name in `NgsiLdToCkan` processor | +|----------------------------|------------------------------|-----------------------------------------------------| +| notes | description | packageDescription | +| version | version | version | +| url | landingPage | landingPage | +| visibility | - | visibility | +| Publisher_type | isPublishedBy | organizationType | +| contact_uri | contactPoint | contactPoint | +| contact_name | - | contactName | +| contact_email | - | contactEmail | +| spatial_uri | - | spatialUri | +| spatial | spatial | spatialCoverage | +| temporal_start | - | temporalStart | +| temporal_end | - | temporalEnd | +| theme | theme | themes | +| access_rights | accessRights | datasetRights | +| tags | keyword | keywords | + + +| CKAN Metadata for resources | Equivalent in `dcat:Distribution` | flowfile attribute name in `NgsiLdToCkan` processor | +|-----------------------------|-----------------------------------|-----------------------------------------------------| +| access_url | accessURL | accessURL | +| availability | availability | availability | +| description | description | resourceDescription | +| mimetype | mediaType | mimeType | +| download_url | downloadURL | downloadURL | +| size | byteSize | byteSize | +| rights | rights | resourceRights | +| license | license | license | +| license_type | - | licenseType | + + +## Template +A basic NiFi template with the `NgsiLdToCkan` processor can be found [here](CKAN_User_Guide_Template.xml). ## Limitations @@ -145,4 +176,9 @@ with a default `application/ld+json` format. * The processor only supports attributes of type `Property`, `Relationship` and `GeoProperty`. * An already existing resource can't be updated with new attributes. * There's no description for datasets. -* Not all DCAT metadata available in a dataset is utilized (for example `publisher` metadata which can be used when creating an organization) \ No newline at end of file +* Not all DCAT metadata available in a dataset is utilized (for example `publisher` metadata which can be used when creating an organization) +* Metadata and dataset title must be added as flowfile attributes instead of being extracted inside the processor (this can be avoided with the use of NGSI-LD Linked Entity Retrieval). + +## Roadmap & Issues + +To check out planned features, report bugs or suggest new features see [open issues](https://github.com/easy-global-market/nifi-ngsild-ckan/issues). \ No newline at end of file From ab55fed8bf3f6af3b3b457130cc4be9136e5d83b Mon Sep 17 00:00:00 2001 From: Benoit Orihuela Date: Fri, 14 Feb 2025 08:56:44 +0100 Subject: [PATCH 4/5] doc: rewording and improvements --- README.md | 67 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index c4ace6a..5367ebb 100644 --- a/README.md +++ b/README.md @@ -18,42 +18,47 @@ - [Roadmap & Issues](#roadmap--issues) ## Overview -`NgsiLdToCkan` is a NiFi processor developed to persist NGSI-LD context data within a [CKAN](https://ckan.org/) server. Context data is received as a notification sent by a Context Broker or any system that supports NGSI-LD. +`NgsiLdToCkan` is a NiFi processor that persists NGSI-LD entities within a [CKAN](https://ckan.org/) server. +Entities are received through NGSI-LD notifications sent by a NGSI-LD Context Broker. ## Functionality -The processor receives NGSI-LD notifications which contain the entities to be published on the CKAN server. - ### CKAN Data Structures And Mapping -CKAN is a powerful platform for managing and publishing collections of data. These collections are owned by organizations. Each organization contains multiple datasets (also called packages) and every dataset consists of several resources. -A resource is the structure that holds the data itself. In addition, a dataset can also contain metadata (information about the context data). +CKAN is a powerful platform for managing and publishing collections of data. These collections are owned by organizations. +Each organization contains multiple datasets (called packages in CKAN) and every dataset consists of several resources. +A resource is the structure that holds the data itself. In addition, a dataset also contains metadata (information about the data). -CKAN offers a generic mapping between its model and [DCAT](https://www.w3.org/TR/vocab-dcat-3/) classes like `dcat:Dataset` and `dcat:Distribution`. +CKAN offers a generic mapping between its model and [DCAT](https://www.w3.org/TR/vocab-dcat-3/) classes like `dcat:Catalog`, `dcat:Dataset` and `dcat:Distribution`. -In this mapping, a `Dataset` entity in the Context Broker is equivalent to a dataset in CKAN, while a `Distribution` entity is equivalent to a resource. However, a `Catalog` entity is not directly equivalent to an organization +In this mapping, a `Dataset` entity in the Context Broker is equivalent to a dataset in CKAN, while a `Distribution` entity +is equivalent to a resource. However, a `Catalog` entity is not directly equivalent to an organization because the concept of an organization in CKAN does not directly align with the concept of a `dcat:Catalog`. A more straight-forward mapping from CKAN metadata to DCAT metadata is also done. - For more in-depth information about `dcat:Dataset` and `dcat:Distribution` see [Smart data models - DCAT data model](https://github.com/smart-data-models/dataModel.DCAT-AP). -### Input Data +When creating datasets and resources in CKAN, the processor leverages the attributes in the received NGSI-LD entities to +enrich the created datasets and resources with the metadata that it can extract from the NGSI-LD entities. One goal of this +behavior is also to expose datasets and resources that comply with the [FAIR principles](https://www.go-fair.org/fair-principles/). -The input data must be a valid NGSI-LD context data with a `data` element containing the entities to be persisted in the CKAN server. Each entity must have a `title` attribute that will -be used as a resource name. A `datasetTitle` must exist as a flowfile attribute to be used as a dataset name. +### Input Data -In addition, metadata can also be added as flowfile attributes to be extracted by the processor. The `keywords` attribute however must always be present. -It is a comma seperated list of terms that will be added as tags to the dataset: ["keyword1", "keyword2", "keyword3"] . +The input data must be a valid NGSI-LD notification containing the entities to be persisted in the CKAN server. +Each entity must at leat have a `title` attribute that will be used as the name of the resource. +A `datasetTitle` must exist as a flowfile attribute to be used as the name of the dataset belonging to the resource. +(future versions of the processor will automatically retrieve dataset information by using the relationship between a +`Distribution` entity and a `Dataset` entity). -**Naming conventions** +In addition, other metadata can also be added as flowfile attributes to be extracted by the processor. +The `keywords` attribute however must always be present. It is a comma seperated list of terms that will be added as tags +to the dataset (e.g., ["keyword1", "keyword2", "keyword3"]). -Names for an organization, a dataset, or a resource must only contain alphanumeric characters, `-` , or `_`. The length must be between 2 and 100 characters. +**Example of a NGSI-LD notification received by the processor** -**Example of a NGSI-LD notification the processor receives** -``` +```json { "id": "urn:ngsi-ld:Notification:1", "type": "Notification", @@ -61,8 +66,8 @@ Names for an organization, a dataset, or a resource must only contain alphanumer "notifiedAt": "2025-02-10T13:29:53.903986Z", "data": [ { - "id": "urn:ngsi-ld:Entity:1", - "type": "Entity", + "id": "urn:ngsi-ld:Distribution:1", + "type": "Distribution", "title": { "type": "Property", "value": "Entity example" @@ -76,25 +81,30 @@ Names for an organization, a dataset, or a resource must only contain alphanumer ] } ``` + ### Output -The `NgsiLdToCkan` processor publishes all entities of the same type received in the notified context data in the same dataset. Each entity will be added as a resource -with a default `application/ld+json` format. +The `NgsiLdToCkan` processor publishes all entities of the same type in the same dataset. +Each entity is as a resource with a default `application/ld+json` format. + +In CKAN, names for an organization, a dataset, or a resource must only contain alphanumeric characters, `-` , or `_`. +The length must be between 2 and 100 characters. Thus, a transformation is automatically performed by the processor. ## Requirements A `Subscription` must be created to trigger the notifications sent when entities are created or updated. -Since there is no direct mapping between `dcat:Catalog` and an organization in CKAN, a `X-CKAN-OrganizationName` key-value pair must be added to the `receiverInfo` in the `Subscription`. -This value will be extracted in the processor as a flowfile attribute and used as the name of the organization owning the resource. +Since there is no direct mapping between `dcat:Catalog` and an organization in CKAN, a `X-CKAN-OrganizationName` key-value pair +must be added to the `receiverInfo` in the `Subscription`. This value will be extracted in the processor as a flowfile +attribute and used as the name of the organization owning the resource. -``` +```json { "id": "urn:ngsi-ld:Subscription:CKAN", "type": "Subscription", "entities": [ { - "type": "Dataset" + "type": "Distribution" } ], "notificationTrigger": [ @@ -127,7 +137,7 @@ This value will be extracted in the processor as a flowfile attribute and used a * `CKAN API Key` property is a token generated from the user account on the CKAN site. -* `Create DataStore` property creates the resource when set to true. +* `Create DataStore` property creates the resource in the datastore when set to true. ## Metadata @@ -168,9 +178,10 @@ as flowfile attributes and its equivalent in `dcat:Dataset` and `dcat:Distributi ## Template + A basic NiFi template with the `NgsiLdToCkan` processor can be found [here](CKAN_User_Guide_Template.xml). -## Limitations +## Current limitations * The processor does not support multi-attributes instances. * The processor only supports attributes of type `Property`, `Relationship` and `GeoProperty`. @@ -181,4 +192,4 @@ A basic NiFi template with the `NgsiLdToCkan` processor can be found [here](CKAN ## Roadmap & Issues -To check out planned features, report bugs or suggest new features see [open issues](https://github.com/easy-global-market/nifi-ngsild-ckan/issues). \ No newline at end of file +To check out planned features, report bugs or suggest new features see [open issues](https://github.com/easy-global-market/nifi-ngsild-ckan/issues). From 09bb482fc2f465b815b401b325d0bc7a60857def Mon Sep 17 00:00:00 2001 From: Benoit Orihuela Date: Tue, 18 Feb 2025 15:54:46 +0100 Subject: [PATCH 5/5] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5367ebb..35e32c6 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ CKAN is a powerful platform for managing and publishing collections of data. The Each organization contains multiple datasets (called packages in CKAN) and every dataset consists of several resources. A resource is the structure that holds the data itself. In addition, a dataset also contains metadata (information about the data). -CKAN offers a generic mapping between its model and [DCAT](https://www.w3.org/TR/vocab-dcat-3/) classes like `dcat:Catalog`, `dcat:Dataset` and `dcat:Distribution`. +CKAN offers a generic mapping between its model and [DCAT](https://www.w3.org/TR/vocab-dcat-3/) classes like `dcat:Dataset` and `dcat:Distribution`. In this mapping, a `Dataset` entity in the Context Broker is equivalent to a dataset in CKAN, while a `Distribution` entity is equivalent to a resource. However, a `Catalog` entity is not directly equivalent to an organization